JdkHttpServer.kt

  1. package com.hexagontk.http.server.jdk

  2. import com.hexagontk.core.fieldsMapOf
  3. import com.hexagontk.core.media.TEXT_PLAIN
  4. import com.hexagontk.core.security.createKeyManagerFactory
  5. import com.hexagontk.core.security.createTrustManagerFactory
  6. import com.hexagontk.core.toText
  7. import com.hexagontk.http.SslSettings
  8. import com.hexagontk.http.model.HttpProtocol
  9. import com.hexagontk.http.model.HttpProtocol.*
  10. import com.hexagontk.http.server.HttpServer
  11. import com.hexagontk.http.HttpFeature
  12. import com.hexagontk.http.handlers.bodyToBytes
  13. import com.hexagontk.http.model.HttpResponse
  14. import com.hexagontk.http.model.HttpResponsePort
  15. import com.hexagontk.http.model.INTERNAL_SERVER_ERROR_500
  16. import com.hexagontk.http.server.HttpServerPort
  17. import com.sun.net.httpserver.Headers
  18. import com.sun.net.httpserver.HttpExchange
  19. import com.sun.net.httpserver.HttpsConfigurator
  20. import com.sun.net.httpserver.HttpServer as SunHttpServer
  21. import com.sun.net.httpserver.HttpsServer as SunHttpsServer
  22. import java.net.InetSocketAddress
  23. import com.sun.net.httpserver.HttpHandler as SunHttpHandler
  24. import java.security.SecureRandom
  25. import java.util.concurrent.Executor
  26. import javax.net.ssl.KeyManagerFactory
  27. import javax.net.ssl.SSLContext
  28. import javax.net.ssl.TrustManagerFactory

  29. /**
  30.  * Implements [HttpServerPort] using the JDK HTTP server.
  31.  *
  32.  * @param backlog .
  33.  * @param executor .
  34.  * @param stopDelay .
  35.  * @param idleInterval Maximum duration in seconds which an idle connection is kept open. This timer
  36.  *  has an implementation specific granularity that may mean that idle connections are closed later
  37.  *  than the specified interval. Values less than or equal to zero are mapped to* the default
  38.  *  setting.
  39.  * @param maxConnections The maximum number of open connections at a time. This includes active
  40.  *  and idle connections. If zero or negative, then no limit is enforced.
  41.  * @param maxIdleConnections The maximum number of idle connections at a time. If set to zero or a
  42.  *  negative value then connections are closed after use.
  43.  * @param drainAmount The maximum number of bytes that will be automatically read and discarded
  44.  *  from a request body that has not been completely consumed by its HttpHandler. If the number of
  45.  *  remaining unread bytes are less than this limit then the connection will be put in the idle
  46.  *  connection cache. If not, then it will be closed.
  47.  * @param maxReqHeaders The maximum number of header fields accepted in a request. If this limit is
  48.  *  exceeded while the headers are being read, then the connection is terminated and the request
  49.  *  ignored. If the value is less than or equal to zero, then the default value is used.
  50.  * @param maxReqTime The maximum time in milliseconds allowed to receive a request headers and body.
  51.  *  In practice, the actual time is a function of request size, network speed, and handler
  52.  *  processing delays. A value less than or equal to zero means the time is not limited. If the
  53.  *  limit is exceeded then the connection is terminated and the handler will receive a IOException.
  54.  *  This timer has an implementation specific granularity that may mean requests are aborted later
  55.  *  than the specified interval.
  56.  * @param maxRspTime The maximum time in milliseconds allowed to receive a response headers and
  57.  *  body. In practice, the actual time is a function of response size, network speed, and handler
  58.  *  processing delays. A value less than or equal to zero means the time is not limited. If the
  59.  *  limit is exceeded then the connection is terminated and the handler will receive a IOException.
  60.  *  This timer has an implementation specific granularity that may mean responses are aborted later
  61.  *  than the specified interval.
  62.  * @param nodelay If true, sets the TCP_NODELAY socket option on all incoming connections.
  63.  */
  64. class JdkHttpServer(
  65.     private val backlog: Int = 1_024,
  66.     private val executor: Executor? = null,
  67.     private val stopDelay: Int = 0,
  68.     private val idleInterval: Int = 30,
  69.     private val maxConnections: Int = -1,
  70.     private val maxIdleConnections: Int = 200,
  71.     private val drainAmount: Int = 65536,
  72.     private val maxReqHeaders: Int = 200,
  73.     private val maxReqTime: Int = -1,
  74.     private val maxRspTime: Int = -1,
  75.     private val nodelay: Boolean = false,
  76. ) : HttpServerPort {

  77.     private companion object {
  78.         const val START_ERROR_MESSAGE = "JDK HTTP server not started correctly"
  79.     }

  80.     private var started = false
  81.     private var sunServer: SunHttpServer? = null

  82.     constructor() : this(
  83.         backlog = 1_024,
  84.         executor = null,
  85.         stopDelay = 0,
  86.         idleInterval = 30,
  87.         maxConnections = -1,
  88.         maxIdleConnections = 200,
  89.         drainAmount = 65536,
  90.         maxReqHeaders = 200,
  91.         maxReqTime = -1,
  92.         maxRspTime = -1,
  93.         nodelay = false,
  94.     )

  95.     init {
  96.         System.setProperty("sun.net.httpserver.idleInterval", idleInterval.toString())
  97.         System.setProperty("jdk.httpserver.maxConnections", maxConnections.toString())
  98.         System.setProperty("sun.net.httpserver.maxIdleConnections", maxIdleConnections.toString())
  99.         System.setProperty("sun.net.httpserver.drainAmount", drainAmount.toString())
  100.         System.setProperty("sun.net.httpserver.maxReqHeaders", maxReqHeaders.toString())
  101.         System.setProperty("sun.net.httpserver.maxReqTime", maxReqTime.toString())
  102.         System.setProperty("sun.net.httpserver.maxRspTime", maxRspTime.toString())
  103.         System.setProperty("sun.net.httpserver.nodelay", nodelay.toString())
  104.     }

  105.     override fun runtimePort(): Int {
  106.         return sunServer?.address?.port ?: error(START_ERROR_MESSAGE)
  107.     }

  108.     override fun started() =
  109.         started

  110.     override fun startUp(server: HttpServer) {
  111.         val settings = server.settings
  112.         val sslSettings = settings.sslSettings
  113.         val handlers = server.handler.byMethod().mapKeys { it.key.toString() }

  114.         val host = settings.bindAddress.hostName
  115.         val port = settings.bindPort
  116.         val address = InetSocketAddress(host, port)

  117.         var jdkServer =
  118.             if (sslSettings == null)
  119.                 SunHttpServer.create(address, backlog)
  120.             else
  121.                 SunHttpsServer.create(address, backlog).apply {
  122.                     val sslContext = sslContext(sslSettings)
  123.                     httpsConfigurator = HttpsConfigurator(sslContext)
  124.                 }

  125.         jdkServer.createContext("/", object : SunHttpHandler {
  126.             override fun handle(exchange: HttpExchange) {
  127.                 val method = exchange.requestMethod
  128.                 val request = JdkRequestAdapter(method, exchange)
  129.                 val response = handlers[method]?.process(request)?.response ?: HttpResponse()
  130.                 reply(response, exchange)
  131.             }
  132.         })

  133.         jdkServer.executor = executor
  134.         jdkServer.start()

  135.         sunServer = jdkServer
  136.         started = true
  137.     }

  138.     override fun shutDown() {
  139.         sunServer?.stop(stopDelay) ?: error(START_ERROR_MESSAGE)
  140.         started = false
  141.     }

  142.     override fun supportedProtocols(): Set<HttpProtocol> =
  143.         setOf(HTTP, HTTPS)

  144.     override fun supportedFeatures(): Set<HttpFeature> =
  145.         emptySet()

  146.     override fun options(): Map<String, *> =
  147.         fieldsMapOf(
  148.             JdkHttpServer::backlog to backlog,
  149.             JdkHttpServer::executor to executor,
  150.             JdkHttpServer::stopDelay to stopDelay,
  151.             JdkHttpServer::idleInterval to idleInterval,
  152.             JdkHttpServer::maxConnections to maxConnections,
  153.             JdkHttpServer::maxIdleConnections to maxIdleConnections,
  154.             JdkHttpServer::drainAmount to drainAmount,
  155.             JdkHttpServer::maxReqHeaders to maxReqHeaders,
  156.             JdkHttpServer::maxReqTime to maxReqTime,
  157.             JdkHttpServer::maxRspTime to maxRspTime,
  158.             JdkHttpServer::nodelay to nodelay,
  159.         )

  160.     private fun reply(response: HttpResponsePort, exchange: HttpExchange) {
  161.         var headers = exchange.responseHeaders

  162.         try {
  163.             response.headers.forEach { headers.add(it.name, it.text) }
  164.             send(response.status, response.body, response.contentType?.text, headers, exchange)
  165.         }
  166.         catch (e: Exception) {
  167.             send(INTERNAL_SERVER_ERROR_500, e.toText(), TEXT_PLAIN.fullType, headers, exchange)
  168.         }
  169.         finally {
  170.             exchange.close()
  171.         }
  172.     }

  173.     private fun send(
  174.         status: Int, result: Any, contentType: String?, headers: Headers, exchange: HttpExchange
  175.     ) {
  176.         if (contentType != null)
  177.             headers.set("content-type", contentType)

  178.         var body = bodyToBytes(result)
  179.         val size = if (body.isEmpty()) -1 else body.size.toLong()
  180.         exchange.sendResponseHeaders(status, size)
  181.         if (size != -1L)
  182.             exchange.responseBody.use { it.write(body) }
  183.     }

  184.     private fun sslContext(sslSettings: SslSettings): SSLContext {
  185.         val keyManager = keyManagerFactory(sslSettings)
  186.         val trustManager = trustManagerFactory(sslSettings)

  187.         val eng = SSLContext.getDefault().createSSLEngine()
  188.         eng.needClientAuth = sslSettings.clientAuth
  189.         val context = SSLContext.getInstance("TLSv1.3")
  190.         context.init(
  191.             keyManager.keyManagers,
  192.             trustManager?.trustManagers ?: emptyArray(),
  193.             SecureRandom.getInstanceStrong()
  194.         )
  195.         return context
  196.     }

  197.     private fun trustManagerFactory(sslSettings: SslSettings): TrustManagerFactory? {
  198.         val trustStoreUrl = sslSettings.trustStore ?: return null
  199.         val trustStorePassword = sslSettings.trustStorePassword
  200.         return createTrustManagerFactory(trustStoreUrl, trustStorePassword)
  201.     }

  202.     private fun keyManagerFactory(sslSettings: SslSettings): KeyManagerFactory {
  203.         val keyStoreUrl = sslSettings.keyStore ?: error("")
  204.         val keyStorePassword = sslSettings.keyStorePassword
  205.         return createKeyManagerFactory(keyStoreUrl, keyStorePassword)
  206.     }
  207. }