Create a custom ApplicationEngine

Ktor’s HTTP client and server provide a common interface while allowing to use of several different engines to perform and handle HTTP requests.

Ktor includes several artifacts and engines:

  • For the server: Netty, Jetty, Tomcat, CIO, TestEngine
  • For the client: ApacheEngine, JettyHttp2Engine, CIOEngine, TestHttpClientEngine

ApplicationEngine API

A simplified version of the ApplicationEngine looks like this:

ApplicationEngineInterface.kt

  1. interface ApplicationEngineFactory<
  2. out TEngine : ApplicationEngine,
  3. TConfiguration : ApplicationEngine.Configuration
  4. > {
  5. fun create(environment: ApplicationEngineEnvironment, configure: TConfiguration.() -> Unit): TEngine
  6. }
  7. interface ApplicationEngine {
  8. val environment: ApplicationEngineEnvironment
  9. fun start(wait: Boolean = false): ApplicationEngine
  10. fun stop(gracePeriod: Long, timeout: Long, timeUnit: TimeUnit)
  11. open class Configuration {
  12. val parallelism: Int
  13. var connectionGroupSize: Int
  14. var workerGroupSize: Int
  15. var callGroupSize: Int
  16. }
  17. }
  18. interface ApplicationEngineEnvironment {
  19. val connectors: List<EngineConnectorConfig>
  20. val application: Application
  21. fun start()
  22. fun stop()
  23. val classLoader: ClassLoader
  24. val log: Logger
  25. val config: ApplicationConfig
  26. val monitor: ApplicationEvents
  27. }
  28. interface EngineConnectorConfig {
  29. val type: ConnectorType
  30. val host: String
  31. val port: Int
  32. }
  33. data class ConnectorType(val name: String) {
  34. companion object {
  35. val HTTP = ConnectorType("HTTP")
  36. val HTTPS = ConnectorType("HTTPS")
  37. }
  38. }
  39. abstract class BaseApplicationEngine(
  40. final override val environment: ApplicationEngineEnvironment,
  41. val pipeline: EnginePipeline = defaultEnginePipeline(environment)
  42. ) : ApplicationEngine {
  43. val application: Application
  44. }

ApplicationEngineFactory

Each implementation of the ApplicationEngineFactory along with a subtyped ApplicationEngine.Configuration define the publicly exposed APIs for each engine.

  1. fun ApplicationEngineFactory.create(environment: ApplicationEngineEnvironment, configure: TConfiguration.() -> Unit): TEngine

The ApplicationEngineFactory.create instantiates the correct subtyped ApplicationEngine.Configuration and calls the provided configure: TConfiguration.() -> Unit lambda that should mutate the configuration object. It also constructs an implementation of the ApplicationEngine, most likely a subtype of BaseApplicationEngine.

For example:

  1. class MyApplicationEngineFactory
  2. <MyApplicationEngine, MyApplicationEngineConfiguration>
  3. {
  4. fun create(
  5. environment: ApplicationEngineEnvironment,
  6. configure: MyApplicationEngineConfiguration.() -> Unit
  7. ): MyApplicationEngine {
  8. val configuration = MyApplicationEngineConfiguration()
  9. configure(configuration)
  10. return MyApplicationEngine(environment, configuration)
  11. }
  12. }

BaseApplicationEngine

The interface ApplicationEngine with an abstract implementation of BaseApplicationEngine starts and stops the application.It holds the ApplicationEngineEnvironment as well as the constructed configuration of the application.

This class has two methods:

  • The start method: connects to the ApplicationEngineEnvironment.connectors (from the environment), starts the environment,and starts and configures the engine to trigger execution of the application pipeline for each HTTP request with an ApplicationCall.
  • The stop method: stops the engine and the environment, and unregisters all items registered by the start method.

The BaseApplicationEngine exposes an ApplicationEngineEnvironment passed to the constructor and creates an EnginePipeline,which is used as an intermediary to pre-intercept the application pipeline. It also installs default transformations in the send and receive pipelines,and logs the defined connection endpoints.

For example:

  1. class MyApplicationEngine(
  2. environment: ApplicationEngineEnvironment,
  3. configuration: MyApplicationEngineConfiguration
  4. ) : BaseApplicationEngine(environment) {
  5. val myEngine = MyEngine()
  6. override fun start(wait: Boolean): MyApplicationEngine {
  7. environment.start()
  8. myEngine.start()
  9. myEngine.addRequestHandler(::handleRequest)
  10. return this
  11. }
  12. override fun stop(gracePeriod: Long, timeout: Long, timeUnit: TimeUnit) {
  13. myEngine.removeRequestHandler(::handleRequest)
  14. myEngine.stop()
  15. environment.stop()
  16. }
  17. private fun handleRequest(request: MyEngineCall) {
  18. val call: ApplicationCall = request.toApplicationCall()
  19. pipeline.execute(call)
  20. }
  21. }