HelidonServerAdapter.kt

  1. package com.hexagonkt.http.server.helidon

  2. import com.hexagonkt.core.fieldsMapOf
  3. import com.hexagonkt.core.security.createKeyManagerFactory
  4. import com.hexagonkt.core.security.createTrustManagerFactory
  5. import com.hexagonkt.core.toText
  6. import com.hexagonkt.http.SslSettings
  7. import com.hexagonkt.http.handlers.bodyToBytes
  8. import com.hexagonkt.http.handlers.HttpHandler
  9. import com.hexagonkt.http.model.HttpProtocol
  10. import com.hexagonkt.http.model.HttpProtocol.*
  11. import com.hexagonkt.http.model.HttpResponse
  12. import com.hexagonkt.http.model.HttpResponsePort
  13. import com.hexagonkt.http.server.HttpServer
  14. import com.hexagonkt.http.server.HttpServerFeature
  15. import com.hexagonkt.http.server.HttpServerFeature.ZIP
  16. import com.hexagonkt.http.server.HttpServerPort
  17. import io.helidon.common.socket.SocketOptions
  18. import io.helidon.http.Method
  19. import io.helidon.http.Status
  20. import io.helidon.http.HeaderNames
  21. import io.helidon.http.HttpMediaType
  22. import io.helidon.http.SetCookie
  23. import io.helidon.webserver.WebServer
  24. import io.helidon.webserver.http.ServerResponse
  25. import io.helidon.webserver.http1.Http1Config
  26. import io.helidon.webserver.http2.Http2Config
  27. import java.security.SecureRandom
  28. import java.time.Duration
  29. import javax.net.ssl.KeyManagerFactory
  30. import javax.net.ssl.SSLContext
  31. import javax.net.ssl.SSLParameters
  32. import javax.net.ssl.TrustManagerFactory

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

  50.     private companion object {
  51.         const val START_ERROR_MESSAGE = "Helidon server not started correctly"
  52.     }

  53.     private var helidonServer: WebServer? = null

  54.     override fun runtimePort(): Int {
  55.         return helidonServer?.port() ?: error(START_ERROR_MESSAGE)
  56.     }

  57.     override fun started() =
  58.         helidonServer?.isRunning ?: false

  59.     override fun startUp(server: HttpServer) {
  60.         val settings = server.settings
  61.         val sslSettings = settings.sslSettings

  62.         val handlers: Map<Method, HttpHandler> =
  63.             server.handler.addPrefix(settings.contextPath)
  64.                 .byMethod()
  65.                 .mapKeys { Method.create(it.key.toString()) }

  66.         // TODO features(): [Config, Encoding, Media, WebServer] Maybe Multipart can be added there
  67.         val serverBuilder = WebServer
  68.             .builder()
  69.             .host(settings.bindAddress.hostName)
  70.             .port(settings.bindPort)
  71.             .routing {
  72.                 it.any({ helidonRequest, helidonResponse ->
  73.                     val method = helidonRequest.prologue().method()
  74.                     val request = HelidonRequestAdapter(method, helidonRequest)
  75.                     val response = handlers[method]?.process(request)?.response ?: HttpResponse()
  76.                     setResponse(request.protocol.secure, response, helidonResponse)
  77.                 })
  78.             }

  79.         if (sslSettings != null)
  80.             serverBuilder.tls {
  81.                 val sslClientAuth = sslSettings.clientAuth
  82.                 it
  83.                     .sslParameters(SSLParameters().apply { needClientAuth = sslClientAuth })
  84.                     .sslContext(sslContext(sslSettings))
  85.             }

  86.         val protocolConfig =
  87.             if (settings.protocol == HTTP || settings.protocol == HTTPS)
  88.                 Http1Config
  89.                     .builder()
  90.                     .receiveLog(receiveLog)
  91.                     .sendLog(sendLog)
  92.                     .validatePath(validatePath)
  93.                     .validateRequestHeaders(validateRequestHeaders)
  94.                     .validateResponseHeaders(validateResponseHeaders)
  95.                     .build()
  96.             else
  97.                 Http2Config
  98.                     .builder()
  99.                     .validatePath(validatePath)
  100.                     .build()

  101.         helidonServer = serverBuilder
  102.             .backlog(backlog)
  103.             .writeQueueLength(writeQueueLength)
  104.             .connectionOptions(SocketOptions
  105.                 .builder()
  106.                 .readTimeout(readTimeout)
  107.                 .connectTimeout(connectTimeout)
  108.                 .tcpNoDelay(tcpNoDelay)
  109.                 .build()
  110.             )
  111.             .protocols(listOf(protocolConfig))
  112.             .build()

  113.         helidonServer?.start() ?: error(START_ERROR_MESSAGE)
  114.     }

  115.     override fun shutDown() {
  116.         helidonServer?.stop() ?: error(START_ERROR_MESSAGE)
  117.     }

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

  120.     override fun supportedFeatures(): Set<HttpServerFeature> =
  121.         setOf(ZIP)

  122.     override fun options(): Map<String, *> =
  123.         fieldsMapOf(
  124.             HelidonServerAdapter::backlog to backlog,
  125.             HelidonServerAdapter::writeQueueLength to writeQueueLength,
  126.             HelidonServerAdapter::readTimeout to readTimeout,
  127.             HelidonServerAdapter::connectTimeout to connectTimeout,
  128.             HelidonServerAdapter::tcpNoDelay to tcpNoDelay,
  129.             HelidonServerAdapter::receiveLog to receiveLog,
  130.             HelidonServerAdapter::sendLog to sendLog,
  131.             HelidonServerAdapter::validatePath to validatePath,
  132.             HelidonServerAdapter::validateRequestHeaders to validateRequestHeaders,
  133.             HelidonServerAdapter::validateResponseHeaders to validateResponseHeaders,
  134.         )

  135.     private fun setResponse(
  136.         secureRequest: Boolean,
  137.         response: HttpResponsePort,
  138.         helidonResponse: ServerResponse
  139.     ) {
  140.         try {
  141.             helidonResponse.status(Status.create(response.status.code))

  142.             response.headers.values.forEach {
  143.                 helidonResponse.header(HeaderNames.create(it.name), *it.strings().toTypedArray())
  144.             }

  145.             val headers = helidonResponse.headers()
  146.             response.cookies
  147.                 .filter { if (secureRequest) true else !it.secure }
  148.                 .forEach {
  149.                     val cookie = SetCookie
  150.                         .builder(it.name, it.value)
  151.                         .maxAge(Duration.ofSeconds(it.maxAge))
  152.                         .path(it.path)
  153.                         .httpOnly(it.httpOnly)
  154.                         .secure(it.secure)

  155.                     if (it.expires != null)
  156.                         cookie.expires(it.expires)

  157.                     if (it.deleted)
  158.                         headers.clearCookie(it.name)
  159.                     else
  160.                         headers.addCookie(cookie.build())
  161.                 }

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

  163.             helidonResponse.send(bodyToBytes(response.body))
  164.         }
  165.         catch (e: Exception) {
  166.             helidonResponse.status(Status.INTERNAL_SERVER_ERROR_500)
  167.             helidonResponse.send(bodyToBytes(e.toText()))
  168.         }
  169.     }

  170.     private fun sslContext(sslSettings: SslSettings): SSLContext {
  171.         val keyManager = keyManagerFactory(sslSettings)
  172.         val trustManager = trustManagerFactory(sslSettings)

  173.         val eng = SSLContext.getDefault().createSSLEngine()
  174.         eng.needClientAuth = sslSettings.clientAuth
  175.         val context = SSLContext.getInstance("TLSv1.3")
  176.         context.init(
  177.             keyManager.keyManagers,
  178.             trustManager?.trustManagers ?: emptyArray(),
  179.             SecureRandom.getInstanceStrong()
  180.         )
  181.         return context
  182.     }

  183.     private fun trustManagerFactory(sslSettings: SslSettings): TrustManagerFactory? {
  184.         val trustStoreUrl = sslSettings.trustStore ?: return null
  185.         val trustStorePassword = sslSettings.trustStorePassword
  186.         return createTrustManagerFactory(trustStoreUrl, trustStorePassword)
  187.     }

  188.     private fun keyManagerFactory(sslSettings: SslSettings): KeyManagerFactory {
  189.         val keyStoreUrl = sslSettings.keyStore ?: error("")
  190.         val keyStorePassword = sslSettings.keyStorePassword
  191.         return createKeyManagerFactory(keyStoreUrl, keyStorePassword)
  192.     }
  193. }