Finagle についてChannel ~ Service部分のデータの流れが気になったので調べた
Finagleについては既知かもしれないがNettyのラッパーとして作られているサーバフレームワークだ、一般的にはWebサーバフレームワークとして使われている
com.twitter.finagle.Http.serve
をエントリポイントとしてServerの立ち上げ(のNettyServer構成部分)、そしてリクエストの受け入れ〜レスポンスのデータの流れを追っていこう
com.twitter.finagle.Http.serve
名前の通りサーバアプリケーションを立ち上げるメソッドだ
overrideで様々な定義がされているが今回は QuickStart 同様 com.twitter.finagle.Server[Req, Rep]#serve(addr: String, service: Service[Req, Rep])
で起動したとして進めていこう
型パラメタ [Req, Rep]
はfinagle Httpサーバの場合は [Request, Response]
型となる
第二引数 Service
はユーザ側で定義するリクエストのハンドリング処理となる、今回話がしたい部分ではないので割愛する
(今回の話の上ではただの Request => Future[Response]
型の関数として認識してもらっても問題ない、たぶん)
serve
引数値を加工したのち、 StdStackServer
インスタンスの作成 & StdStackServer#serve
メソッドの呼び出しを行う
StdStackServer#serve
ではHttp2の判定を行ったあと
ListeningStackServer#serve
を呼び出す
(Stack.Params
については正直良くわかっていないので割愛する)
com.twitter.finagle.server.ListeningStackServer#serve
ListeningServer
インスタンスを作成している
コンストラクタに様々な処理が含まれているが、今回の話で着目してもしいのが newListeningServer
の呼び出しだ、この中で StdStackServer#newListener().listen
が呼び出されている
com.twitter.finagle.server.StdStackServer#newListener
実装は params[HttpImpl].listener(params)
の一行だ
params[HttpImpl]
からは com.twitter.finagle.Http.Netty4Impl
が取得され(おそらくこれはデフォルト値だ) listener
を呼び出すことで com.twitter.finagle.netty4.Netty4Listener
のインスタンスが取得される
ちなみに Netty4Listener
の第三引数はidentity、第四引数は ChannelTransport
が使用される
com.twitter.finagle.netty4.Netty4Listener.listen
ListeningServerBuilder#bindWithBridge
を呼び出している netty ServerBootstrap
の組み上げとNetty<->Finagle Service間の橋渡し部分の構築を行う
com.twitter.finagle.netty4.ListeningServerBuilder#bindWithBridge
ここがNettyとの境界を理解するためにおそらく一番重要な箇所だ
BossGroupとWorkGroupにはLinux専用の機能を使うかのフラグを見た後 NioEventLoopGroup
か EpollEventLoopGroup
が使用される(後者がLinux専用のものだ)
BossGroupのほうはThread数が1で固定されているWorkGroupのほうはデフォルトでは8以上だ(多くない?)
他様々なoptionが設定されたあと、Handlerの登録に入る
順番はコメントにも書かれている通り raw => marshalling => framed => bridge
の順で適用される(コメントにはないが実際には pipelineInit
も適用される)
あっているか自信はないがひとつずつ見ていこう
pipelineInit
長い…
com.twitter.finagle.netty4.http.ServerPipelineInit
より HttpServerCodec
と initServer
メソッド内の各ハンドラが追加される
netty側で用意されているhttp系のハンドラなので割愛
Netty4RawServerChannelInitializer(raw)
- Netty4ServerSslChannelInitializer
コメントに書かれている通りSSL/TLS用のハンドラ群だ、中身については追わない - ChannelStatsHandler
複数のチャネルにまたがって計測する値などを管理するハンドラだと思う - ChannelSnooper
多分logger的なやつ…
marshalling
今回の例ではidentityが渡ってきているので、このイニシャライザではとくに何もハンドラを追加しない
Netty4FramedServerChannelInitializer(framed)
- writeTimeout/readTimeout
タイムアウトのハンドラ - ChannelRequestStatsHandler
requestCountするハンドラらしい
ServerBridge(bridge)
Nettyの世界からFinagleの世界への橋渡しを行うレイヤ、今回の話のキモ的な部分
ServerBridge
の実装自体は簡単で自信をpipelineから排除したのち、transporterの作成+コールバックの実行のみを行っている
つまり本格的な受けたしの実装はTransporter及びコールバックのほうとなる、詳しく見ていこう
(コールバックはlithen呼び出し側の この部分 だ)
Transporter/Dispatcher
Transporterコールバック関数にてStreamTransporter/Dispatcherでラップされる
それを踏まえた上でChannel ~ Dispatcherまでのデータの流れを追っていこう
ChannelTransport
コンストラクタ内で ChannelInboundHandlerAdapter
を生成している
その中の channelRead
関数で自身が持っている(あとで説明するが Dispatcher
からreadされる)QueueにMsgを詰めている、NettyのChannelの概念は以降登場しない、これ以降はFinagleの世界だ
GenStreamingSerialServerDispatcher
内部的に処理loopが回っており、そこで Transporter
のQueueからデータを読み出すread関数を読み出している
気をつけたいのが呼び出されているのは ChannelTransport#read
ではなく Netty4ServerStreamTransport#read
だ(結局 Netty4ServerStreamTransport#read
から ChannelTransport#read
が呼び出されているのだが、気をつけてほしいのはひとつラッパーが噛んでいるということだ)
Netty4ServerStreamTransport#read
自身が持っている ChannelTransport#read
を呼び出すことでQueueからメッセージを取り出しNettyのRequest objectからFinagleのRequest objectへの変換を行っている
QueueはAsyncQueueのため、戻り値はFutureになっている、どちらも twitter.util
にあるclassだ
HttpServerDispatcher#dispatch
自身のsuper classである GenStreamingSerialServerDispatcher#loop
-> GenStreamingSerialServerDispatcher#dispatchAndHandleFn
を経由して呼び出されている
dispatch
関数ではユーザが用意したサービスにRequestオブジェクトが渡されるようになっている(つまり、read側の終点となっている)
HttpServerDispatcher#handle
dispatch
と同じく GenStreamingSerialServerDispatcher#dispatchAndHandleFn
から呼び出されている
ここからはレスポンスをチャネルに書き込むためのデータの流れになる
渡ってきたResponse型を Netty4ServerStreamTransport#write
に渡している
Netty4ServerStreamTransport#write
read同様FinagleのResponseをNettyのResponse objectに変換して ChannelTransport#write
に書きこんでいる
ChannelTransport#write
保持しているチャネルに対してメッセージを書き込む
書き込んだあとはNettyの仕組みに従ってユーザへレスポンスが配送される
データの流れとしては以上だ、validation処理やclose処理などいろいろハブった部分があったり本当のソースコードはもっと抽象化されていたりと今回書いた流れほど簡潔ではないが
誰かのソースコードリーディングの手助けになればと思います