Method Definitions

All API frameworks share a common base provided by the Reflection and Conversion modules which define some of the capabilities that can be used when defining an API method signature.

Injection

This section describes how parameters of your functions are initialized and which kind of types can be used.

Primitives

By default, a parameter type can be used within a method definition if it is either String, Boolean, an enum, a number (such as Int, Long, Double or BigDecimal), a UUID, or one of the date and time types Instant, OffsetDateTime, LocalDateTime, LocalDate, LocalTime and kotlin.time.Duration.

Webservices

Kotlin
@ResourceMethod
fun length(text: String): Int = text.length

Functional

Kotlin
Inline.create()
    .get { text: String -> text.length }

Controllers

Kotlin
fun index(text: String): Int = text.length

Parameters can be declared nullable (e.g. Int?) and will be initialized with null if not present. A parameter with a default value (e.g. limit: Int = 50) keeps its default value if not present or empty. Otherwise, parameters of a primitive type such as Int, Double or Boolean are initialized with 0 or false if not present - so declare a parameter nullable or give it a default value if clients may omit it. Kotlin does not allow default values on lambda parameters, so functional handlers declare such parameters in a function and pass a function reference.

By default, parameters are read from the request query (?text=abc).

If you would like to read the parameter directly from the request body, you can mark it with the @FromBody annotation. As lambda parameters cannot be annotated either, functional handlers use a function reference here as well.

Webservices

Kotlin
@ResourceMethod(Method.Put)
fun length(@FromBody text: String): Int = text.length

Functional

Kotlin
fun length(@FromBody text: String): Int = text.length

Inline.create()
    .put(::length)

Controllers

Kotlin
@ControllerAction(Method.Put)
fun index(@FromBody text: String): Int = text.length

To read the parameter from the request path, use the appropriate mechanism provided by the application framework.

Webservices

Kotlin
@ResourceMethod(Method.Delete, ":id")
fun delete(id: Int) { /* ... */ }

Functional

Kotlin
Inline.create()
    .delete(":id") { id: Int -> /* ... */ }

Controllers

Kotlin
@ControllerAction(Method.Delete)
fun delete(@FromPath id: Int) { /* ... */ }

Some frameworks allow to further restrict path parameters using a regular expression with a named group, whose name (letters only) is the name of the parameter.

Webservices

Kotlin
data class Book(val ean: Long, val title: String)

@ResourceMethod(path = "(?<ean>[0-9]{12,13})")
fun getBook(ean: Long): Book? = TODO("look up the book")

Functional

Kotlin
Inline.create()
    .get("books/(?<ean>[0-9]{12,13})") { ean: Long -> /* ... */ }

Complex Types

When using a complex type in a parameter declaration, the value will be deserialized from the request body. By default, handlers will accept content declared as XML, JSON, YAML or form encoded. If the client does not declare the Content-Type, the server will try to treat the body as JSON. A body that cannot be read is answered with 400 Bad Request, a Content-Type without a matching format with 415 Unsupported Media Type.

Webservices

Kotlin
data class Book(val id: Int, val title: String)

@ResourceMethod(Method.Post)
fun save(book: Book) { /* ... */ }

Functional

Kotlin
data class Book(val id: Int, val title: String)

Inline.create()
    .post { book: Book -> /* ... */ }

Controllers

Kotlin
data class Book(val id: Int, val title: String)

@ControllerAction(Method.Post)
fun save(book: Book) { /* ... */ }

HTML Forms

Form data can be used to populate both complex types and a generic list of key-value pairs (BodyArguments, read with getEntry like the query). Browsers will encode the content as application/x-www-form-urlencoded and the framework will populate the arguments as needed. This allows such endpoints to be used both from browsers and as a regular API.

Example form:

HTML
    <form action="/save" method="post">
        <label for="id">ID:</label>
        <input type="number" id="id" name="id" required>
        <br><br>
        
        <label for="name">Name:</label>
        <input type="text" id="name" name="name" required>
        <br><br>
        
        <input type="submit" value="Submit">
    </form>

Can be read using the following definitions:

Webservices

Kotlin
data class Customer(val id: Int = 0, val name: String = "")

@ResourceMethod(Method.Post)
fun save(bodyArgs: BodyArguments) { /* ... */ }

// or

@ResourceMethod(Method.Post)
fun save(customer: Customer) { /* ... */ }

