HelidonHttpServer.kt
- package com.hexagontk.http.server.helidon
- import com.hexagontk.core.fieldsMapOf
- import com.hexagontk.core.media.TEXT_PLAIN
- import com.hexagontk.core.security.createKeyManagerFactory
- import com.hexagontk.core.security.createTrustManagerFactory
- import com.hexagontk.core.toText
- import com.hexagontk.http.SslSettings
- import com.hexagontk.http.handlers.bodyToBytes
- import com.hexagontk.http.handlers.HttpHandler
- import com.hexagontk.http.model.HttpProtocol
- import com.hexagontk.http.model.HttpProtocol.*
- import com.hexagontk.http.model.HttpResponse
- import com.hexagontk.http.model.HttpResponsePort
- import com.hexagontk.http.server.HttpServer
- import com.hexagontk.http.HttpFeature
- import com.hexagontk.http.HttpFeature.*
- import com.hexagontk.http.server.HttpServerPort
- import io.helidon.common.socket.SocketOptions
- import io.helidon.http.Method
- import io.helidon.http.Status
- import io.helidon.http.HeaderNames
- import io.helidon.http.HeaderNames.CONTENT_TYPE
- import io.helidon.http.HttpMediaType
- import io.helidon.http.SetCookie
- import io.helidon.webserver.WebServer
- import io.helidon.webserver.http.ServerResponse
- import io.helidon.webserver.http1.Http1Config
- import io.helidon.webserver.http2.Http2Config
- import java.security.SecureRandom
- import java.time.Duration
- import javax.net.ssl.KeyManagerFactory
- import javax.net.ssl.SSLContext
- import javax.net.ssl.SSLParameters
- import javax.net.ssl.TrustManagerFactory
- /**
- * Implements [HttpServerPort] using Helidon.
- *
- * TODO Add settings for HTTP2 and separate them on constructor parameters
- */
- class HelidonHttpServer(
- private val backlog: Int = 1_024,
- private val writeQueueLength: Int = 0,
- private val readTimeout: Duration = Duration.ofSeconds(30),
- private val connectTimeout: Duration = Duration.ofSeconds(10),
- private val tcpNoDelay: Boolean = false,
- private val receiveLog: Boolean = true,
- private val sendLog: Boolean = true,
- private val validatePath: Boolean = true,
- private val validateRequestHeaders: Boolean = true,
- private val validateResponseHeaders: Boolean = false,
- private val smartAsyncWrites: Boolean = false,
- ) : HttpServerPort {
- private companion object {
- const val START_ERROR_MESSAGE = "Helidon server not started correctly"
- }
- private var helidonServer: WebServer? = null
- constructor() : this(
- backlog = 1_024,
- writeQueueLength = 0,
- readTimeout = Duration.ofSeconds(30),
- connectTimeout = Duration.ofSeconds(10),
- tcpNoDelay = false,
- receiveLog = true,
- sendLog = true,
- validatePath = true,
- validateRequestHeaders = true,
- validateResponseHeaders = false,
- smartAsyncWrites = false,
- )
- override fun runtimePort(): Int {
- return helidonServer?.port() ?: error(START_ERROR_MESSAGE)
- }
- override fun started() =
- helidonServer?.isRunning ?: false
- override fun startUp(server: HttpServer) {
- val settings = server.settings
- val sslSettings = settings.sslSettings
- val handlers: Map<Method, HttpHandler> =
- server.handler
- .byMethod()
- .mapKeys { Method.create(it.key.toString()) }
- val serverBuilder = WebServer
- .builder()
- .host(settings.bindAddress.hostName)
- .port(settings.bindPort)
- .routing {
- it.any({ helidonRequest, helidonResponse ->
- val method = helidonRequest.prologue().method()
- val request = HelidonRequestAdapter(method, helidonRequest)
- val response = handlers[method]?.process(request)?.response ?: HttpResponse()
- setResponse(request.protocol.secure, response, helidonResponse)
- })
- }
- if (sslSettings != null)
- serverBuilder.tls {
- val sslClientAuth = sslSettings.clientAuth
- it
- .sslParameters(SSLParameters().apply { needClientAuth = sslClientAuth })
- .sslContext(sslContext(sslSettings))
- }
- val protocolConfig =
- if (settings.protocol == HTTP || settings.protocol == HTTPS)
- Http1Config
- .builder()
- .receiveLog(receiveLog)
- .sendLog(sendLog)
- .validatePath(validatePath)
- .validateRequestHeaders(validateRequestHeaders)
- .validateResponseHeaders(validateResponseHeaders)
- .build()
- else
- Http2Config
- .builder()
- .validatePath(validatePath)
- .build()
- helidonServer = serverBuilder
- .backlog(backlog)
- .writeQueueLength(writeQueueLength)
- .smartAsyncWrites(smartAsyncWrites)
- .connectionOptions(SocketOptions
- .builder()
- .readTimeout(readTimeout)
- .connectTimeout(connectTimeout)
- .tcpNoDelay(tcpNoDelay)
- .build()
- )
- .protocols(listOf(protocolConfig))
- .build()
- helidonServer?.start() ?: error(START_ERROR_MESSAGE)
- }
- override fun shutDown() {
- helidonServer?.stop() ?: error(START_ERROR_MESSAGE)
- }
- override fun supportedProtocols(): Set<HttpProtocol> =
- setOf(HTTP, HTTPS, HTTP2)
- override fun supportedFeatures(): Set<HttpFeature> =
- setOf(ZIP, COOKIES, MULTIPART)
- override fun options(): Map<String, *> =
- fieldsMapOf(
- HelidonHttpServer::backlog to backlog,
- HelidonHttpServer::writeQueueLength to writeQueueLength,
- HelidonHttpServer::readTimeout to readTimeout,
- HelidonHttpServer::connectTimeout to connectTimeout,
- HelidonHttpServer::tcpNoDelay to tcpNoDelay,
- HelidonHttpServer::receiveLog to receiveLog,
- HelidonHttpServer::sendLog to sendLog,
- HelidonHttpServer::validatePath to validatePath,
- HelidonHttpServer::validateRequestHeaders to validateRequestHeaders,
- HelidonHttpServer::validateResponseHeaders to validateResponseHeaders,
- HelidonHttpServer::smartAsyncWrites to smartAsyncWrites,
- )
- private fun setResponse(
- secureRequest: Boolean,
- response: HttpResponsePort,
- helidonResponse: ServerResponse
- ) {
- try {
- helidonResponse.status(Status.create(response.status))
- response.headers.all.forEach { (k, v) ->
- helidonResponse.header(HeaderNames.create(k), *v.map { it.text }.toTypedArray())
- }
- val headers = helidonResponse.headers()
- response.cookies
- .filter { if (secureRequest) true else !it.secure }
- .forEach {
- val cookie = SetCookie
- .builder(it.name, it.value)
- .maxAge(Duration.ofSeconds(it.maxAge))
- .path(it.path)
- .httpOnly(it.httpOnly)
- .secure(it.secure)
- if (it.expires != null)
- cookie.expires(it.expires)
- if (it.deleted)
- headers.clearCookie(it.name)
- else
- headers.addCookie(cookie.build())
- }
- response.contentType?.let { ct -> headers.contentType(HttpMediaType.create(ct.text)) }
- helidonResponse.send(bodyToBytes(response.body))
- }
- catch (e: Exception) {
- helidonResponse.status(Status.INTERNAL_SERVER_ERROR_500)
- helidonResponse.header(CONTENT_TYPE, TEXT_PLAIN.fullType)
- helidonResponse.send(bodyToBytes(e.toText()))
- }
- }
- private fun sslContext(sslSettings: SslSettings): SSLContext {
- val keyManager = keyManagerFactory(sslSettings)
- val trustManager = trustManagerFactory(sslSettings)
- val eng = SSLContext.getDefault().createSSLEngine()
- eng.needClientAuth = sslSettings.clientAuth
- val context = SSLContext.getInstance("TLSv1.3")
- context.init(
- keyManager.keyManagers,
- trustManager?.trustManagers ?: emptyArray(),
- SecureRandom.getInstanceStrong()
- )
- return context
- }
- private fun trustManagerFactory(sslSettings: SslSettings): TrustManagerFactory? {
- val trustStoreUrl = sslSettings.trustStore ?: return null
- val trustStorePassword = sslSettings.trustStorePassword
- return createTrustManagerFactory(trustStoreUrl, trustStorePassword)
- }
- private fun keyManagerFactory(sslSettings: SslSettings): KeyManagerFactory {
- val keyStoreUrl = sslSettings.keyStore ?: error("")
- val keyStorePassword = sslSettings.keyStorePassword
- return createKeyManagerFactory(keyStoreUrl, keyStorePassword)
- }
- }