More Related Content Similar to Akka HTTP (20) Akka HTTP2. Akka Stream & HTTP
リリースおめでとう!
ノンブロッキングで背圧制御に満ちたエコシステム
形成の幕開けになることを期待します
3. Akka HTTPとは
• akka IOとAkka Streamを使って、NIOかつ背圧制御
に基づいたHTTPモジュール
• 低レベルAPIのakka-http-coreと高レベルAPIのakka-
http-experimentalがある
• Akka Streamがシステム内の調和を保つとしたら、
Akka HTTPはシステム間の調和を実現する
4. マイクロサービスの難しさ
“every single one of your peer teams suddenly becomes a
potential DOS attacker”
周りの全ての同僚チームが、突如 DOS アタッカーになりうる
ようになった
和訳原文
Stevey の “Google プラットフォームぶっちゃけ話”にでてくる
Amazonのマイクロサービス化によるAWS誕生のくだり
システム間のインターフェースとしてもっとも使われるHTTPに
背圧制御をもたらす必要がある
6. Akka Streamの復習
val intSource: Source[Int, NotUsed] = Source(List(1, 2, 3)) //Sourceは1つの出力をもつ。Sourceなどをグラフという。
val toStringFlow: Flow[Int, String, NotUsed] = Flow[Int].map(_.toString) //Flowは1つの入力と1つの出力をもつ。mapなどをステージという
。
val seqSink: Sink[String, Future[Seq[String]]] = Sink.seq[String] //Sinkは1つの入力をもつ。第2型引数がMaterialized Value。
/**
* +-----------------------------------------------------------------------------------+
* | runnableGraph |
* | |
* | +------------+ +--------------+ +---------+ |
* | | | | | | | |
* | | intSource ~ Int ~> ~ Int ~> toStringFlow ~ String ~> ~ String ~> seqSink | |
* | | | | | | | |
* | +------------+ +--------------+ +---------+ |
* +-----------------------------------------------------------------------------------+
*/
// すべてのポートが閉じたグラフはRunnableGraph[Mat]になり、マテリアライズ化が可能になる
val runnableGraph: RunnableGraph[Future[Seq[String]]] =
intSource.via(toStringFlow).toMat(seqSink)((sourceMatValue, sinkMatValue) => sinkMatValue)
// RunnableGraphのrunを呼んでマテリアライズ化する。ここからストリームが動き出す。マテリアライズ化の結果としてMaterialized Valueが
返る。
val materializedValue: Future[Seq[String]] = runnableGraph.run()(ActorMaterializer())
// Materialized Valueからストリームの結果を受け取れる場合がある
val streamResult: Seq[String] = Await.result(materializedValue, 10 seconds) //Seq("1", "2", "3")
9. クライアントサイドAPI
val connectionFlow: Flow[HttpRequest, HttpResponse, Future[OutgoingConnection]] =
Http().outgoingConnection(host, port) //単一のHTTPコネクションストリーム
val responseFuture1: Future[HttpResponse] =
Source.single(HttpRequest(uri = "/"))
.via(connectionFlow)
.runWith(Sink.head)
val poolClientFlow: Flow[(HttpRequest, Int), (Try[HttpResponse], Int), HostConnectionPool] =
Http().cachedHostConnectionPool[Int](host, port) //ホスト単位でコネクションプールをもつストリーム
val responseFuture2: Future[(Try[HttpResponse], Int)] =
Source.single(HttpRequest(uri = "/") -> 1)
.via(poolClientFlow)
.runWith(Sink.head)
10. サーバーサイドAPI
val serverSource: Source[Http.IncomingConnection, Future[Http.ServerBinding]] = Http().bind(host, port) //bindでコネクション
のSourceを得る
val bindingFuture: Future[ServerBinding] = serverSource.to(Sink.foreach { connection =>
val connFlow: Flow[HttpResponse, HttpRequest, NotUsed] = connection.flow //それぞれのコネクションのデータの送受信を表す
フロー
val requestHandler: Flow[HttpRequest, HttpResponse, NotUsed] = Flow[HttpRequest].map {
case HttpRequest(GET, Uri.Path("/"), _, _, _) =>
HttpResponse(entity = HttpEntity(ContentTypes.`text/html(UTF-8)`, "<html><body>Hello world!</body></html>"))
}
/**
* +----------+ +----------------+
* | | ~HttpRequest~> | |
* | connFlow | | requestHandler |
* | | <~HttpResponse~ | |
* +----------+ +----------------+
*/
connFlow.joinMat(requestHandler)(Keep.right).run() //コネクションのフローにリクエストハンドラーを接続してリクエストを消
費する
}).run()
11. HttpRequest, HttpResponse
final case class HttpRequest(method: HttpMethod,
uri: Uri,
headers: immutable.Seq[HttpHeader],
entity: RequestEntity,
protocol: HttpProtocol)
sealed trait RequestEntity extends HttpEntity
final case class HttpResponse(status: StatusCode,
headers: immutable.Seq[HttpHeader],
entity: ResponseEntity,
protocol: HttpProtocol)
sealed trait ResponseEntity extends HttpEntity
sealed trait HttpEntity {
def dataBytes: Source[ByteString, Any]
}
なんとHttpEntityの中身はSource[ByteString, Any]だ
これが効率的なデータの送受信と
WebsoketやSSEなどの異なるプロトコルを統一的に扱うことを可能にしている
12. • Futureなのでコネクションプールの数を超えて並列に呼ぶことができる
• IOの方がCPUより遅いのでコネクションが足りなくなる
• NIOなのでスレッドプールのスレッド数=コネクション数にしても解決しない
リソース間調整
import akka.io.IO
import spray.can.Http
import spray.client.pipelining._
trait RequestProvider { this: Actor =>
import context.system
import context.dispatcher
lazy val pipeline = {
sendReceive(IO(Http)(system)) ~> unmarshal[String]
}
def request(path: String): Future[String] = pipeline(Get(s"$requestUrl/$path"))
}
スレッドプールとコネクションプール
val safeRequest: Flow[String, String, NotUsed] = Flow[String].mapAsync(maxConnection)(request)
Akka StreamのmapAsync(parallelism)(asyncFunction)を使えば
parallelism以上にasyncFunctionが呼ばれることはない
Akka HTTPでもコネクションプールをもつクライアントは
mapAsyncで制御している
Spray clientの例
14. TCPコネクションにHTTPのセマンティクスをのせる
val transportFlow: Flow[ByteString, ByteString, Future[Tcp.OutgoingConnection]] = Tcp().outgoingConnection(new
InetSocketAddress(host, port)) //TCPコネクションのステージ
val tlsStage: BidiFlow[SslTlsOutbound, ByteString, ByteString, SessionBytes, NotUsed] = TLSPlacebo()
//TLSのプラシーボ効果ステージ。ByteStringをTLSの型にラップしているだけで何もしていない。HTTPスキーム用。
val outgoingTlsConnectionLayer: Flow[SslTlsOutbound, SessionBytes, Future[Http.OutgoingConnection]] =
tlsStage.joinMat(transportFlow) { (_, tcpConnFuture: Future[Tcp.OutgoingConnection]) =>
tcpConnFuture map { tcpConn => Http.OutgoingConnection(tcpConn.localAddress, tcpConn.remoteAddress) }
//TCPコネクションステージにTLSステージを接続する。Materialized ValueをTcp.OutgoingConnectionから
Http.OutgoingConnectionに変換する。
}
val clientLayer: BidiFlow[HttpRequest, SslTlsOutbound, SslTlsInbound, HttpResponse, NotUsed] = Http().clientLayer(Host(host,
port))
//HttpRequest -> SslTlsOutbound、SslTlsInbound -> HttpResponseへの変換ステージ
val outgoingConnection: Flow[HttpRequest, HttpResponse, Future[Http.OutgoingConnection]] =
clientLayer.joinMat(outgoingTlsConnectionLayer)(Keep.right) //TCP/TLSステージとHTTPステージを接続
クライアント編
15. val transportFlow: Flow[ByteString, ByteString, Future[Tcp.OutgoingConnection]] = Tcp().outgoingConnection(new
InetSocketAddress(host, port)) //TCPコネクションのステージ
val tlsStage: BidiFlow[SslTlsOutbound, ByteString, ByteString, SessionBytes, NotUsed] = TLSPlacebo()
//TLSのプラシーボ効果ステージ。ByteStringをTLSの型にラップしているだけで何もしていない。HTTPスキーム用。
/**
* +------------------------------------------------+
* | outgoingTlsConnectionLayer |
* | |
* | +----------+ +---------------+ |
* SslTlsOutbound ~~> | ~ByteString~> | | |
* | | tlsStage | | transportFlow | |
* SessionBytes <~~ | <~ByteString~ | | |
* | +----------+ +---------------+ |
* +------------------------------------------------+
*/
val outgoingTlsConnectionLayer: Flow[SslTlsOutbound, SessionBytes, Future[Http.OutgoingConnection]] =
tlsStage.joinMat(transportFlow) { (_, tcpConnFuture: Future[Tcp.OutgoingConnection]) =>
tcpConnFuture map { tcpConn => Http.OutgoingConnection(tcpConn.localAddress, tcpConn.remoteAddress) }
//TCPコネクションステージにTLSステージを接続する。Materialized ValueをTcp.OutgoingConnectionから
Http.OutgoingConnectionに変換する。
}
TCPコネクションにHTTPのセマンティクスをのせる
① TCPコネクションにTLSの解釈を接続する
16. val clientLayer: BidiFlow[HttpRequest, SslTlsOutbound, SslTlsInbound, HttpResponse, NotUsed] =
Http().clientLayer(Host(host, port))
//HttpRequest -> SslTlsOutbound、SslTlsInbound -> HttpResponseへの変換ステージ
/**
* +-------------------------------------------------------------------+
* | outgoingConnection |
* | |
* | +-------------+ +----------------------------+ |
* HttpRequest ~~> | ~SslTlsOutbound~> | | |
* | | clientLayer | | outgoingTlsConnectionLayer | |
* HttpResponse <~~ | <~SslTlsInbound~ | | |
* | +-------------+ +----------------------------+ |
* +-------------------------------------------------------------------+
*/
val outgoingConnection: Flow[HttpRequest, HttpResponse, Future[Http.OutgoingConnection]] =
clientLayer.joinMat(outgoingTlsConnectionLayer)(Keep.right) //TCP/TLSステージとHTTPステージを接続
TCPコネクションにHTTPのセマンティクスをのせる
② HTTPのセマンティクスを解釈する
17. val outgoingConnection: Flow[HttpRequest, HttpResponse, Future[Http.OutgoingConnection]] =
clientLayer.joinMat(outgoingTlsConnectionLayer)(Keep.right)
//TCP/TLSステージとHTTPステージを接続
Source.single(httpRequest).via(outgoingConnection).toMat(Sink.head)(Keep.right).run()
Source.single(httpRequest).via(Http().outgoingConnection(host, port)).toMat(Sink.head)(Keep.right).run()
これら一連の処理は
Http().outgoingConnection(host, port)
と等価です
TCPコネクションにHTTPのセマンティクスをのせる
動かしてみる
18. TCPコネクションにHTTPのセマンティクスをのせる
val connections: Source[Tcp.IncomingConnection, Future[Tcp.ServerBinding]] = Tcp().bind(host, port) //TCPのbind
val tlsStage: BidiFlow[SslTlsOutbound, ByteString, ByteString, SessionBytes, NotUsed] = TLSPlacebo()
//TLSのプラシーボ効果ステージ。ByteStringをTLSの型にラップしているだけで何もしていない。HTTPスキーム用。
val serverLayer: BidiFlow[HttpResponse, SslTlsOutbound, SslTlsInbound, HttpRequest, NotUsed] = Http().serverLayer()
//HttpResponse -> SslTlsOutbound、SslTlsInbound -> HttpRequestへの変換ステージ
val serverSource: Source[IncomingConnection, Future[ServerBinding]] = connections.map {
case Tcp.IncomingConnection(localAddress, remoteAddress, flow) =>
/**
* +----------------------------------------------------------------------------+
* | IncomingConnection.flow |
* | |
* | +-------------+ +----------+ +----------+ |
* HttpResponse ~~> | ~SslTlsOutbound~> | | ~ByteString~> | | |
* | | serverLayer | | tlsStage | | identity | |
* HttpRequest <~~ | <~SslTlsInbound~ | | <~ByteString~ | | |
* | +-------------+ +----------+ +----------+ |
* +----------------------------------------------------------------------------+
*/
// TCP/TLSステージとHTTPステージを接続して、Tcp.IncomingConnectionからHttp.IncomingConnectionに変換
Http.IncomingConnection(localAddress, remoteAddress,
serverLayer atop tlsStage join Flow[ByteString].map(identity)
)
}.mapMaterializedValue { bindFuture: Future[Tcp.ServerBinding] =>
bindFuture.map(tcpBinding => Http.ServerBinding(tcpBinding.localAddress)(unbindAction = () => tcpBinding.unbind()))
// Tcp.ServerBindingからHttp.ServerBindingに変換
}
サーバー編
19. val bindingFuture: Future[ServerBinding] = serverSource.to(Sink.foreach { connection =>
val connFlow: Flow[HttpResponse, HttpRequest, NotUsed] = connection.flow
val requestHandler: Flow[HttpRequest, HttpResponse, NotUsed] = Flow[HttpRequest].map {
case HttpRequest(GET, Uri.Path("/"), _, _, _) =>
HttpResponse(entity = HttpEntity(ContentTypes.`text/html(UTF-8)`, "<html><body>Hello world!</body></html>"))
}
connFlow.joinMat(requestHandler)(Keep.right).run()
}).run()
val serverSource: Source[Http.IncomingConnection, Future[Http.ServerBinding]] = Http().bind(host, port)
これら一連の処理は
Http().bind(host, port)
と等価です
TCPコネクションにHTTPのセマンティクスをのせる
動かしてみる
22. akka.io.Tcpの復習
case object Ack extends Tcp.Event
class PullModeClient(remote: InetSocketAddress) extends Actor with ActorLogging {
import context.system
override def preStart: Unit = {
val manager: ActorRef = IO(Tcp) //IOエクステンションからTCPマネージャーを取得
manager ! Tcp.Connect(remote, pullMode = true) //TCPマネージャーに接続要求を送る
}
def receive: Receive = connecting
def connecting: Receive = {
case Tcp.Connected(remote, local) => { //TCP接続の完了
val connection = sender() //senderがコネクションWorkerアクター
context.become(connected(connection))
connection ! Tcp.Register(self) //自分自身をコネクションWorkerアクターに登録
connection ! Tcp.ResumeReading //サーバーからの受信を再開する
}
}
def connected(connection: ActorRef): Receive = {
case Tcp.Received(data) => log.info("received {}", data) //サーバーからデータを受信
case data: ByteString => connection ! Tcp.Write(data, Ack) //ユーザーからデータの送信要求がきたら書き込む。成功したらAckを返
してもらう。Ackが返るまで受け付けてはいけません!!
case Ack => connection ! Tcp.ResumeReading //データの送信が完了したら受信を再開する
}
}
Pull Mode を使う読み込み側の背圧制御のために
23. akka.io.Tcpの復習
case object Ack extends Tcp.Event
class PullModeClient(remote: InetSocketAddress) extends Actor with ActorLogging {
import context.system
override def preStart: Unit = {
val manager: ActorRef = IO(Tcp) //IOエクステンションからTCPマネージャーを取得
manager ! Tcp.Connect(remote, pullMode = true) //TCPマネージャーに接続要求を送る
}
def receive: Receive = connecting
def connecting: Receive = {
case Tcp.Connected(remote, local) => { //TCP接続の完了
val connection = sender() //senderがコネクションWorkerアクター
context.become(connected(connection))
connection ! Tcp.Register(self) //自分自身をコネクションWorkerアクターに登録
connection ! Tcp.ResumeReading //サーバーからの受信を再開する
}
}
def connected(connection: ActorRef): Receive = {
case Tcp.Received(data) => log.info("received {}", data) //サーバーからデータを受信
case data: ByteString => connection ! Tcp.Write(data, Ack) //ユーザーからデータの送信要求がきたら書き込む。成功したらAckを返
してもらう。Ackが返るまで受け付けてはいけません!!
case Ack => connection ! Tcp.ResumeReading //データの送信が完了したら受信を再開する
}
}
Pull Mode を使う読み込み側の背圧制御のために
_人人人人人人人人人人人人人人人人_
> 書き込み側にも背圧制御欲しい <
 ̄Y^Y^Y^Y^Y^Y^Y^Y^Y^Y^Y^Y^Y^Y^Y ̄