Functional

Kotlin
data class Customer(val id: Int = 0, val name: String = "")

Inline.create()
    .post("save") { bodyArgs: BodyArguments -> /* ... */ }

// or

Inline.create()
    .post("save") { customer: Customer -> /* ... */ }

Controllers

Kotlin
data class Customer(val id: Int = 0, val name: String = "")

@ControllerAction(Method.Post)
fun save(bodyArgs: BodyArguments) { /* ... */ }

// or

@ControllerAction(Method.Post)
fun save(customer: Customer) { /* ... */ }

To read form data into a class, the framework creates an instance and then sets its properties by name - so the class needs a constructor without parameters, or default values for all parameters of its primary constructor, as Customer above. Fields that are no constructor parameter need to be mutable (var) to be set.

Request Injection

To access information about the currently executed request you can add a parameter of type Request to your method definition which will automatically be populated.

Webservices

Kotlin
@ResourceMethod
fun getUserAgent(request: Request): String? = request.header.headers.getEntry("User-Agent")

Functional

Kotlin
Inline.create()
    .get("user-agent") { request: Request -> request.header.headers.getEntry("User-Agent") }

Controllers

Kotlin
fun userAgent(request: Request): String? = request.header.headers.getEntry("User-Agent")

Injecting requests is required if you would like to generate custom responses.

If you frequently access the request in your endpoints to achieve a certain functionality, think about adding custom primitives, custom injectors or a custom concern.

Handler Injection

Similar to request injection you can also inject the Handler which is responsible for the current request. This is typically not required but can be used to create and return custom request handlers on the fly.

Streams

The request body can be injected as an InputStream, e.g. when implementing file uploads. This stream represents the processed request payload, so it will already be de-chunked (and decompressed, if decompression is enabled). It reads the payload directly from the connection instead of buffering it, so it is well suited for very large payloads - read it before your function returns.

Webservices

Kotlin
import org.genhttp.modules.reflection.*
import org.genhttp.modules.webservices.*

import java.io.InputStream

@ResourceMethod(Method.Put, "upload")
fun upload(file: InputStream) { /* ... */ }

Functional

Kotlin
import org.genhttp.modules.functional.*

import java.io.InputStream

val api = Inline.create()
    .put("upload") { file: InputStream -> /* ... */ }

Controllers

Kotlin
import org.genhttp.modules.controllers.*
import org.genhttp.modules.reflection.*

import java.io.InputStream

@ControllerAction(Method.Put)
fun upload(file: InputStream) { /* ... */ }

Custom Injection

To inject custom types besides the built-in capabilities, you can configure a custom InjectionRegistry and use it with your API services. The registry accepts ParameterInjector instances that define what types are supported and how the value is determined for a given request.

Kotlin
import org.genhttp.api.content.Handler
import org.genhttp.api.infrastructure.Server
import org.genhttp.api.protocol.Request

import kotlin.reflect.KClass

interface ParameterInjector {

    fun supports(server: Server, type: KClass<*>): Boolean

    suspend fun getValue(handler: Handler, request: Request, targetType: KClass<*>): Any?

}

supports is passed the server the service is running on and the type of the parameter, but not the request - so its answer should only depend on the type and the server configuration. It is evaluated when the service is prepared and again on every request, so keep it cheap.

The following injector will inspect the request headers for a correlation ID and create a new one if not present. It is added to the default injection registry, which is then passed to the service:

Webservices

Kotlin
import org.genhttp.api.content.Handler
import org.genhttp.api.infrastructure.Server
import org.genhttp.api.protocol.Request
import org.genhttp.api.protocol.getEntry

import org.genhttp.modules.layouting.*
import org.genhttp.modules.reflection.*
import org.genhttp.modules.reflection.injectors.ParameterInjector
import org.genhttp.modules.webservices.*

import java.util.UUID

import kotlin.reflect.KClass

data class CorrelationId(val id: String)

class CorrelationInjector : ParameterInjector {

    override fun supports(server: Server, type: KClass<*>): Boolean = type == CorrelationId::class

    override suspend fun getValue(handler: Handler, request: Request, targetType: KClass<*>): Any {
        val id = request.header.headers.getEntry("X-Correlation-ID")

        return CorrelationId(id ?: UUID.randomUUID().toString())
    }

}

val injection = Injection.default()
    .add(CorrelationInjector())

