Execution Modes

The service frameworks (webservices, functional handlers and controllers) analyze the signature of each of your functions with kotlin-reflect: where every argument is read from, how the result is sent to the client and which interceptors run before the function. This analysis is done once, while the handler chain is prepared (i.e. when the server starts), and not when a request is handled.

For every request, the framework then reads the arguments in the order of the parameters, invokes the function and maps the result into an HTTP response. How the function is invoked is controlled by the execution mode.

In GenHTTP, ExecutionMode.Auto compiles a delegate for each method with code generation at runtime. genhttp-kt generates no code: ExecutionMode.Auto calls the member functions of your services through a method handle that is resolved once per function - the counterpart of the compiled delegate - and uses kotlin-reflect wherever it is needed, so both execution modes work the same on every platform the JVM runs on.

Choosing an Execution Mode

There are two execution modes:

  • ExecutionMode.Auto (the default) passes the arguments by position. Member functions (webservices, controllers) are called through a method handle resolved while the handler chain is prepared, other functions and functions with parameters or results of value classes through kotlin-reflect (callSuspend). If a parameter with a default value has no value in the request, it falls back to passing the arguments by name, so that Kotlin applies the default value.
  • ExecutionMode.Reflection always passes the arguments by name (callSuspendBy), building a map of parameters and their values for every call.

Both modes produce the same results - the frameworks are tested in both of them. To force the named invocation instead - e.g. while debugging an issue with the invocation of a function - pass ExecutionMode.Reflection to the framework handlers.

Webservices

Kotlin
import org.genhttp.engine.internal.Host

import org.genhttp.modules.layouting.*
import org.genhttp.modules.practices.*
import org.genhttp.modules.reflection.*
import org.genhttp.modules.webservices.*

// http://localhost:8080/my/

class MyService {

    @ResourceMethod
    fun sayHello(): String = "Hello World!"

}

suspend fun main() {
    val api = Layout.create()
        .addService<MyService>("my", mode = ExecutionMode.Reflection)

    Host.create()
        .handler(api)
        .defaults()
        .run()
}

Functional

Kotlin
import org.genhttp.engine.internal.Host

import org.genhttp.modules.functional.*
import org.genhttp.modules.practices.*
import org.genhttp.modules.reflection.*

// http://localhost:8080/

suspend fun main() {
    val api = Inline.create()
        .get { "Hello World!" }
        .executionMode(ExecutionMode.Reflection)

    Host.create()
        .handler(api)
        .defaults()
        .run()
}

Controllers

Kotlin
import org.genhttp.engine.internal.Host

import org.genhttp.modules.controllers.*
import org.genhttp.modules.layouting.*
import org.genhttp.modules.practices.*
import org.genhttp.modules.reflection.*

// http://localhost:8080/my/say-hello/

class MyController {

    fun sayHello(): String = "Hello World!"

}

suspend fun main() {
    val api = Layout.create()
        .addController<MyController>("my", mode = ExecutionMode.Reflection)

    Host.create()
        .handler(api)
        .defaults()
        .run()
}

The builders of the frameworks accept the mode as well, e.g. ServiceResource.from<MyService>().executionMode(...) or Controller.from<MyController>().executionMode(...).

The mode applies to functions - the members of webservices and controllers as well as function references passed to functional handlers (get(::hello)). Suspend lambdas of functional handlers (get { id: Int -> ... }) are invoked directly through their function type in both modes.

Performance

As the signatures are analyzed when the server starts, handling a request only consists of routing it to the requested function, reading the arguments, invoking the function and writing the result. ExecutionMode.Auto passes the arguments as they have been read, while ExecutionMode.Reflection builds a map of parameters and values for every call - which is why Auto is the default. For endpoints where every microsecond counts, a handwritten handler avoids reflection altogether.

Error Handling

As nothing is compiled, there is no compilation that could fail. A signature that cannot be served is detected when it is analyzed - when the handler chain is prepared on server start - and reported with an exception that describes the problem, for example:

  • a lambda whose signature cannot be read, e.g. a plain lambda instead of a suspend lambda passed to on(...) (use a suspend lambda with explicitly typed parameters or a function reference),
  • a reference to a member function without an instance (use a bound reference such as service::hello),
  • two parameters whose names only differ in case, as arguments are matched case-insensitively.

The server logs such an exception as “Failed to prepare the handler chain” when it starts, and requests to handlers that could not be prepared are answered with 500 Internal Server Error. If the server runs in development mode (development() on the host), the error page shows the exception. Lambdas take up to 16 parameters; use a function reference for more.

If you encounter a valid signature that cannot be served or is invoked incorrectly, please report an issue to the genhttp-kt repository.