そこでAkka Streamだ
24. Akka Streamのステージ
final case class Map[In, Out](f: In ⇒ Out) extends GraphStage[FlowShape[In, Out]] {
val in = Inlet[In]("Map.in") //入力ポート
val out = Outlet[Out]("Map.out") //出力ポート
override def shape: FlowShape[In, Out] = FlowShape.of(in, out)
override def createLogic(inheritedAttributes: Attributes): GraphStageLogic =
new GraphStageLogic(shape) {
setHandler(in, new InHandler { //入力ポート`in`のイベントハンドラ
override def onPush(): Unit = { //PUSHされた時のフック
val v = f(grab(in)) //pushされた要素を取得してfを適用
push(out, v) //`out`にpush
}
})
setHandler(out, new OutHandler { //出力ポート`out`のイベントハンドラ
override def onPull(): Unit = { //PULLされた時のフック
pull(in) //`in`からpull
}
})
}
}
//使い方
def mapFlow[In, Out](f: In => Out): Flow[In, Out, NotUsed] = Flow.fromGraph(Map(f))
ストリームのステージの実装例としてMapを見てみよう
Source.map()やFlow.map()はこのように実装されている
25. class TcpStreamLogic(val shape: FlowShape[ByteString, ByteString], val role: TcpRole) extends GraphStageLogic(shape) {
implicit def self: ActorRef = stageActor.ref
private def bytesIn = shape.in //読み込み用のポート
private def bytesOut = shape.out //書き込み用のポート
private var connection: ActorRef = _ //TCPコネクションのworkerアクター
override def preStart(): Unit = {
role match {
case ob @ Outbound(manager, cmd: akka.io.Tcp.Connect, _, _) ⇒
getStageActor(connecting(ob)) //このステージのアクターのreceiveをconnectingで初期化
manager ! cmd // managerはIO(Tcp)と同じ。Tcp.Connectコマンドを送る。
}
}
private def connecting(ob: Outbound)(evt: (ActorRef, Any)): Unit = {
val (sender, msg) = evt
msg match {
case c: akka.io.Tcp.Connected =>
role.asInstanceOf[Outbound].localAddressPromise.success(c.localAddress) //Materialized ValueのPromiseにlocalAddressを書き込む
connection = sender // senderがTCPコネクションのworkerアクター
setHandler(bytesOut, readHandler) //bytesOutのイベントハンドラをreadHandlerに設定
stageActor.become(connected) //このステージのアクターのreceiveをconnectedにする(context.become(connected)と同じ)
connection ! akka.io.Tcp.Register(self)
if (isAvailable(bytesOut)) connection ! ResumeReading
pull(bytesIn) //bytesInからpullして要素を要求
}
}
private def connected(evt: (ActorRef, Any)): Unit = {
val (sender, msg) = evt
msg match {
case akka.io.Tcp.Received(data) => push(bytesOut, data) //データを受信したらbytesOutにpushする
case WriteAck => pull(bytesIn) //書き込み成功のAckが返ってきたらbytesInにpullして要素を要求
}
}
setHandler(bytesOut, new OutHandler {
override def onPull(): Unit = ()
})
val readHandler = new OutHandler {
override def onPull(): Unit = { //pullされたときに呼ばれるイベントハンドラー
connection ! ResumeReading
}
}
setHandler(bytesIn, new InHandler {
override def onPush(): Unit = { //pushされたときに呼ばれるイベントハンドラー
val elem = grab(bytesIn) //bytesInにpushされた要素を取得
connection ! Write(elem.asInstanceOf[ByteString], WriteAck)
}
})
}
Tcp().outgoingConnectionステージの実装
https://github.com/akka/akka/blob/v2.4.2/akka-
stream/src/main/scala/akka/stream/impl/io/TcpStages.scala#L171-L287
長いけどakka.io.Tcpの復習でみた
コードと比較すると
理解しやすい
26. class TcpStreamLogic(val shape: FlowShape[ByteString, ByteString], val role: TcpRole) extends
GraphStageLogic(shape) {
implicit def self: ActorRef = stageActor.ref
private def bytesIn = shape.in //読み込み用のポート
private def bytesOut = shape.out //書き込み用のポート
private var connection: ActorRef = _ //TCPコネクションのworkerアクター
override def preStart(): Unit = {
role match {
case ob @ Outbound(manager, cmd: akka.io.Tcp.Connect, _, _) ⇒
getStageActor(connecting(ob)) //このステージのアクターのreceiveをconnectingで初期化
manager ! cmd // managerはIO(Tcp)と同じ。Tcp.Connectコマンドを送る。
}
}
}
class PullModeClient(remote: InetSocketAddress) extends Actor with ActorLogging {
import context.system
override def preStart: Unit = {
val manager: ActorRef = IO(Tcp) //IOエクステンションからTCPマネージャーを取得
manager ! Tcp.Connect(remote, pullMode = true) //TCPマネージャーに接続要求を送る
}
def receive: Receive = connecting
}
akka.io.Tcpの対応する部分
Tcp().outgoingConnectionステージの実装
27. private def connecting(ob: Outbound)(evt: (ActorRef, Any)): Unit = {
val (sender, msg) = evt
msg match {
case c: akka.io.Tcp.Connected =>
role.asInstanceOf[Outbound].localAddressPromise.success(c.localAddress) //Materialized ValueのPromise
にlocalAddressを書き込む
connection = sender // senderがTCPコネクションのworkerアクター
setHandler(bytesOut, readHandler) //bytesOutのイベントハンドラをreadHandlerに設定
stageActor.become(connected) //このステージのアクターのreceiveをconnectedにする(
context.become(connected)と同じ)
connection ! akka.io.Tcp.Register(self)
if (isAvailable(bytesOut)) connection ! ResumeReading
pull(bytesIn) //bytesInからpullして要素を要求
}
}
}
def connecting: Receive = {
case Tcp.Connected(remote, local) => { //TCP接続の完了
val connection = sender() //senderがコネクションWorkerアクター
context.become(connected(connection))
connection ! Tcp.Register(self) //自分自身をコネクションWorkerアクターに登録
connection ! Tcp.ResumeReading //サーバーからの受信を再開する
}
}
akka.io.Tcpの対応する部分
Tcp().outgoingConnectionステージの実装
28. private def connected(evt: (ActorRef, Any)): Unit = {
val (sender, msg) = evt
msg match {
case akka.io.Tcp.Received(data) => push(bytesOut, data) //R② データを受信したらbytesOutにpushする
case WriteAck => pull(bytesIn) //W② 書き込み成功のAckが返ってきたらbytesInにpullして要素を要求
}
}
val readHandler = new OutHandler {
override def onPull(): Unit = { //bytesOutでpullされたときに呼ばれるイベントハンドラー
connection ! ResumeReading //R① bytesOutにpull要求がきたら読み込みを再開する
}
}
setHandler(bytesIn, new InHandler {
override def onPush(): Unit = { //bytesInでpushされたときに呼ばれるイベントハンドラー
val elem = grab(bytesIn) //bytesInにpushされた要素を取得
connection ! Write(elem.asInstanceOf[ByteString], WriteAck) //W① pushされたデータを書き込む
}
})
def connected(connection: ActorRef): Receive = {
case Tcp.Received(data) => log.info("received {}", data) //サーバーからデータを受信
case data: ByteString => connection ! Tcp.Write(data, Ack) //ユーザーからデータの送信要求がきたら
case Ack => connection ! Tcp.ResumeReading //データの送信が完了したら受信を再開する
}
akka.io.Tcpの対応する部分
Tcp().outgoingConnectionステージの実装
29. private def connected(evt: (ActorRef, Any)): Unit = {
val (sender, msg) = evt
msg match {
case akka.io.Tcp.Received(data) => push(bytesOut, data) //R② データを受信したらbytesOutにpushする
case WriteAck => pull(bytesIn) //W② 書き込み成功のAckが返ってきたらbytesInにpullして要素を要求
}
}
val readHandler = new OutHandler {
override def onPull(): Unit = { //bytesOutでpullされたときに呼ばれるイベントハンドラー
connection ! ResumeReading //R① bytesOutにpull要求がきたら読み込みを再開する
}
}
setHandler(bytesIn, new InHandler {
override def onPush(): Unit = { //bytesInでpushされたときに呼ばれるイベントハンドラー
val elem = grab(bytesIn) //bytesInにpushされた要素を取得
connection ! Write(elem.asInstanceOf[ByteString], WriteAck) //W① pushされたデータを書き込む
}
})
def connected(connection: ActorRef): Receive = {
case Tcp.Received(data) => log.info("received {}", data) //サーバーからデータを受信
case data: ByteString => connection ! Tcp.Write(data, Ack) //ユーザーからデータの送信要求がきたら
case Ack => connection ! Tcp.ResumeReading //データの送信が完了したら受信を再開する
}
akka.io.Tcpの対応する部分
欲しいと言うまでデータは来ない
ようになった
Tcp().outgoingConnectionステージの実装
31. class TcpStreamLogic(val shape: FlowShape[ByteString, ByteString], val role: TcpRole) extends GraphStageLogic(shape) {
implicit def self: ActorRef = stageActor.ref
private def bytesIn = shape.in //読み込み用のポート
private def bytesOut = shape.out //書き込み用のポート
private var connection: ActorRef = _ //TCPコネクションのworkerアクター
setHandler(bytesOut, new OutHandler {
override def onPull(): Unit = ()
})
override def preStart(): Unit = {
role match {
case Inbound(conn, _) =>
setHandler(bytesOut, readHandler) //bytesOutのイベントハンドラをreadHandlerに設定
connection = conn
getStageActor(connected) //このステージのアクターのreceiveをconnectedにする(context.become(connected)と同じ)
connection ! Register(self)
pull(bytesIn) //bytesInからpullして要素を要求
}
}
private def connected(evt: (ActorRef, Any)): Unit = {
val (sender, msg) = evt
msg match {
case akka.io.Tcp.Received(data) => push(bytesOut, data) //R② データを受信したらbytesOutにpushする
case WriteAck => pull(bytesIn) //W② 書き込み成功のAckが返ってきたらbytesInにpullして要素を要求
}
}
val readHandler = new OutHandler {
override def onPull(): Unit = { //bytesOutでpullされたときに呼ばれるイベントハンドラー
connection ! ResumeReading //R① bytesOutにpull要求がきたら読み込みを再開する
}
}
setHandler(bytesIn, new InHandler {
override def onPush(): Unit = { //bytesInでpushされたときに呼ばれるイベントハンドラー
val elem = grab(bytesIn) //bytesInにpushされた要素を取得
connection ! Write(elem.asInstanceOf[ByteString], WriteAck) //W① pushされたデータを書き込む
}
})
}
Tcp.IncomingConnection#flowステージの実装
開始の仕方が違うだけで
実装は同じ
https://github.com/akka/akka/blob/v2.4.2/akka-
stream/src/main/scala/akka/stream/impl/io/TcpStages.scala#L171-L287