val api = Layout.create()
    .addService<MyService>("service", injection)

class MyService {

    @ResourceMethod
    fun getCorrelationId(correlation: CorrelationId): String = correlation.id

}

Functional

Kotlin
import org.genhttp.api.content.Handler
import org.genhttp.api.infrastructure.Server
import org.genhttp.api.protocol.Request
import org.genhttp.api.protocol.getEntry

import org.genhttp.modules.functional.*
import org.genhttp.modules.reflection.*
import org.genhttp.modules.reflection.injectors.ParameterInjector

import java.util.UUID

import kotlin.reflect.KClass

data class CorrelationId(val id: String)

class CorrelationInjector : ParameterInjector {

    override fun supports(server: Server, type: KClass<*>): Boolean = type == CorrelationId::class

    override suspend fun getValue(handler: Handler, request: Request, targetType: KClass<*>): Any {
        val id = request.header.headers.getEntry("X-Correlation-ID")

        return CorrelationId(id ?: UUID.randomUUID().toString())
    }

}

val injection = Injection.default()
    .add(CorrelationInjector())

val api = Inline.create()
    .injectors(injection)
    .get { correlation: CorrelationId -> correlation.id }

Controllers

Kotlin
import org.genhttp.api.content.Handler
import org.genhttp.api.infrastructure.Server
import org.genhttp.api.protocol.Request
import org.genhttp.api.protocol.getEntry

import org.genhttp.modules.controllers.*
import org.genhttp.modules.layouting.*
import org.genhttp.modules.reflection.*
import org.genhttp.modules.reflection.injectors.ParameterInjector

import java.util.UUID

import kotlin.reflect.KClass

data class CorrelationId(val id: String)

class CorrelationInjector : ParameterInjector {

    override fun supports(server: Server, type: KClass<*>): Boolean = type == CorrelationId::class

    override suspend fun getValue(handler: Handler, request: Request, targetType: KClass<*>): Any {
        val id = request.header.headers.getEntry("X-Correlation-ID")

        return CorrelationId(id ?: UUID.randomUUID().toString())
    }

}

val injection = Injection.default()
    .add(CorrelationInjector())

val api = Layout.create()
    .addController<MyController>("controller", injection)

class MyController {

    fun getCorrelationId(correlation: CorrelationId): String = correlation.id

}

User Injection

To inject the authenticated user, you can add a typed injector to your injection registry.

Kotlin
import org.genhttp.modules.authentication.basic.BasicAuthenticationUser
import org.genhttp.modules.reflection.Injection
import org.genhttp.modules.reflection.injectors.UserInjector

val injection = Injection.default()
    .add(UserInjector(BasicAuthenticationUser::class))

Instead of injecting the user, you can also use a built-in interceptor to achieve role-based authorization.

Response Generation

This section describes the various mechanisms to generate a service response.

Primitives

By default, a type can be used as a return type within a method definition under the same rules as parameter primitives: String, Boolean, enums, numbers, UUID or the date and time types.

If the function returns null, the server will generate a HTTP 204 No Content.

Webservices

Kotlin
@ResourceMethod
fun length(text: String): Int = text.length

Functional

Kotlin
Inline.create()
    .get { text: String -> text.length }

Controllers

Kotlin
fun index(text: String): Int = text.length

Complex Types

When returning a complex type, the value will be serialized and sent to the client. The response format is negotiated with the client using the Accept request header. By default, the server is capable of generating XML, JSON, YAML or form encoded responses. If no format is specified by the client, the implementation will fall back to JSON.

If the function returns null, the server will generate a HTTP 204 No Content.

Webservices

Kotlin
data class Book(val id: Int, val title: String)

@ResourceMethod
fun doWork(): Book = Book(1, "The Hobbit")

Functional

Kotlin
data class Book(val id: Int, val title: String)

Inline.create()
    .get { Book(1, "The Hobbit") }

Controllers

Kotlin
data class Book(val id: Int, val title: String)

fun index(): Book = Book(1, "The Hobbit")

Custom Responses

When injecting the request into your function, you can directly generate a Response or ResponseBuilder and return it to the client. This allows you to take full control over the response generation but is less readable than the typed versions.

Webservices

Kotlin
@ResourceMethod
fun respond(request: Request): ResponseBuilder {
    val content = Resource.fromString("Hello World")
        .build()

    return request.respond()
        .header("X-My-Header", "my-value")
        .content(content, ContentType.TextPlain)
}

