简体   繁体   English

一个 akka 流函数,它创建一个接收器和一个发出接收器接收到的任何东西的源

[英]An akka streams function that creates a sink and a source that emits whatever the sink receives

The goal is to implement a function with this signature目标是用这个签名实现一个函数

def bindedSinkAndSource[A]:(Sink[A, Any], Source[A, Any]) = ???

where the returned source emits whatever the sink receives.返回的源发出接收器接收到的任何内容。

My primary goal is to implement a websocket forwarder by means of the handleWebSocketMessages directive.我的主要目标是通过handleWebSocketMessages指令实现一个 websocket 转发器。
The forwarder graph is:转发器图为:

leftReceiver ~> rightEmitter
leftEmitter <~ rightReceiver

where the leftReceiver and leftEmiter are the in and out of the left endpoint handler flow;其中leftReceiverleftEmiter是左端点处理程序流的进出; and rightReceiver and rightEmitter are the in and out of the right endpoint handler flow.rightReceiverrightEmitter是进出右端点处理程序流。

For example:例如:

import akka.NotUsed
import akka.http.scaladsl.model.ws.Message
import akka.http.scaladsl.server.Directive.addByNameNullaryApply
import akka.http.scaladsl.server.Directives._
import akka.http.scaladsl.server.Route
import akka.stream.scaladsl.Flow
import akka.stream.scaladsl.Sink
import akka.stream.scaladsl.Source

def buildHandlers(): Route = {
    val (leftReceiver, rightEmitter) = bindedSinkAndSource[Message];
    val (rightReceiver, leftEmitter) = bindedSinkAndSource[Message];

    val leftHandlerFlow = Flow.fromSinkAndSource(leftReceiver, leftEmitter)
    val rightHandlerFlow = Flow.fromSinkAndSource(rightReceiver, rightEmitter)

    pathPrefix("leftEndpointChannel") {
        handleWebSocketMessages(leftHandlerFlow)
    } ~
        pathPrefix("rightEndpointChannel") {
            handleWebSocketMessages(rightHandlerFlow)
        }
}

All the ideas that came to me were frustrated by the fact that the handleWebSocketMessages(..) directive don't give access to the materialized value of the received flow.由于handleWebSocketMessages(..)指令无法访问接收到的流的物化值,我想到的所有想法都感到沮丧。

I found a way to achieve the goal, but there could be shorter and easier ways.我找到了实现目标的方法,但可能还有更短、更简单的方法。 If you know one, please don't hesitate to add your knowledge.如果你知道一个,请不要犹豫,添加你的知识。

import org.reactivestreams.Publisher
import org.reactivestreams.Subscriber
import org.reactivestreams.Subscription

import akka.NotUsed
import akka.stream.scaladsl.Sink
import akka.stream.scaladsl.Source

def bindedSinkAndSource[A]: (Sink[A, NotUsed], Source[A, NotUsed]) = {

    class Binder extends Subscriber[A] with Publisher[A] { binder =>
        var oUpStreamSubscription: Option[Subscription] = None;
        var oDownStreamSubscriber: Option[Subscriber[_ >: A]] = None;
        var pendingRequestFromDownStream: Option[Long] = None;
        var pendingCancelFromDownStream: Boolean = false;

        def onSubscribe(upStreamSubscription: Subscription): Unit = {
            this.oUpStreamSubscription match {
                case Some(_) => upStreamSubscription.cancel // rule 2-5
                case None =>
                    this.oUpStreamSubscription = Some(upStreamSubscription);
                    if (pendingRequestFromDownStream.isDefined) {
                        upStreamSubscription.request(pendingRequestFromDownStream.get)
                        pendingRequestFromDownStream = None
                    }
                    if (pendingCancelFromDownStream) {
                        upStreamSubscription.cancel()
                        pendingCancelFromDownStream = false
                    }
            }
        }

        def onNext(a: A): Unit = {
            oDownStreamSubscriber.get.onNext(a)
        }

        def onComplete(): Unit = {
            oDownStreamSubscriber.foreach { _.onComplete() };
            this.oUpStreamSubscription = None
        }

        def onError(error: Throwable): Unit = {
            oDownStreamSubscriber.foreach { _.onError(error) };
            this.oUpStreamSubscription = None
        }

        def subscribe(downStreamSubscriber: Subscriber[_ >: A]): Unit = {
            assert(this.oDownStreamSubscriber.isEmpty);
            this.oDownStreamSubscriber = Some(downStreamSubscriber);

            downStreamSubscriber.onSubscribe(new Subscription() {
                def request(n: Long): Unit = {
                    binder.oUpStreamSubscription match {
                        case Some(usSub) => usSub.request(n);
                        case None =>
                            assert(binder.pendingRequestFromDownStream.isEmpty);
                            binder.pendingRequestFromDownStream = Some(n);
                    }
                };
                def cancel(): Unit = {
                    binder.oUpStreamSubscription match {
                        case Some(usSub) => usSub.cancel();
                        case None =>
                            assert(binder.pendingCancelFromDownStream == false);
                            binder.pendingCancelFromDownStream = true;
                    }
                    binder.oDownStreamSubscriber = None
                }
            })
        }
    }

    val binder = new Binder;
    val receiver = Sink.fromSubscriber(binder);
    val emitter = Source.fromPublisher(binder);
    (receiver, emitter);
}   

Note that the instance vars of the Binder class may suffer concurrency problems if the sink and source this method creates are not fused later by the user.请注意,如果此方法创建的接收器和源没有被用户稍后融合,则Binder类的实例变量可能会遇到并发问题。 If that is not the case, all the accesses to these variables should be enclosed inside synchronized zones.如果情况并非如此,则对这些变量的所有访问都应包含在同步区域内。 Another solution would be to ensure that the sink and the source are materialized in an execution context with a single thread.另一种解决方案是确保接收器和源在具有单个线程的执行上下文中具体化。

Two days later I discovered MergeHub and BroadcastHub.两天后,我发现了 MergeHub 和 BroadcastHub。 Using them the answer is much shorter:使用它们的答案要短得多:

import akka.stream.Materializer
def bindedSinkAndSource[T](implicit sm: Materializer): (Sink[T, NotUsed], Source[T, NotUsed]) = {
  import akka.stream.scaladsl.BroadcastHub;
  import akka.stream.scaladsl.MergeHub;
  import akka.stream.scaladsl.Keep;

  MergeHub.source[T](perProducerBufferSize = 8).toMat(BroadcastHub.sink[T](bufferSize = 256))(Keep.both) run
}

with the advantage that the returned sink and source can be materialized multiple times.优点是返回的接收器和源可以多次实现。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM