akka流功能,可创建接收器和发出接收器所接收的任何内容的源

时间:2016-09-21 16:31:02

标签: scala websocket akka-stream akka-http

目标是使用此签名实现一个功能

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

返回的源发出接收器的任何内容。

我的主要目标是通过handleWebSocketMessages指令实现websocket转发器 转发器图表是:

leftReceiver ~> rightEmitter
leftEmitter <~ rightReceiver

其中leftReceiverleftEmiter是左端点处理程序流的进出;并且rightReceiverrightEmitter是右端点处理程序流的进出。

例如:

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)
        }
}

由于handleWebSocketMessages(..)指令无法访问所接收流的具体化值,所以我遇到的所有想法都受挫。

1 个答案:

答案 0 :(得分:1)

我找到了实现目标的方法,但可能会有更短更简单的方法。如果您认识一个,请不要犹豫,添加您的知识。

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);
}   

请注意,如果此方法创建的接收器和源不会被用户稍后融合,则Binder类的实例变量可能会遇到并发问题。如果不是这种情况,那么对这些变量的所有访问都应该包含在同步区域内。另一种解决方案是确保使用单个线程在执行上下文中实现接收器和源。

两天后,我发现了MergeHub和BroadcastHub。使用它们的答案要短得多:

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
}

的优点是返回的接收器和源可以多次实现。