HelidonHttpServer.kt

  1. package com.hexagontk.http.server.helidon

  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.handlers.bodyToBytes
  9. import com.hexagontk.http.handlers.HttpHandler
  10. import com.hexagontk.http.model.HttpProtocol
  11. import com.hexagontk.http.model.HttpProtocol.*
  12. import com.hexagontk.http.model.HttpResponse
  13. import com.hexagontk.http.model.HttpResponsePort
  14. import com.hexagontk.http.server.HttpServer
  15. import com.hexagontk.http.HttpFeature
  16. import com.hexagontk.http.HttpFeature.*
  17. import com.hexagontk.http.server.HttpServerPort
  18. import io.helidon.common.socket.SocketOptions
  19. import io.helidon.http.Method
  20. import io.helidon.http.Status
  21. import io.helidon.http.HeaderNames
  22. import io.helidon.http.HeaderNames.CONTENT_TYPE
  23. import io.helidon.http.HttpMediaType
  24. import io.helidon.http.SetCookie
  25. import io.helidon.webserver.WebServer
  26. import io.helidon.webserver.http.ServerResponse
  27. import io.helidon.webserver.http1.Http1Config
  28. import io.helidon.webserver.http2.Http2Config
  29. import java.security.SecureRandom
  30. import java.time.Duration
  31. import javax.net.ssl.KeyManagerFactory
  32. import javax.net.ssl.SSLContext
  33. import javax.net.ssl.SSLParameters
  34. import javax.net.ssl.TrustManagerFactory

  35. /**
  36.  * Implements [HttpServerPort] using Helidon.
  37.  *
  38.  * TODO Add settings for HTTP2 and separate them on constructor parameters
  39.  */
  40. class HelidonHttpServer(
  41.     private val backlog: Int = 1_024,
  42.     private val writeQueueLength: Int = 0,
  43.     private val readTimeout: Duration = Duration.ofSeconds(30),
  44.     private val connectTimeout: Duration = Duration.ofSeconds(10),
  45.     private val tcpNoDelay: Boolean = false,
  46.     private val receiveLog: Boolean = true,
  47.     private val sendLog: Boolean = true,
  48.     private val validatePath: Boolean = true,
  49.     private val validateRequestHeaders: Boolean = true,
  50.     private val validateResponseHeaders: Boolean = false,
  51.     private val smartAsyncWrites: Boolean = false,
  52. ) : HttpServerPort {

  53.     private companion object {
  54.         const val START_ERROR_MESSAGE = "Helidon server not started correctly"
  55.     }

  56.     private var helidonServer: WebServer? = null

  57.     constructor() : this(
  58.         backlog = 1_024,
  59.         writeQueueLength = 0,
  60.         readTimeout = Duration.ofSeconds(30),
  61.         connectTimeout = Duration.ofSeconds(10),
  62.         tcpNoDelay = false,
  63.         receiveLog = true,
  64.         sendLog = true,
  65.         validatePath = true,
  66.         validateRequestHeaders = true,
  67.         validateResponseHeaders = false,
  68.         smartAsyncWrites = false,
  69.     )

  70.     override fun runtimePort(): Int {
  71.         return helidonServer?.port() ?: error(START_ERROR_MESSAGE)
  72.     }

  73.     override fun started() =
  74.         helidonServer?.isRunning ?: false

  75.     override fun startUp(server: HttpServer) {
  76.         val settings = server.settings
  77.         val sslSettings = settings.sslSettings

  78.         val handlers: Map<Method, HttpHandler> =
  79.             server.handler
  80.                 .byMethod()
  81.                 .mapKeys { Method.create(it.key.toString()) }

  82.         val serverBuilder = WebServer
  83.             .builder()
  84.             .host(settings.bindAddress.hostName)
  85.             .port(settings.bindPort)
  86.             .routing {
  87.                 it.any({ helidonRequest, helidonResponse ->
  88.                     val method = helidonRequest.prologue().method()
  89.                     val request = HelidonRequestAdapter(method, helidonRequest)
  90.                     val response = handlers[method]?.process(request)?.response ?: HttpResponse()
  91.                     setResponse(request.protocol.secure, response, helidonResponse)
  92.                 })
  93.             }

  94.         if (sslSettings != null)
  95.             serverBuilder.tls {
  96.                 val sslClientAuth = sslSettings.clientAuth
  97.                 it
  98.                     .sslParameters(SSLParameters().apply { needClientAuth = sslClientAuth })
  99.                     .sslContext(sslContext(sslSettings))
  100.             }

  101.         val protocolConfig =
  102.             if (settings.protocol == HTTP || settings.protocol == HTTPS)
  103.                 Http1Config
  104.                     .builder()
  105.                     .receiveLog(receiveLog)
  106.                     .sendLog(sendLog)
  107.                     .validatePath(validatePath)
  108.                     .validateRequestHeaders(validateRequestHeaders)
  109.                     .validateResponseHeaders(validateResponseHeaders)
  110.                     .build()
  111.             else
  112.                 Http2Config
  113.                     .builder()
  114.                     .validatePath(validatePath)
  115.                     .build()

  116.         helidonServer = serverBuilder
  117.             .backlog(backlog)
  118.             .writeQueueLength(writeQueueLength)
  119.             .smartAsyncWrites(smartAsyncWrites)
  120.             .connectionOptions(SocketOptions
  121.                 .builder()
  122.                 .readTimeout(readTimeout)
  123.                 .connectTimeout(connectTimeout)
  124.                 .tcpNoDelay(tcpNoDelay)
  125.                 .build()
  126.             )
  127.             .protocols(listOf(protocolConfig))
  128.             .build()

  129.         helidonServer?.start() ?: error(START_ERROR_MESSAGE)
  130.     }

  131.     override fun shutDown() {
  132.         helidonServer?.stop() ?: error(START_ERROR_MESSAGE)
  133.     }

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

  136.     override fun supportedFeatures(): Set<HttpFeature> =
  137.         setOf(ZIP, COOKIES, MULTIPART)

  138.     override fun options(): Map<String, *> =
  139.         fieldsMapOf(
  140.             HelidonHttpServer::backlog to backlog,
  141.             HelidonHttpServer::writeQueueLength to writeQueueLength,
  142.             HelidonHttpServer::readTimeout to readTimeout,
  143.             HelidonHttpServer::connectTimeout to connectTimeout,
  144.             HelidonHttpServer::tcpNoDelay to tcpNoDelay,
  145.             HelidonHttpServer::receiveLog to receiveLog,
  146.             HelidonHttpServer::sendLog to sendLog,
  147.             HelidonHttpServer::validatePath to validatePath,
  148.             HelidonHttpServer::validateRequestHeaders to validateRequestHeaders,
  149.             HelidonHttpServer::validateResponseHeaders to validateResponseHeaders,
  150.             HelidonHttpServer::smartAsyncWrites to smartAsyncWrites,
  151.         )

  152.     private fun setResponse(
  153.         secureRequest: Boolean,
  154.         response: HttpResponsePort,
  155.         helidonResponse: ServerResponse
  156.     ) {
  157.         try {
  158.             helidonResponse.status(Status.create(response.status))

  159.             response.headers.all.forEach { (k, v) ->
  160.                 helidonResponse.header(HeaderNames.create(k), *v.map { it.text }.toTypedArray())
  161.             }

  162.             val headers = helidonResponse.headers()
  163.             response.cookies
  164.                 .filter { if (secureRequest) true else !it.secure }
  165.                 .forEach {
  166.                     val cookie = SetCookie
  167.                         .builder(it.name, it.value)
  168.                         .maxAge(Duration.ofSeconds(it.maxAge))
  169.                         .path(it.path)
  170.                         .httpOnly(it.httpOnly)
  171.                         .secure(it.secure)

  172.                     if (it.expires != null)
  173.                         cookie.expires(it.expires)

  174.                     if (it.deleted)
  175.                         headers.clearCookie(it.name)
  176.                     else
  177.                         headers.addCookie(cookie.build())
  178.                 }

  179.             response.contentType?.let { ct -> headers.contentType(HttpMediaType.create(ct.text)) }

  180.             helidonResponse.send(bodyToBytes(response.body))
  181.         }
  182.         catch (e: Exception) {
  183.             helidonResponse.status(Status.INTERNAL_SERVER_ERROR_500)
  184.             helidonResponse.header(CONTENT_TYPE, TEXT_PLAIN.fullType)
  185.             helidonResponse.send(bodyToBytes(e.toText()))
  186.         }
  187.     }

  188.     private fun sslContext(sslSettings: SslSettings): SSLContext {
  189.         val keyManager = keyManagerFactory(sslSettings)
  190.         val trustManager = trustManagerFactory(sslSettings)

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

  201.     private fun trustManagerFactory(sslSettings: SslSettings): TrustManagerFactory? {
  202.         val trustStoreUrl = sslSettings.trustStore ?: return null
  203.         val trustStorePassword = sslSettings.trustStorePassword
  204.         return createTrustManagerFactory(trustStoreUrl, trustStorePassword)
  205.     }

  206.     private fun keyManagerFactory(sslSettings: SslSettings): KeyManagerFactory {
  207.         val keyStoreUrl = sslSettings.keyStore ?: error("")
  208.         val keyStorePassword = sslSettings.keyStorePassword
  209.         return createKeyManagerFactory(keyStoreUrl, keyStorePassword)
  210.     }
  211. }