Coming from GenHTTP

If you know GenHTTP, you already know genhttp-kt. The API is the same - the same builders, modules, handlers and concerns - written the way a Kotlin developer expects. This page shows how C# translates, so you can use the samples you find for GenHTTP, and the GenHTTP documentation itself, with genhttp-kt.

The samples on this page have a C# and a Kotlin tab. Choose one and every sample of the page switches with it.

Packages

Every NuGet package of GenHTTP is a Maven artifact of genhttp-kt with the group org.genhttp. Module names are written in kebab-case for the artifact and in lowercase for the package:

GenHTTP (NuGet)genhttp-kt (Maven)Package
GenHTTP.Coreorg.genhttp:genhttp-coreorg.genhttp.engine.internal (the engine)
GenHTTP.Fullorg.genhttp:genhttp-full-
GenHTTP.Core.Ioxideorg.genhttp:genhttp-core-nettyorg.genhttp.engine.netty (the Netty engine)
GenHTTP.Full.Ioxideorg.genhttp:genhttp-full-netty-
GenHTTP.Apiorg.genhttp:genhttp-apiorg.genhttp.api
GenHTTP.Modules.Functionalorg.genhttp:genhttp-modules-functionalorg.genhttp.modules.functional
GenHTTP.Modules.SinglePageApplicationsorg.genhttp:genhttp-modules-single-page-applicationsorg.genhttp.modules.singlepageapplications
GenHTTP.Testingorg.genhttp:genhttp-testingorg.genhttp.testing

A using GenHTTP.Modules.Functional; becomes import org.genhttp.modules.functional.*. Many features of the modules are extension functions, as they are extension methods in C#, so import the package of a module with a wildcard - without the import, the compiler only sees part of the overloads and reports a type mismatch.

Naming

C#Kotlin
IHandler, IRequest, IResponseBuilderHandler, Request, ResponseBuilder - no I prefix
Respond(), Header, Content(...)respond(), header, content(...) - camelCase
HandleAsync() returning ValueTasksuspend fun handle() - no Async suffix
[ResourceMethod] (ResourceMethodAttribute)@ResourceMethod - no Attribute suffix
ContentType.TextHtml, ResponseStatus.OkContentType.TextHtml, ResponseStatus.Ok - unchanged
Resource.FromString("...") (static factory)Resource.fromString("...") (companion extension)
Layout.Create().Add("api", api)Layout.create().add("api", api)

Builders are kept as they are: a factory returns a builder, every builder function returns the builder, and build() creates the instance. You may still use Kotlin’s apply { } on any of them.

Types

C# / .NETKotlin / JVM
ValueTask, Task, ValueTask<T>suspend fun ..., suspend fun ...: T
Func<A, R>, Action<A>(A) -> R, (A) -> Unit
Action<IResponseBuilder>ResponseBuilder.() -> Unit (a lambda with receiver)
StreamInputStream (reading), OutputStream (writing)
ReadOnlyMemory<byte>, byte[]ByteBuffer (read-only views), ByteArray
DateTime, TimeSpanjava.time.Instant, kotlin.time.Duration
Uri, IPAddress, CultureInfo, GuidURI, InetAddress, Locale, UUID
FileInfo, DirectoryInfojava.nio.file.Path
X509Certificate2 (with private key)KeyStore.PrivateKeyEntry
ILogger, ILoggerFactorySLF4J Logger, ILoggerFactory
recorddata class
IDisposable, IAsyncDisposableAutoCloseable, suspend fun close()
TryGet(key, out value)tryGet(key) returning a nullable value
params T[], IEnumerable<T>vararg, Iterable<T>
Embedded resources of an AssemblyThe classpath (Resource.fromClasspath(...))

Libraries

Where GenHTTP uses a library of .NET, genhttp-kt uses the established one of the JVM:

PurposeGenHTTPgenhttp-kt
JSON, XML, YAMLSystem.Text.Json, XmlSerializer, YamlDotNetJackson 3 with its Kotlin module
Protobufprotobuf-netkotlinx-serialization-protobuf
TemplatesCottlePebble
Open APINSwagswagger-core
JWTSystem.IdentityModelNimbus JOSE + JWT
ArchivesSharpCompressApache Commons Compress
Brotli, Zstandard.NETBrotli4j, zstd-jni
Dependency injectionMicrosoft.Extensions.DependencyInjectionKoin
HTTP client (proxies)HttpClientjava.net.http.HttpClient
LoggingMicrosoft.Extensions.LoggingSLF4J

Side by Side

Hosting

A server is created, configured and run exactly as in GenHTTP. RunAsync() becomes the suspending run(), which blocks the coroutine until the application is shut down.

Kotlin

Kotlin
import org.genhttp.api.content.io.Resource
import org.genhttp.engine.internal.Host
import org.genhttp.modules.io.*
import org.genhttp.modules.practices.*

