Quick Start

Simple HTTP service

In this guide, we are going to create a sample HTTP service. You can read the Core or HTTP Server modules documentation for more information.

You can start by cloning a starter project (Gradle Starter or Maven Starter). Or you can create a project from scratch following these steps:

  1. Configure Kotlin in Gradle or Maven.
  2. Setup the JCenter repository (follow the link and click on the Set me up! button).
  3. Add the dependency in Gradle or Maven:
implementation("com.hexagonkt:http_server_jetty:$hexagonVersion")
<dependency>
  <groupId>com.hexagonkt</groupId>
  <artifactId>http_server_jetty</artifactId>
  <version>$hexagonVersion</version>
</dependency>
  1. Write the code in the src/main/kotlin/Hello.kt file:
import com.hexagonkt.http.httpDate
import com.hexagonkt.http.server.Server
import com.hexagonkt.http.server.ServerPort
import com.hexagonkt.http.server.jetty.JettyServletAdapter
import com.hexagonkt.injection.InjectionManager.bindObject

/**
 * Service server. It is created lazily to allow ServerPort injection (set up in main).
 */
val server: Server by lazy {
    Server {
        before {
            response.setHeader("Date", httpDate())
        }

        get("/hello/{name}") { ok("Hello, ${pathParameters["name"]}!", "text/plain") }
    }
}

/**
 * Start the service from the command line.
 */
fun main() {
    bindObject<ServerPort>(JettyServletAdapter()) // Bind Jetty server to HTTP Server Port
    server.start()
}
  1. Run the service and view the results at: http://localhost:2010/hello/world

You can check the Developer Guide for more details. Or you can clone the Gradle Starter or Maven Starter for a minimal fully working example (including tests).

Books Example

A simple CRUD example showing how to manage book resources. Here you can check the full test.

data class Book(val author: String, val title: String)

private val books: MutableMap<Int, Book> = linkedMapOf(
    100 to Book("Miguel de Cervantes", "Don Quixote"),
    101 to Book("William Shakespeare", "Hamlet"),
    102 to Book("Homer", "The Odyssey")
)

val server: Server by lazy {
    Server(adapter) {
        post("/books") {
            // Require fails if parameter does not exists
            val author = queryParameters.require("author").first()
            val title = queryParameters.require("title").first()
            val id = (books.keys.max() ?: 0) + 1
            books += id to Book(author, title)
            send(201, id)
        }

        get("/books/{id}") {
            val bookId = pathParameters.require("id").toInt()
            val book = books[bookId]
            if (book != null)
                // ok() is a shortcut to send(200)
                ok("Title: ${book.title}, Author: ${book.author}")
            else
                send(404, "Book not found")
        }

        put("/books/{id}") {
            val bookId = pathParameters.require("id").toInt()
            val book = books[bookId]
            if (book != null) {
                books += bookId to book.copy(
                    author = queryParameters["author"]?.first() ?: book.author,
                    title = queryParameters["title"]?.first() ?: book.title
                )

                ok("Book with id '$bookId' updated")
            }
            else {
                send(404, "Book not found")
            }
        }

        delete("/books/{id}") {
            val bookId = pathParameters.require("id").toInt()
            val book = books[bookId]
            books -= bookId
            if (book != null)
                ok("Book with id '$bookId' deleted")
            else
                send(404, "Book not found")
        }

        // Matches path's requests with *any* HTTP method as a fallback (return 404 instead 405)
        any("/books/{id}") { send(405) }

        get("/books") { ok(books.keys.joinToString(" ", transform = Int::toString)) }
    }
}

Session Example

Example showing how to use sessions. Here you can check the full test.

val server: Server by lazy {
    Server(adapter) {
        path("/session") {
            get("/id") { ok(session.id ?: "null") }
            get("/access") { ok(session.lastAccessedTime?.toString() ?: "null") }
            get("/new") { ok(session.isNew()) }

            path("/inactive") {
                get { ok(session.maxInactiveInterval ?: "null") }

                put("/{time}") {
                    session.maxInactiveInterval = pathParameters.require("time").toInt()
                }
            }

            get("/creation") { ok(session.creationTime ?: "null") }
            post("/invalidate") { session.invalidate() }

            path("/{key}") {
                put("/{value}") {
                    session.set(pathParameters.require("key"), pathParameters.require("value"))
                }

                get { ok(session.get(pathParameters.require("key")).toString()) }
                delete { session.remove(pathParameters.require("key")) }
            }

            get {
                val attributes = session.attributes
                val attributeTexts = attributes.entries.map { it.key + " : " + it.value }

                response.setHeader("attributes", attributeTexts.joinToString(", "))
                response.setHeader("attribute values", attributes.values.joinToString(", "))
                response.setHeader("attribute names", attributes.keys.joinToString(", "))

                response.setHeader("creation", session.creationTime.toString())
                response.setHeader("id", session.id ?: "")
                response.setHeader("last access", session.lastAccessedTime.toString())

                response.status = 200
            }
        }
    }
}

Cookies Example

Demo server to show the use of cookies. Here you can check the full test.

