サーバーで何が起きているのか?

Estimated reading time: 2 minutes

Ktorは柔軟で拡張性があるように設計されています。 そのため小さく、シンプルな部品で構成されていますが、もしあなたが何が起きているのか理解していないならばブラックボックスのように見えるでしょう。

このセクションでは、Ktorが内部で何を行っているかを探索し、その汎用的なインフラストラクチャとしての性質について学んでいきましょう。

目次:

エントリーポイント

Ktorアプリケーションをいくつかの方法で起動することができます:

起動

共通

ApplicationEngineEnvironment:

起動するためにはイミュータブルな環境を構築する必要があります。 クラスローダー、ロガー、設定ファイル、 アプリケーションイベントのイベントバスとして起動するmonitor、 コネクター群、モジュール群を設定し、それらがアプリケーションやwatchPathを形成します。

ApplicationEngineEnvironmentBuilderや、お手軽なDSL関数であるapplicationEngineEnvironment, commandLineEnvironmentを使うことで構築ができます。

ApplicationEngine:

複数種類のApplicationEngineがあります。 Netty, Jetty, CIO or Tomcatといったサーバーをサポートしています。

ApplicationEngineはアプリケーションを起動する役割をもったクラスであり、 特定のconfigurationと、起動・停止可能なenvironmentを持っています。

特定のアプリケーションエンジンを起動したとき、 configurationが使われ、 正しいport、hostをSSLや証明書を使い、指定したworkerでリッスンすることができます。

コネクターは特定のhttp/httpsホスト・ポートをリッスンするために使われており、 一方Applicationパイプラインはリクエストをハンドルするために使われています。

Applicationパイプライン:

ApplicationはApplicationEngineEnvironmentによって作成され、初めは空の状態です。 コンテキストとしてのApplicationCallを持たないパイプラインになります。 environmentの作成時にアプリケーションの設定を行うため各指定されたmoduleが呼び出されます。

embeddedServer

main関数を起動しembeddedServer関数を呼び出すとき、 特定のApplicationEngineFactoryを渡すことでApplicationEngineEnvironmentが作成または渡されます。

EngineMain

Ktorは各サポートしているサーバーエンジンごとにEngineMainクラスを定義しています。 このクラスはアプリケーションを起動することができるmain関数を定義しています。 また、commandLineEnvironmentを使うことで、 HOCON application.confファイルをresourceから読み込み、 どのmoduleをインストールするかやサーバーをどう設定するかについて追加の引数を使って指定することができます。

CommandLine.ktソースファイル内に通常これらのクラスは宣言されています。

TestApplicationEngine

テストのため、KtorはTestApplicationEngineと、withTestApplicationという便利なメソッドを定義しています。 それを使うことでアプリケーションmoduleやパイプラインやその他の機能を、サーバーを実際に起動したり何かしらをモックしたりすることなしに、テストすることができます。 そこではインメモリの設定としてMapApplicationConfig("ktor.deployment.environment" to "test")を使用し、 テスト環境内で実行することを決めています。

イベントの監視

環境に紐づく形で、monitorインスタンスがあり、Ktorはそれをアプリケーションイベントを生成するために使っています。 monitorインスタンスをイベントをサブスクライブするために使うことができます。 例えば、アプリケーション停止イベントをサブスクライブすることで、特定サービスのシャットダウンや何らかのリソースの終了などを実行することができます。

Ktorが定義するイベント一覧:

val ApplicationStarting = EventDefinition<Application>()
val ApplicationStarted = EventDefinition<Application>()
val ApplicationStopPreparing = EventDefinition<ApplicationEnvironment>()
val ApplicationStopping = EventDefinition<Application>()
val ApplicationStopped = EventDefinition<Application>()

Pipeline

Ktorは非同期で拡張可能な処理を行うためのパイプラインを定義します。 パイプラインはKtor全体で利用されています。

すべてのパイプラインはsubjectの型、contextの型、インターセプタが関連付けられているphaseのリストを持ちます。 また、attributeというオブジェクトコンテナとして動作する型も持ちます。

フェーズは順序付けられており、どのPhaseよりも先なのか後なのかあるいは最後なのかといった実行順序について定義されています。

各パイプラインは順序付けられたPhaseのリストを持っており、さらに各Phaseごとにinterceptorのセットを持っています。

例:

  • Pipeline
    • Phase1
      • Interceptor1
      • Interceptor2
    • Phase2
      • Interceptor3
      • Interceptor4

あるPhaseのためのインターセプタは、同じPhaseの他のインターセプタには依存していませんが、前のPhaseのインターセプタには依存しています。

パイプラインの実行時、すべての登録されているインターセプタはPhaseで定義されている順序で実行されます。

ApplicationCallPipeline

Ktorのサーバー部分はApplicationCallPipelineを定義しており、ApplicationCallをコンテキストとして持っています。 ApplicationインスタンスはApplicationCallPipelineインスタンスでもあります。

サーバーのアプリケーションエンジンがHTTPリクエストをハンドルするとき、Applicationパイプラインが実行されます

コンテキストのクラスであるApplicationCallはApplicationやrequestresponseattributesparametersなどを保持しています。

最終的にアプリケーションmoduleは、アプリケーションパイプラインの特定のフェーズにインターセプタの登録するのを終了し、 requestの処理とresponseの送信を行います。

ApplicationCallPipelineは以下のビルトインPhaseをパイプラインに定義しています:

val Setup = PipelinePhase("Setup") // Phase for preparing the call, and processing attributes
val Monitoring = PipelinePhase("Monitoring") // Phase for tracing calls: logging, metrics, error handling etc. 
val Features = PipelinePhase("Features") // Phase for infrastructure features, most intercept at this phase
val Call = PipelinePhase("Call") // Phase for processing a call and sending a response
val Fallback = PipelinePhase("Fallback") // Phase for handling unprocessed calls

Feature

KtorはApplicationFeature クラスでアプリケーションの機能を定義しています。 Featureは指定のパイプラインにinstall可能なものです。 Featureはパイプラインにアクセスし、interceptorに登録され、様々な種類のことを実行します。

Routing

Featureやパイプラインツリーがどのようにともに動作しているのかを説明するには、Routingがどのように動くのかを見るといいでしょう。

Routingは他の機能同様、通常以下のようにインストールされます:

install(Routing) { }

登録・起動するための簡単なメソッドも用意されています。すでに登録されていない場合のみインストールされるようになっています。

routing { }

Routingはツリーとして定義されます。 各ノードはRouteであり、ApplicationCallPipelineの分離されたインスタンスでもあります。 そのため、ルートのroutingノードが実行されたとき、それ自体のパイプラインが実行されます。 そしてパイプラインの実行が終わったときには、routeが実行され終わっています。

関連記事