suspend fun main() {
    val content = Content.from(Resource.fromString("Hello World!"))

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

C#

C#
using GenHTTP.Engine.Internal;
using GenHTTP.Modules.IO;
using GenHTTP.Modules.Practices;

var content = Content.From(Resource.FromString("Hello World!"));

await Host.Create()
          .Handler(content)
          .Defaults()
          .RunAsync();

Functional Handlers

Lambdas become suspend lambdas with typed parameters - their names and types are used to map the request, as in C#. Kotlin does not allow annotations or default values on lambda parameters, so an operation that needs them ([FromBody], int page = 1) is a function reference instead.

Kotlin

Kotlin
import org.genhttp.modules.functional.*
import org.genhttp.modules.reflection.FromBody

fun echo(@FromBody body: String) = body

fun createApi() = Inline.create()
    .get("/greet/:name") { name: String -> "Hello $name!" }
    .post("/echo", ::echo)

C#

C#
using GenHTTP.Modules.Functional;
using GenHTTP.Modules.Reflection;

var api = Inline.Create()
                .Get("/greet/:name", (string name) => $"Hello {name}!")
                .Post("/echo", ([FromBody] string body) => body);

Webservices

Attributes become annotations, Task<T> becomes a suspend function, records become data classes.

Kotlin

Kotlin
import kotlinx.coroutines.delay

import org.genhttp.modules.layouting.*
import org.genhttp.modules.reflection.FromBody
import org.genhttp.modules.reflection.Method
import org.genhttp.modules.webservices.*

data class Greeting(val text: String)

class GreetingResource {

    @ResourceMethod(path = ":name")
    fun greet(name: String) = Greeting("Hello $name!")

    @ResourceMethod(Method.Post)
    suspend fun echo(@FromBody body: String): String {
        delay(10)
        return body
    }

}

fun createApp() = Layout.create()
    .addService<GreetingResource>("greetings")

C#

C#
using GenHTTP.Modules.Layouting;
using GenHTTP.Modules.Reflection;
using GenHTTP.Modules.Webservices;

var app = Layout.Create()
                .AddService<GreetingResource>("greetings");

public record Greeting(string Text);

public class GreetingResource
{

    [ResourceMethod(":name")]
    public Greeting Greet(string name) => new($"Hello {name}!");

    [ResourceMethod(RequestMethod.Post)]
    public async Task<string> Echo([FromBody] string body)
    {
        await Task.Delay(10);
        return body;
    }

}

Custom Handlers

IHandler becomes Handler, its methods are suspend functions. On the internal engine, a handler may block - every request runs on a virtual thread - and may suspend as well. On the Netty engine, handlers run on the event loop and must suspend instead of blocking, as on ioxide.

Kotlin

Kotlin
import org.genhttp.api.content.Handler
import org.genhttp.api.infrastructure.Server
import org.genhttp.api.protocol.ContentType
import org.genhttp.api.protocol.Request
import org.genhttp.api.protocol.Response
import org.genhttp.modules.io.*

class HelloHandler : Handler {

    override suspend fun prepare(server: Server) {}

    override suspend fun handle(request: Request): Response? =
        request.respond()
            .content("Hello World!", ContentType.TextPlain)
            .build()

}

C#

C#
using GenHTTP.Api.Content;
using GenHTTP.Api.Infrastructure;
using GenHTTP.Api.Protocol;
using GenHTTP.Modules.IO;

public class HelloHandler : IHandler
{

    public ValueTask PrepareAsync(IServer server) => ValueTask.CompletedTask;

    public ValueTask<IResponse?> HandleAsync(IRequest request)
    {
        var response = request.Respond()
                              .Content("Hello World!", ContentType.TextPlain)
                              .Build();

        return new(response);
    }

}

Resources

Embedded resources of an assembly are resources on the classpath - in a Gradle project, the files below src/main/resources. A classpath folder is found in every root of the classpath, in the IDE as well as in a packaged JAR.

Kotlin

Kotlin
import org.genhttp.api.content.io.Resource
import org.genhttp.api.content.io.ResourceTree
import org.genhttp.modules.files.*
import org.genhttp.modules.io.*
import org.genhttp.modules.layouting.*

fun createApp() = Layout.create()
    .index(Content.from(Resource.fromClasspath("web/index.html")))
    .add("static", Assets.from(ResourceTree.fromClasspath("web/static")))

C#

C#
using GenHTTP.Modules.Files;
using GenHTTP.Modules.IO;
using GenHTTP.Modules.Layouting;

var app = Layout.Create()
                .Index(Content.From(Resource.FromAssembly("index.html")))
                .Add("static", Assets.From(ResourceTree.FromAssembly("static")));

What is Not There

A few parts of GenHTTP are specific to .NET and have no counterpart in genhttp-kt:

  • The Kestrel engine - genhttp-kt runs on its own engines: the port of GenHTTP’s internal engine and the Netty engine, which takes the place of Ioxide.
  • The ASP.NET Core adapter, as ASP.NET Core does not exist on the JVM.
  • Roslyn code generation for web services - genhttp-kt calls operations through method handles and kotlin-reflect, see Execution Modes.

Everything else is there. Where genhttp-kt intentionally behaves differently, the deviation log says so and explains why.