HttpServer.kt
- package com.hexagonkt.http.server
- import com.hexagonkt.core.logging.Logger
- import com.hexagonkt.core.Jvm.charset
- import com.hexagonkt.core.Jvm.cpuCount
- import com.hexagonkt.core.Jvm.hostName
- import com.hexagonkt.core.Jvm.name
- import com.hexagonkt.core.Jvm.version
- import com.hexagonkt.core.Jvm.localeCode
- import com.hexagonkt.http.model.HttpProtocol.HTTP2
- import java.lang.Runtime.getRuntime
- import com.hexagonkt.core.text.AnsiColor.BLUE
- import com.hexagonkt.core.text.AnsiColor.CYAN
- import com.hexagonkt.core.text.AnsiColor.DEFAULT
- import com.hexagonkt.core.text.AnsiColor.MAGENTA
- import com.hexagonkt.core.text.Ansi.RESET
- import com.hexagonkt.core.text.AnsiEffect.BOLD
- import com.hexagonkt.core.text.AnsiEffect.UNDERLINE
- import com.hexagonkt.core.Jvm.timeZone
- import com.hexagonkt.core.Jvm.totalMemory
- import com.hexagonkt.core.Jvm.usedMemory
- import com.hexagonkt.core.text.prependIndent
- import com.hexagonkt.core.urlOf
- import com.hexagonkt.http.server.HttpServerFeature.ZIP
- import com.hexagonkt.http.handlers.HttpHandler
- import com.hexagonkt.http.handlers.HandlerBuilder
- import com.hexagonkt.http.handlers.path
- import java.io.Closeable
- import java.lang.System.nanoTime
- import java.net.URL
- /**
- * Server that listen to HTTP connections on a port and address and route requests to handlers.
- */
- data class HttpServer(
- private val adapter: HttpServerPort,
- val handler: HttpHandler,
- val settings: HttpServerSettings = HttpServerSettings()
- ) : Closeable {
- companion object {
- private val logger: Logger = Logger(this::class)
- val banner: String = """
- $CYAN _________
- $CYAN / \
- $CYAN / ____ /
- $CYAN / / / /
- $CYAN / / /__/$BLUE /\$BOLD H E X A G O N$RESET
- $CYAN / /$BLUE / \$DEFAULT ___
- $CYAN \ /$BLUE ___ / /
- $CYAN \/$BLUE / / / /$CYAN T O O L K I T$RESET
- $BLUE / /___/ /
- $BLUE / /
- $BLUE \_________/ https://hexagontk.com/http_server
- $RESET
- """.trimIndent()
- }
- /**
- * Create a server with a builder ([HandlerBuilder]) to set up handlers.
- *
- * @param adapter The server engine.
- * @param settings Server settings. Port and address will be searched in this map.
- * @param block Handlers' setup block.
- * @return A new server with the configured handlers.
- */
- constructor(
- adapter: HttpServerPort,
- settings: HttpServerSettings = HttpServerSettings(),
- block: HandlerBuilder.() -> Unit
- ) :
- this(adapter, path(block = block), settings)
- override fun close() {
- stop()
- }
- init {
- val supportedProtocols = adapter.supportedProtocols()
- check(settings.protocol in supportedProtocols) {
- val supportedProtocolsText = supportedProtocols.joinToString(", ")
- "Requesting unsupported protocol. Adapter's protocols: $supportedProtocolsText"
- }
- if (settings.zip)
- check(adapter.supportedFeatures().contains(ZIP)) {
- val adapterName = adapter::class.qualifiedName
- "Requesting ZIP compression with an adapter without support: '$adapterName'"
- }
- }
- /**
- * Runtime port of the server.
- *
- * @exception IllegalStateException Throw an exception if the server hasn't been started.
- */
- val runtimePort: Int
- get() = if (started()) adapter.runtimePort() else error("Server is not running")
- /**
- * Runtime binding of the server.
- *
- * @exception IllegalStateException Throw an exception if the server hasn't been started.
- */
- val binding: URL
- get() = urlOf("${settings.bindUrl}:$runtimePort")
- /**
- * The port name of the server.
- */
- val portName: String = adapter.javaClass.simpleName
- /**
- * Check whether the server has been started.
- *
- * @return True if the server has started, else false.
- */
- fun started(): Boolean =
- adapter.started()
- /**
- * Start the server with the adapter instance and adds a shutdown hook for stopping the server.
- */
- fun start() {
- val startTimestamp = nanoTime()
- getRuntime().addShutdownHook(
- Thread(
- {
- if (started())
- adapter.shutDown()
- },
- "shutdown-${settings.bindAddress.hostName}-${settings.bindPort}"
- )
- )
- adapter.startUp(this)
- logger.info { "Server started${createBanner(nanoTime() - startTimestamp)}" }
- }
- /**
- * Stop the server.
- */
- fun stop() {
- adapter.shutDown()
- logger.info { "Server stopped" }
- }
- internal fun createBanner(startUpTimestamp: Long): String {
- val startUpTime = "%,.0f".format(startUpTimestamp / 1e6)
- val protocol = settings.protocol
- val banner = settings.banner ?: return " at $binding ($startUpTime ms)"
- val jvmMemoryValue = "$BLUE${totalMemory()} KB$RESET"
- val usedMemoryValue = "$BOLD$MAGENTA${usedMemory()} KB$RESET"
- val serverAdapterValue = "$BOLD$CYAN$portName$RESET"
- val protocols = adapter.supportedProtocols()
- .joinToString("$RESET, $CYAN", CYAN, RESET) { if (it == protocol) "✅$it" else "$it" }
- val features = adapter.supportedFeatures()
- .joinToString("$RESET, $CYAN", CYAN, RESET) { it.toString() }
- val options = adapter.options()
- .map { (k, v) -> "$k($v)" }
- .joinToString("$RESET, $CYAN", CYAN, RESET)
- val hostnameValue = "$BLUE$hostName$RESET"
- val cpuCountValue = "$BLUE$cpuCount$RESET"
- val javaVersionValue = "$BOLD${BLUE}Java $version$RESET [$BLUE$name$RESET]"
- val localeValue = "$BLUE$localeCode$RESET"
- val timezoneValue = "$BLUE${timeZone.id}$RESET"
- val charsetValue = "$BLUE$charset$RESET"
- val startUpTimeValue = "$BOLD$MAGENTA$startUpTime ms$RESET"
- val bindingValue = "$BLUE$UNDERLINE$binding$RESET"
- val information =
- """
- Server Adapter: $serverAdapterValue ($protocols)
- Supported Features: $features
- Configuration Options: $options
- 🖥️️ Running in '$hostnameValue' with $cpuCountValue CPUs $jvmMemoryValue of memory
- 🛠 Using $javaVersionValue
- 🌍 Locale: $localeValue Timezone: $timezoneValue Charset: $charsetValue
- ⏱️ Started in $startUpTimeValue (excluding VM) using $usedMemoryValue
- 🚀 Served at $bindingValue${if (protocol == HTTP2) " (HTTP/2)" else "" }
- """
- val fullBanner = banner + information.trimIndent()
- return "\n" + fullBanner.prependIndent()
- }
- }