Functional

Kotlin
Inline.create()
    .get { request: Request ->
        val content = Resource.fromString("Hello World")
            .build()

        request.respond()
            .header("X-My-Header", "my-value")
            .content(content, ContentType.TextPlain)
    }

Controllers

Kotlin
fun respond(request: Request): ResponseBuilder {
    val content = Resource.fromString("Hello World")
        .build()

    return request.respond()
        .header("X-My-Header", "my-value")
        .content(content, ContentType.TextPlain)
}

For additional hints on how to use this mechanism to provide responses, see the response content section.

Results

Results are type-safe responses that can still be adjusted to modify the generated HTTP response. Therefore, results can be considered an advanced way to generate responses without the need to fully generate the response in the first place.

Webservices

Kotlin
@ResourceMethod
fun doWork(): Result<String> = Result("Hello World").header("X-My-Header", "my-value")

Functional

Kotlin
Inline.create()
    .get { Result("Hello World").header("X-My-Header", "my-value") }

Controllers

Kotlin
fun index(): Result<String> = Result("Hello World").header("X-My-Header", "my-value")

The Result<T> class lets you adjust the response around the payload via status(...), connection(...), header(...) and cookie(...) - the payload itself is still serialized the same way it would be if you had returned it directly. This does not only work for data structures, but also for special types such as streams.

Result is org.genhttp.modules.reflection.Result, not the kotlin.Result of the standard library. Kotlin prefers it as soon as it is imported, explicitly or with import org.genhttp.modules.reflection.*.

Handlers

Instead of generating a response you can also return a Handler or HandlerBuilder instance. This allows you to provide a whole segment on your web application by re-using the existing handlers or by implementing custom ones.

The following example will render a fully navigable directory listing view depending on the tenant ID passed to the function:

Webservices

Kotlin
@ResourceMethod(path = "files/:tenant")
fun files(tenant: Int): HandlerBuilder {
    val tree = ResourceTree.fromDirectory("/data/tenants/$tenant")

    return Listing.from(tree)
}

Functional

Kotlin
Inline.create()
    .get("files/:tenant") { tenant: Int -> Listing.from(ResourceTree.fromDirectory("/data/tenants/$tenant")) }

Controllers

Kotlin
fun files(tenant: Int): HandlerBuilder {
    val tree = ResourceTree.fromDirectory("/data/tenants/$tenant")

    return Listing.from(tree)
}

Binary Data

To return files or similar content, you can directly return an InputStream instance from your function. The framework will automatically close the stream after it has been sent. Returning streams is not thread-safe as streams are stateful, so you will need to create a new instance for every request to be answered.

Besides streams, returning ByteArray and ByteBuffer is also supported.

Webservices

Kotlin
import org.genhttp.modules.webservices.*

import java.io.File
import java.io.InputStream
import java.nio.ByteBuffer

@ResourceMethod(path = "file")
fun getFile(): InputStream = File("...").inputStream()

@ResourceMethod(path = "bytes")
fun getBytes(): ByteArray = "Binary data".toByteArray()

@ResourceMethod(path = "memory")
fun getMemory(): ByteBuffer = ByteBuffer.wrap("Binary data".toByteArray())

Functional

Kotlin
import org.genhttp.modules.functional.*

import java.io.File
import java.nio.ByteBuffer

val api = Inline.create()
    .get("file") { File("...").inputStream() }
    .get("bytes") { "Binary data".toByteArray() }
    .get("memory") { ByteBuffer.wrap("Binary data".toByteArray()) }

Controllers

Kotlin
import java.io.File
import java.io.InputStream
import java.nio.ByteBuffer

fun download(): InputStream = File("...").inputStream()

fun bytes(): ByteArray = "Binary data".toByteArray()

fun memory(): ByteBuffer = ByteBuffer.wrap("Binary data".toByteArray())

Empty Responses

Functions returning Unit will automatically generate a HTTP 204 No Content response. This is also the case when null is returned.

Webservices

Kotlin
@ResourceMethod
fun doWork() { }

Functional

Kotlin
Inline.create()
    .get { }

Controllers

Kotlin
fun doWork() { }

Asynchronous Execution

