Ktorは柔軟で拡張性があるように設計されています。 そのため小さく、シンプルな部品で構成されていますが、もしあなたが何が起きているのか理解していないならばブラックボックスのように見えるでしょう。
このセクションでは、Ktorが内部で何を行っているかを探索し、その汎用的なインフラストラクチャとしての性質について学んでいきましょう。
目次:
Ktorアプリケーションをいくつかの方法で起動することができます:
embeddedServer
を呼び出すmain
関数EngineMain
main
関数を起動し、HOCON application.conf
設定ファイルを利用ktor-server-test-host
アーティファクトからwithTestApplication
を使いテストの一部として起動起動するためにはイミュータブルな環境を構築する必要があります。 クラスローダー、ロガー、設定ファイル、 アプリケーションイベントのイベントバスとして起動するmonitor、 コネクター群、モジュール群を設定し、それらがアプリケーションやwatchPathを形成します。
ApplicationEngineEnvironmentBuilder
や、お手軽なDSL関数であるapplicationEngineEnvironment
, commandLineEnvironment
を使うことで構築ができます。
複数種類のApplicationEngine
があります。
Netty, Jetty, CIO or Tomcatといったサーバーをサポートしています。
ApplicationEngineはアプリケーションを起動する役割をもったクラスであり、 特定のconfigurationと、起動・停止可能なenvironmentを持っています。
特定のアプリケーションエンジンを起動したとき、 configurationが使われ、 正しいport、hostをSSLや証明書を使い、指定したworkerでリッスンすることができます。
コネクターは特定のhttp/httpsホスト・ポートをリッスンするために使われており、
一方Application
パイプラインはリクエストをハンドルするために使われています。
Applicationパイプライン:
ApplicationはApplicationEngineEnvironment
によって作成され、初めは空の状態です。
コンテキストとしてのApplicationCall
を持たないパイプラインになります。
environmentの作成時にアプリケーションの設定を行うため各指定されたmoduleが呼び出されます。
main関数を起動しembeddedServer
関数を呼び出すとき、
特定のApplicationEngineFactory
を渡すことでApplicationEngineEnvironment
が作成または渡されます。
Ktorは各サポートしているサーバーエンジンごとにEngineMain
クラスを定義しています。
このクラスはアプリケーションを起動することができるmain
関数を定義しています。
また、commandLineEnvironment
を使うことで、
HOCON application.conf
ファイルをresourceから読み込み、
どのmoduleをインストールするかやサーバーをどう設定するかについて追加の引数を使って指定することができます。
CommandLine.kt
ソースファイル内に通常これらのクラスは宣言されています。
io.ktor.server.cio.EngineMain.main
io.ktor.server.jetty.EngineMain.main
io.ktor.server.netty.EngineMain.main
io.ktor.server.tomcat.EngineMain.main
テストのため、KtorはTestApplicationEngine
と、withTestApplication
という便利なメソッドを定義しています。
それを使うことでアプリケーションmoduleやパイプラインやその他の機能を、サーバーを実際に起動したり何かしらをモックしたりすることなしに、テストすることができます。
そこではインメモリの設定としてMapApplicationConfig("ktor.deployment.environment" to "test")
を使用し、
テスト環境内で実行することを決めています。
環境に紐づく形で、monitorインスタンスがあり、Ktorはそれをアプリケーションイベントを生成するために使っています。 monitorインスタンスをイベントをサブスクライブするために使うことができます。 例えば、アプリケーション停止イベントをサブスクライブすることで、特定サービスのシャットダウンや何らかのリソースの終了などを実行することができます。
val ApplicationStarting = EventDefinition<Application>()
val ApplicationStarted = EventDefinition<Application>()
val ApplicationStopPreparing = EventDefinition<ApplicationEnvironment>()
val ApplicationStopping = EventDefinition<Application>()
val ApplicationStopped = EventDefinition<Application>()
Ktorは非同期で拡張可能な処理を行うためのパイプラインを定義します。 パイプラインはKtor全体で利用されています。
すべてのパイプラインはsubjectの型、contextの型、インターセプタが関連付けられているphaseのリストを持ちます。 また、attributeというオブジェクトコンテナとして動作する型も持ちます。
フェーズは順序付けられており、どのPhaseよりも先なのか後なのかあるいは最後なのかといった実行順序について定義されています。
各パイプラインは順序付けられたPhaseのリストを持っており、さらに各Phaseごとにinterceptorのセットを持っています。
例:
あるPhaseのためのインターセプタは、同じPhaseの他のインターセプタには依存していませんが、前のPhaseのインターセプタには依存しています。
パイプラインの実行時、すべての登録されているインターセプタはPhaseで定義されている順序で実行されます。
Ktorのサーバー部分はApplicationCallPipeline
を定義しており、ApplicationCall
をコンテキストとして持っています。
Application
インスタンスはApplicationCallPipeline
インスタンスでもあります。
サーバーのアプリケーションエンジンがHTTPリクエストをハンドルするとき、Application
パイプラインが実行されます
コンテキストのクラスであるApplicationCall
はApplicationやrequest
、response
、attributes
、parameters
などを保持しています。
最終的にアプリケーション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
KtorはApplicationFeature
クラスでアプリケーションの機能を定義しています。
Featureは指定のパイプラインにinstall
可能なものです。
Featureはパイプラインにアクセスし、interceptorに登録され、様々な種類のことを実行します。
Featureやパイプラインツリーがどのようにともに動作しているのかを説明するには、Routingがどのように動くのかを見るといいでしょう。
Routingは他の機能同様、通常以下のようにインストールされます:
install(Routing) { }
登録・起動するための簡単なメソッドも用意されています。すでに登録されていない場合のみインストールされるようになっています。
routing { }
Routingはツリーとして定義されます。
各ノードはRoute
であり、ApplicationCallPipeline
の分離されたインスタンスでもあります。
そのため、ルートのroutingノードが実行されたとき、それ自体のパイプラインが実行されます。
そしてパイプラインの実行が終わったときには、routeが実行され終わっています。