val server: Server by lazy {
    Server(adapter) {
        post("/assertNoCookies") {
            if (request.cookies.isNotEmpty())
                halt(500)
        }

        post("/addCookie") {
            val name = queryParameters["cookieName"]?.first()
            val value = queryParameters["cookieValue"]?.first()
            response.addCookie(HttpCookie(name, value))
        }

        post("/assertHasCookie") {
            val cookieName = queryParameters.require("cookieName").first()
            val cookieValue = request.cookies[cookieName]?.value
            if (queryParameters["cookieValue"]?.first() != cookieValue)
                halt(500)
        }

        post("/removeCookie") {
            response.removeCookie(queryParameters.require("cookieName").first())
        }
    }
}

Error Handling Example

Code to show how to handle callback exceptions and HTTP error codes. Here you can check the full test.

class CustomException : IllegalArgumentException()

val server: Server by lazy {
    Server(adapter) {
        error(UnsupportedOperationException::class) {
            response.setHeader("error", it.message ?: it.javaClass.name)
            send(599, "Unsupported")
        }

        error(IllegalArgumentException::class) {
            response.setHeader("runtimeError", it.message ?: it.javaClass.name)
            send(598, "Runtime")
        }

        // Catching `Exception` handles any unhandled exception before (it has to be the last)
        error(Exception::class) { send(500, "Root handler") }

        // It is possible to execute a handler upon a given status code before returning
        error(588) { send(578, "588 -> 578") }

        get("/exception") { throw UnsupportedOperationException("error message") }
        get("/baseException") { throw CustomException() }
        get("/unhandledException") { error("error message") }

        get("/halt") { halt("halted") }
        get("/588") { halt(588) }
    }
}

Filters Example

This example shows how to add filters before and after route execution. Here you can check the full test.

private val users: Map<String, String> = mapOf(
    "Turing" to "London",
    "Dijkstra" to "Rotterdam"
)

private val server: Server by lazy {
    Server(adapter) {
        before { attributes["start"] = nanoTime() }

        before("/protected/*") {
            val authorization = request.headers["Authorization"] ?: halt(401, "Unauthorized")
            val credentials = authorization.first().removePrefix("Basic ")
            val userPassword = String(Base64.getDecoder().decode(credentials)).split(":")

            // Parameters set in call attributes are accessible in other filters and routes
            attributes["username"] = userPassword[0]
            attributes["password"] = userPassword[1]
        }

        // All matching filters are run in order unless call is halted
        before("/protected/*") {
            if(users[attributes["username"]] != attributes["password"])
                halt(403, "Forbidden")
        }

        get("/protected/hi") { ok("Hello ${attributes["username"]}!") }

        // After filters are ran even if request was halted before
        after { response.setHeader("time", nanoTime() - attributes["start"] as Long) }
    }
}

Files Example

The following code shows how to serve resources and receive files. Here you can check the full test.

private val server: Server by lazy {
    Server(adapter) {
        path("/static") {
            get("/files/*", Resource("assets")) // Serve `assets` resources on `/html/*`
            get("/resources/*", File(directory)) // Serve `test` folder on `/pub/*`
        }

        get("/html/*", Resource("assets")) // Serve `assets` resources on `/html/*`
        get("/pub/*", File(directory)) // Serve `test` folder on `/pub/*`
        get(Resource("public")) // Serve `public` resources folder on `/*`

        post("/multipart") { ok(request.parts.keys.joinToString(":")) }

        post("/file") {
            val part = request.parts.values.first()
            val content = part.inputStream.reader().readText()
            ok(content)
        }

        post("/form") {
            fun serializeMap(map: Map<String, List<String>>): List<String> = listOf(
                map.map { "${it.key}:${it.value.joinToString(",")}}" }.joinToString("\n")
            )

            val queryParams = serializeMap(queryParameters)
            val formParams = serializeMap(formParameters)
            val params = serializeMap(parameters)

            response.headers["queryParams"] = queryParams
            response.headers["formParams"] = formParams
            response.headers["params"] = params
        }
    }
}

CORS Example

This example shows how to set up CORS for REST APIs used from the browser. Here you can check the full test.

val server: Server by lazy {
    Server(adapter) {
        corsPath("/default", CorsSettings())
        corsPath("/example/org", CorsSettings("example.org"))
        corsPath("/no/credentials", CorsSettings(supportCredentials = false))
        corsPath("/only/post", CorsSettings(allowedMethods = setOf(POST)))
        corsPath("/cache", CorsSettings(preFlightMaxAge = 10))
        corsPath("/exposed/headers", CorsSettings(exposedHeaders = setOf("head")))
        corsPath("/allowed/headers", CorsSettings(allowedHeaders = setOf("head")))
    }
}

private fun Router.corsPath(path: String, settings: CorsSettings) {
    path(path) {
        // CORS settings can change for different routes
        cors(settings)

        get("/path") { ok(request.method) }
        post("/path") { ok(request.method) }
        put("/path") { ok(request.method) }
        delete("/path") { ok(request.method) }
        get { ok(request.method) }
        post { ok(request.method) }
        put { ok(request.method) }
        delete { ok(request.method) }
    }
}

Comments