Service functions can be declared as suspend functions, and the lambdas of functional handlers are suspend lambdas anyway, so they can call other suspending functions such as delay or the API of a coroutine-based client. All the features described in this document work for suspending functions as well. Blocking calls are fine too: requests are handled on virtual threads, so a blocking call only parks a virtual thread.

Webservices

Kotlin
@ResourceMethod
suspend fun doWork(): Int {
    delay(100)
    return 42
}

Functional

Kotlin
Inline.create()
    .get {
        delay(100)
        42
    }

Controllers

Kotlin
suspend fun doWork(): Int {
    delay(100)
    return 42
}

Registries

This section describes registries that can be used for both injection as well as response generation.

Custom Primitives

Primitives (such as UUID or Int) used in parameters or as a response type are automatically handled using the built-in FormatterRegistry. You can add support for a custom type by implementing a Formatter and adding it to a custom registry which is then used by your services:

Kotlin
import org.genhttp.api.protocol.ByteString
import org.genhttp.api.protocol.ResponseContent

import kotlin.reflect.KClass

interface Formatter {

    fun canHandle(type: KClass<*>): Boolean

    fun read(value: ByteString, type: KClass<*>): Any?

    fun write(value: Any, type: KClass<*>): String?

    fun getContent(value: Any): ResponseContent

}

read and write are used wherever the value only needs to be parsed or formatted as part of a larger operation (e.g. a path or query parameter). getContent is used when the value is the whole response body, so it needs to produce a full ResponseContent rather than just a string. A typed variant of read is available as the extension function read<T>(value).

The following implementation will add support for a Point type with x and y coordinates so it can be used in a service. The formatter is added to the default formatting registry, which is then passed to the service:

Webservices

Kotlin
import org.genhttp.api.protocol.ByteString
import org.genhttp.api.protocol.ResponseContent

import org.genhttp.modules.conversion.*
import org.genhttp.modules.conversion.formatters.Formatter
import org.genhttp.modules.io.strings.StringContent
import org.genhttp.modules.layouting.*
import org.genhttp.modules.webservices.*

import kotlin.reflect.KClass

data class Point(val x: Int, val y: Int)

class PointFormatter : Formatter {

    override fun canHandle(type: KClass<*>): Boolean = type == Point::class

    override fun read(value: ByteString, type: KClass<*>): Any {
        val (x, y) = value.toString().split('-')

        return Point(x.toInt(), y.toInt())
    }

    override fun write(value: Any, type: KClass<*>): String {
        val point = value as Point

        return "${point.x}-${point.y}"
    }

    override fun getContent(value: Any): ResponseContent = StringContent(write(value, Point::class))

}

val registry = Formatting.default()
    .add(PointFormatter())

val api = Layout.create()
    .addService<MyService>("service", formatters = registry)

class MyService {

    @ResourceMethod(path = "invert/:point")
    fun invert(point: Point): Point = Point(point.y, point.x)

}

Functional

Kotlin
import org.genhttp.api.protocol.ByteString
import org.genhttp.api.protocol.ResponseContent

import org.genhttp.modules.conversion.*
import org.genhttp.modules.conversion.formatters.Formatter
import org.genhttp.modules.functional.*
import org.genhttp.modules.io.strings.StringContent

import kotlin.reflect.KClass

data class Point(val x: Int, val y: Int)

class PointFormatter : Formatter {

    override fun canHandle(type: KClass<*>): Boolean = type == Point::class

    override fun read(value: ByteString, type: KClass<*>): Any {
        val (x, y) = value.toString().split('-')

        return Point(x.toInt(), y.toInt())
    }

    override fun write(value: Any, type: KClass<*>): String {
        val point = value as Point

        return "${point.x}-${point.y}"
    }

    override fun getContent(value: Any): ResponseContent = StringContent(write(value, Point::class))

}

val registry = Formatting.default()
    .add(PointFormatter())

val api = Inline.create()
    .formatters(registry)
    .get("invert/:point") { point: Point -> Point(point.y, point.x) }

Controllers

Kotlin
import org.genhttp.api.protocol.ByteString
import org.genhttp.api.protocol.ResponseContent

import org.genhttp.modules.controllers.*
import org.genhttp.modules.conversion.*
import org.genhttp.modules.conversion.formatters.Formatter
import org.genhttp.modules.io.strings.StringContent
import org.genhttp.modules.layouting.*

import kotlin.reflect.KClass

data class Point(val x: Int, val y: Int)

