Customise a Server backend
http4k Core
How to write a custom server implmentation
Whilst the http4k server modules ship with a sensibly configured standard server-backend setup, a lot of projects will
require specialised implementations of the underlying server backend. http4k makes this easy with the ServerConfig
interface.
Gradle setup
dependencies {
implementation(platform("org.http4k:http4k-bom:6.6.0.1"))
implementation("org.http4k:http4k-core")
implementation("org.http4k:http4k-server-jetty")
}
Examples
Secure Jetty
The example below shows a customised Jetty setup which enables HTTPS traffic by reimplementing the ServerConfig
interface. The idea is that this single class will encapsulate the usage of the Server platform API behind the http4k
abstraction and provide a simple way to reuse it across different applications.
package content.howto.customise_a_server_backend
import org.eclipse.jetty.server.HttpConfiguration
import org.eclipse.jetty.server.HttpConnectionFactory
import org.eclipse.jetty.server.SecureRequestCustomizer
import org.eclipse.jetty.server.Server
import org.eclipse.jetty.server.ServerConnector
import org.eclipse.jetty.server.SslConnectionFactory
import org.eclipse.jetty.util.ssl.SslContextFactory
import org.http4k.core.HttpHandler
import org.http4k.core.Response
import org.http4k.core.Status
import org.http4k.core.then
import org.http4k.filter.DebuggingFilters.PrintRequestAndResponse
import org.http4k.server.Http4kServer
import org.http4k.server.ServerConfig
import org.http4k.server.asServer
import org.http4k.server.toJettyHandler
class SecureJetty(
private val sslPort: Int,
private val localKeyStorePath: String,
private val localKeystorePassword: String,
private val localKeyManagerPassword: String
) : ServerConfig {
override fun toServer(http: HttpHandler): Http4kServer {
val server = Server().apply {
val https = HttpConfiguration().apply {
addCustomizer(SecureRequestCustomizer())
}
val sslContextFactory = SslContextFactory.Server().apply {
keyStorePath = localKeyStorePath
keyStorePassword = localKeystorePassword
keyManagerPassword = localKeyManagerPassword
}
connectors = arrayOf(
ServerConnector(
server,
SslConnectionFactory(sslContextFactory, "http/1.1"),
HttpConnectionFactory(https)
).apply { port = sslPort }
)
insertHandler(http.toJettyHandler())
}
return object : Http4kServer {
override fun start(): Http4kServer = apply { server.start() }
override fun stop(): Http4kServer = apply { server.stop() }
override fun port(): Int = if (sslPort > 0) sslPort else server.uri.port
}
}
}
fun main() {
PrintRequestAndResponse().then { Response(Status.OK).body("hello from secure jetty!") }
.asServer(
SecureJetty(
sslPort = 9000,
localKeyStorePath = "keystore.jks",
localKeystorePassword = "password",
localKeyManagerPassword = "password"
)
).start()
}
Apache with Local Address Binding
This example shows how to create a custom server backend using Apache with a local address binding.
package content.howto.customise_a_server_backend
import org.http4k.core.HttpHandler
import org.http4k.server.Http4kServer
import org.http4k.server.ServerConfig
import org.http4k.server.ServerConfig.StopMode
import org.http4k.server.defaultBootstrap
import org.http4k.server.stopWith
import java.net.InetAddress
import java.time.Duration
/**
* Support for creating a custom Apache server with address and canonical hostname.
*/
class CustomApacheServer(
val port: Int = 8000,
val address: InetAddress,
val canonicalHostname: String = "localhost",
override val stopMode: StopMode = StopMode.Graceful(Duration.ofSeconds(5))
) : ServerConfig {
override fun toServer(http: HttpHandler): Http4kServer = object : Http4kServer {
private val server = defaultBootstrap(port, http, canonicalHostname)
.setLocalAddress(address)
.create()
override fun start() = apply { server.start() }
override fun stop() = apply {
server.stopWith(stopMode)
}
override fun port(): Int = if (port != 0) port else server.localPort
}
}
Undertow with HTTP 2
This example shows how to create a custom server backend using Undertow with HTTP2 support.
import io.undertow.UndertowOptions.ENABLE_HTTP2
import org.http4k.core.HttpHandler
import org.http4k.server.Http4kServer
import org.http4k.server.PolyServerConfig
import org.http4k.server.ServerConfig.StopMode
import org.http4k.server.ServerConfig.StopMode.Immediate
import org.http4k.server.buildHttp4kUndertowServer
import org.http4k.server.buildUndertowHandlers
import org.http4k.server.defaultUndertowBuilder
import org.http4k.sse.SseHandler
import org.http4k.websocket.WsHandler
/**
* Custom Undertow server configuration with http 2 support
*/
class CustomUndertowServer(
private val port: Int = 8000,
private val enableHttp2: Boolean = false,
override val stopMode: StopMode = Immediate
) : PolyServerConfig {
override fun toServer(http: HttpHandler?, ws: WsHandler?, sse: SseHandler?): Http4kServer {
val (httpHandler, multiProtocolHandler) = buildUndertowHandlers(http, ws, sse, stopMode)
return defaultUndertowBuilder(port, multiProtocolHandler)
.setServerOption(ENABLE_HTTP2, enableHttp2)
.buildHttp4kUndertowServer(httpHandler, stopMode, port)
}
}