NettyServerAdapter.kt

  1. package com.hexagonkt.http.server.netty

  2. import com.hexagonkt.core.Jvm
  3. import com.hexagonkt.core.fieldsMapOf
  4. import com.hexagonkt.core.security.createKeyManagerFactory
  5. import com.hexagonkt.core.security.createTrustManagerFactory
  6. import com.hexagonkt.http.SslSettings
  7. import com.hexagonkt.http.model.HttpProtocol
  8. import com.hexagonkt.http.model.HttpProtocol.*
  9. import com.hexagonkt.http.server.HttpServer
  10. import com.hexagonkt.http.server.HttpServerFeature
  11. import com.hexagonkt.http.server.HttpServerFeature.*
  12. import com.hexagonkt.http.server.HttpServerPort
  13. import com.hexagonkt.http.server.HttpServerSettings
  14. import com.hexagonkt.http.handlers.HttpHandler
  15. import io.netty.bootstrap.ServerBootstrap
  16. import io.netty.channel.*
  17. import io.netty.channel.nio.NioEventLoopGroup
  18. import io.netty.channel.socket.nio.NioServerSocketChannel
  19. import io.netty.handler.codec.http.*
  20. import io.netty.handler.ssl.ClientAuth.OPTIONAL
  21. import io.netty.handler.ssl.ClientAuth.REQUIRE
  22. import io.netty.handler.ssl.SslContext
  23. import io.netty.handler.ssl.SslContextBuilder
  24. import io.netty.util.concurrent.DefaultEventExecutorGroup
  25. import java.net.InetSocketAddress
  26. import java.util.concurrent.TimeUnit.SECONDS
  27. import javax.net.ssl.KeyManagerFactory
  28. import javax.net.ssl.TrustManagerFactory

  29. /**
  30.  * Implements [HttpServerPort] using Netty [Channel].
  31.  */
  32. open class NettyServerAdapter(
  33.     private val bossGroupThreads: Int = 1,
  34.     private val workerGroupThreads: Int = 0,
  35.     private val executorThreads: Int = Jvm.cpuCount * 2,
  36.     private val soBacklog: Int = 4 * 1_024,
  37.     private val soReuseAddr: Boolean = true,
  38.     private val soKeepAlive: Boolean = true,
  39.     private val shutdownQuietSeconds: Long = 0,
  40.     private val shutdownTimeoutSeconds: Long = 0,
  41.     private val keepAliveHandler: Boolean = true,
  42.     private val httpAggregatorHandler: Boolean = true,
  43.     private val chunkedHandler: Boolean = true,
  44.     private val enableWebsockets: Boolean = true,
  45. ) : HttpServerPort {

  46.     private var nettyChannel: Channel? = null
  47.     private var bossEventLoop: MultithreadEventLoopGroup? = null
  48.     private var workerEventLoop: MultithreadEventLoopGroup? = null

  49.     constructor() : this(
  50.         bossGroupThreads = 1,
  51.         workerGroupThreads = 0,
  52.         executorThreads = Jvm.cpuCount * 2,
  53.         soBacklog = 4 * 1_024,
  54.         soReuseAddr = true,
  55.         soKeepAlive = true,
  56.         shutdownQuietSeconds = 0,
  57.         shutdownTimeoutSeconds = 0,
  58.         keepAliveHandler = true,
  59.         httpAggregatorHandler = true,
  60.         chunkedHandler = true,
  61.         enableWebsockets = true,
  62.     )

  63.     override fun runtimePort(): Int =
  64.         (nettyChannel?.localAddress() as? InetSocketAddress)?.port
  65.             ?: error("Error fetching runtime port")

  66.     override fun started() =
  67.         nettyChannel?.isOpen ?: false

  68.     override fun startUp(server: HttpServer) {
  69.         val bossGroup = groupSupplier(bossGroupThreads)
  70.         val workerGroup =
  71.             if (workerGroupThreads < 0) bossGroup
  72.             else groupSupplier(workerGroupThreads)
  73.         val executorGroup =
  74.             if (executorThreads > 0) DefaultEventExecutorGroup(executorThreads)
  75.             else null

  76.         try {
  77.             val settings = server.settings
  78.             val sslSettings = settings.sslSettings
  79.             val handlers: Map<HttpMethod, HttpHandler> =
  80.                 server.handler.addPrefix(settings.contextPath)
  81.                     .byMethod()
  82.                     .mapKeys { HttpMethod.valueOf(it.key.toString()) }

  83.             val nettyServer = serverBootstrapSupplier(bossGroup, workerGroup)
  84.                 .childHandler(createInitializer(sslSettings, handlers, executorGroup, settings))

  85.             val address = settings.bindAddress
  86.             val port = settings.bindPort
  87.             val future = nettyServer.bind(address, port).sync()

  88.             nettyChannel = future.channel()
  89.             bossEventLoop = bossGroup
  90.             workerEventLoop = workerGroup
  91.         }
  92.         catch (_: Exception) {
  93.             bossGroup.shutdownGracefully()
  94.             workerGroup.shutdownGracefully()
  95.             executorGroup?.shutdownGracefully()
  96.         }
  97.     }

  98.     open fun groupSupplier(it: Int): MultithreadEventLoopGroup =
  99.         NioEventLoopGroup(it)

  100.     open fun serverBootstrapSupplier(
  101.         bossGroup: MultithreadEventLoopGroup,
  102.         workerGroup: MultithreadEventLoopGroup,
  103.     ): ServerBootstrap =
  104.         ServerBootstrap().group(bossGroup, workerGroup)
  105.             .channel(NioServerSocketChannel::class.java)
  106.             .option(ChannelOption.SO_BACKLOG, soBacklog)
  107.             .option(ChannelOption.SO_REUSEADDR, soReuseAddr)
  108.             .childOption(ChannelOption.SO_KEEPALIVE, soKeepAlive)
  109.             .childOption(ChannelOption.SO_REUSEADDR, soReuseAddr)

  110.     private fun createInitializer(
  111.         sslSettings: SslSettings?,
  112.         handlers: Map<HttpMethod, HttpHandler>,
  113.         group: DefaultEventExecutorGroup?,
  114.         settings: HttpServerSettings
  115.     ) =
  116.         when {
  117.             sslSettings != null -> sslInitializer(sslSettings, handlers, group, settings)
  118.             else -> HttpChannelInitializer(
  119.                 handlers,
  120.                 group,
  121.                 settings,
  122.                 keepAliveHandler,
  123.                 httpAggregatorHandler,
  124.                 chunkedHandler,
  125.                 enableWebsockets,
  126.             )
  127.         }

  128.     private fun sslInitializer(
  129.         sslSettings: SslSettings,
  130.         handlers: Map<HttpMethod, HttpHandler>,
  131.         group: DefaultEventExecutorGroup?,
  132.         settings: HttpServerSettings
  133.     ): HttpsChannelInitializer =
  134.         HttpsChannelInitializer(
  135.             handlers,
  136.             sslContext(sslSettings),
  137.             sslSettings,
  138.             group,
  139.             settings,
  140.             keepAliveHandler,
  141.             httpAggregatorHandler,
  142.             chunkedHandler,
  143.             enableWebsockets,
  144.         )

  145.     private fun sslContext(sslSettings: SslSettings): SslContext {
  146.         val keyManager = keyManagerFactory(sslSettings)

  147.         val sslContextBuilder = SslContextBuilder
  148.             .forServer(keyManager)
  149.             .clientAuth(if (sslSettings.clientAuth) REQUIRE else OPTIONAL)

  150.         val trustManager = trustManagerFactory(sslSettings)

  151.         return if (trustManager == null) sslContextBuilder.build()
  152.             else sslContextBuilder.trustManager(trustManager).build()
  153.     }

  154.     private fun trustManagerFactory(sslSettings: SslSettings): TrustManagerFactory? {
  155.         val trustStoreUrl = sslSettings.trustStore ?: return null
  156.         return createTrustManagerFactory(trustStoreUrl, sslSettings.trustStorePassword)
  157.     }

  158.     private fun keyManagerFactory(sslSettings: SslSettings): KeyManagerFactory {
  159.         val keyStoreUrl = sslSettings.keyStore ?: error("")
  160.         return createKeyManagerFactory(keyStoreUrl, sslSettings.keyStorePassword)
  161.     }

  162.     override fun shutDown() {
  163.         workerEventLoop
  164.             ?.shutdownGracefully(shutdownQuietSeconds, shutdownTimeoutSeconds, SECONDS)?.sync()
  165.         bossEventLoop
  166.             ?.shutdownGracefully(shutdownQuietSeconds, shutdownTimeoutSeconds, SECONDS)?.sync()

  167.         nettyChannel = null
  168.         bossEventLoop = null
  169.         workerEventLoop = null
  170.     }

  171.     override fun supportedProtocols(): Set<HttpProtocol> =
  172.         setOf(HTTP, HTTPS, HTTP2)

  173.     override fun supportedFeatures(): Set<HttpServerFeature> =
  174.         setOf(ZIP, WEB_SOCKETS, SSE)

  175.     override fun options(): Map<String, *> =
  176.         fieldsMapOf(
  177.             NettyServerAdapter::bossGroupThreads to bossGroupThreads,
  178.             NettyServerAdapter::workerGroupThreads to workerGroupThreads,
  179.             NettyServerAdapter::executorThreads to executorThreads,
  180.             NettyServerAdapter::soBacklog to soBacklog,
  181.             NettyServerAdapter::soKeepAlive to soKeepAlive,
  182.             NettyServerAdapter::shutdownQuietSeconds to shutdownQuietSeconds,
  183.             NettyServerAdapter::shutdownTimeoutSeconds to shutdownTimeoutSeconds,
  184.             NettyServerAdapter::keepAliveHandler to keepAliveHandler,
  185.             NettyServerAdapter::httpAggregatorHandler to httpAggregatorHandler,
  186.             NettyServerAdapter::chunkedHandler to chunkedHandler,
  187.             NettyServerAdapter::enableWebsockets to enableWebsockets,
  188.         )
  189. }