class PointFormatter : Formatter {

    override fun canHandle(type: KClass<*>): Boolean = type == Point::class

    override fun read(value: ByteString, type: KClass<*>): Any {
        val (x, y) = value.toString().split('-')

        return Point(x.toInt(), y.toInt())
    }

    override fun write(value: Any, type: KClass<*>): String {
        val point = value as Point

        return "${point.x}-${point.y}"
    }

    override fun getContent(value: Any): ResponseContent = StringContent(write(value, Point::class))

}

val registry = Formatting.default()
    .add(PointFormatter())

val api = Layout.create()
    .addController<MyController>("controller", formatters = registry)

class MyController {

    fun invert(@FromPath point: Point): Point = Point(point.y, point.x)

}

The serialization format is implemented by the formatter, so the API can be called via /invert/8-10 and will return 10-8 as a text formatted response.

Serialization Formats

Serialization allows to read and write complex types, so they can be used in your method definitions. By default, services can consume and produce entities in XML, JSON, YAML or in form encoding, with a default fallback to JSON. JSON, XML and YAML are read and written with Jackson and its Kotlin module, so data classes work as they are.

When sending an entity to your service, the client should specify the Content-Type of the body so the server can choose the correct deserializer to read the data with. The Accept header sent by the client tells the server which serialization format is preferred by the client when a response is generated. The server will tell the client which serialization format was used to generate the body of the response by specifying the Content-Type header again.

HTTP Content Flow

To add support for an additional format you can implement the SerializationFormat interface and add your implementation to a registry which is then passed to your service, via .add(contentType, format).

For example, the package genhttp-modules-protobuf adds support for Protocol Buffers (ContentType.ApplicationProtobuf) which is not enabled by default. It is based on kotlinx.serialization, so the types sent and received need to be @Serializable. It adds an addProtobuf() extension to the registry builder, so you don’t need to register the format manually.

Webservices

Kotlin
import kotlinx.serialization.Serializable

import org.genhttp.modules.conversion.*
import org.genhttp.modules.layouting.*
import org.genhttp.modules.protobuf.*
import org.genhttp.modules.reflection.*
import org.genhttp.modules.webservices.*

@Serializable
data class Book(val id: Int, val title: String)

val registry = Serialization.default()
    .addProtobuf()

val api = Layout.create()
    .addService<BookService>("service", serializers = registry)

class BookService {

    @ResourceMethod(Method.Put)
    fun store(book: Book): Book = book

}

Functional

Kotlin
import kotlinx.serialization.Serializable

import org.genhttp.modules.conversion.*
import org.genhttp.modules.functional.*
import org.genhttp.modules.protobuf.*

@Serializable
data class Book(val id: Int, val title: String)

val registry = Serialization.default()
    .addProtobuf()

val api = Inline.create()
    .serializers(registry)
    .put { book: Book -> book }

Controllers

Kotlin
import kotlinx.serialization.Serializable

import org.genhttp.modules.controllers.*
import org.genhttp.modules.conversion.*
import org.genhttp.modules.layouting.*
import org.genhttp.modules.protobuf.*
import org.genhttp.modules.reflection.*

@Serializable
data class Book(val id: Int, val title: String)

val registry = Serialization.default()
    .addProtobuf()

val api = Layout.create()
    .addController<BookController>("controller", serializers = registry)

class BookController {

    @ControllerAction(Method.Put)
    fun store(book: Book): Book = book

}

To configure the default serialization formats, you can pass a Jackson JsonMapper directly to the default() factory. Start with JsonFormat.getDefaultOptions() to keep the settings genhttp-kt uses by default and change what you need:

Kotlin
import org.genhttp.modules.conversion.Serialization
import org.genhttp.modules.conversion.serializers.json.JsonFormat

import tools.jackson.databind.SerializationFeature

val mapper = JsonFormat.getDefaultOptions()
    .enable(SerializationFeature.INDENT_OUTPUT)
    .build()

val serialization = Serialization.default(jsonMapper = mapper)

To produce the same output as GenHTTP, the default JSON settings write property names in camel case, leave out properties that are null, ignore unknown properties when reading, write enums as numbers and escape HTML-sensitive and non-ASCII characters. XML element names are written in Pascal case and read case-insensitively.

Interceptors

With interceptors, you can add behavior on operation-level by using additional annotations. This is useful for features such as authorization. An interceptor implements OperationInterceptor, and an annotation class annotated with @InterceptWith connects it with the functions it should intercept. As lambdas cannot be annotated, functional handlers use a function reference.

Webservices

Kotlin
import org.genhttp.api.content.ProviderException
import org.genhttp.api.protocol.ByteString
import org.genhttp.api.protocol.Request
import org.genhttp.api.protocol.ResponseStatus
import org.genhttp.engine.internal.Host

import org.genhttp.modules.layouting.*
import org.genhttp.modules.reflection.*
import org.genhttp.modules.reflection.operations.Operation
import org.genhttp.modules.webservices.*

suspend fun main() {
    val app = Layout.create().addService<MyService>("service")

    Host.create()
        .handler(app)
        .run()
}

@Target(AnnotationTarget.FUNCTION)
@InterceptWith(LocalhostInterceptor::class)
annotation class RequireLocalhost

class LocalhostInterceptor : OperationInterceptor {

    override fun configure(attribute: Annotation) {
        if (attribute is RequireLocalhost) {
            // ...
        }
    }

    override suspend fun intercept(request: Request, operation: Operation, arguments: Map<ByteString, Any?>): InterceptionResult? {
        if (request.client.address?.isLoopbackAddress != true) {
            throw ProviderException(ResponseStatus.Forbidden, "Access from localhost only!")
        }

        return null
    }

}

class MyService {

    @ResourceMethod
    @RequireLocalhost
    fun myMethod(): String = "Hello to localhost"

}

Functional

Kotlin
import org.genhttp.api.content.ProviderException
import org.genhttp.api.protocol.ByteString
import org.genhttp.api.protocol.Request
import org.genhttp.api.protocol.ResponseStatus
import org.genhttp.engine.internal.Host

import org.genhttp.modules.functional.*
import org.genhttp.modules.reflection.*
import org.genhttp.modules.reflection.operations.Operation

suspend fun main() {
    val app = Inline.create().get(::hello)

    Host.create()
        .handler(app)
        .run()
}

@RequireLocalhost
fun hello(): String = "Hello to localhost!"

@Target(AnnotationTarget.FUNCTION)
@InterceptWith(LocalhostInterceptor::class)
annotation class RequireLocalhost

class LocalhostInterceptor : OperationInterceptor {

    override fun configure(attribute: Annotation) {
        if (attribute is RequireLocalhost) {
            // ...
        }
    }

    override suspend fun intercept(request: Request, operation: Operation, arguments: Map<ByteString, Any?>): InterceptionResult? {
        if (request.client.address?.isLoopbackAddress != true) {
            throw ProviderException(ResponseStatus.Forbidden, "Access from localhost only!")
        }

        return null
    }

}

Controllers

Kotlin
import org.genhttp.api.content.ProviderException
import org.genhttp.api.protocol.ByteString
import org.genhttp.api.protocol.Request
import org.genhttp.api.protocol.ResponseStatus
import org.genhttp.engine.internal.Host

import org.genhttp.modules.controllers.*
import org.genhttp.modules.layouting.*
import org.genhttp.modules.reflection.*
import org.genhttp.modules.reflection.operations.Operation

suspend fun main() {
    val app = Layout.create().addController<MyController>("controller")

    Host.create()
        .handler(app)
        .run()
}

@Target(AnnotationTarget.FUNCTION)
@InterceptWith(LocalhostInterceptor::class)
annotation class RequireLocalhost

class LocalhostInterceptor : OperationInterceptor {

    override fun configure(attribute: Annotation) {
        if (attribute is RequireLocalhost) {
            // ...
        }
    }

    override suspend fun intercept(request: Request, operation: Operation, arguments: Map<ByteString, Any?>): InterceptionResult? {
        if (request.client.address?.isLoopbackAddress != true) {
            throw ProviderException(ResponseStatus.Forbidden, "Access from localhost only!")
        }

        return null
    }

}

class MyController {

    @ControllerAction(Method.Get)
    @RequireLocalhost
    fun myMethod(): String = "Hello to localhost"

}

An instance of the interceptor is created for every function it is declared on and configured with the annotation found there, so it can read the arguments of the annotation in configure (@InterceptWith can also be placed on a function directly). Instead of throwing an exception, intercept can also return an InterceptionResult with a payload - adjusted like a result - to answer the request without invoking the function.