Skip to content

martinandersson/nomagichttp

Repository files navigation

GitHub CI JitPack

NoMagicHTTP

A Java library for receiving HTTP requests and responding to them.

The library uses virtual threads (JEP 425), meaning that the dependent application writes "straightforward blocking code", yet reaps "near-optimal hardware utilization".

The API is elegant, and based on the firmly held belief that magic is evil. Reflection code, error-prone annotations, missing "beans" and God-like, ill-defined "context" objects will never be a part of the library. The source code is crafted by artsmen seeking perfection through simplicity, developer happiness, and a minimal waste of time.

What you get is a server as fast and scalable as any cross-platform JDK-based HTTP server implementation could possibly be.

All-you-need JavaDoc is here.

WARNING: This project is in an alpha phase without proper release management/versioning. The document POA.md details planned future work, and consequently, what parts of the HTTP stack have not yet been delivered.

Minimal Example

In an empty directory, create a new Gradle build file build.gradle:

plugins {
    id('application')
}

repositories {
    maven {
        url('https://jitpack.io')
    }
}

dependencies {
    implementation('com.github.martinandersson:nomagichttp:master-SNAPSHOT')
}

// Some extra sauce needed while virtual threads are a preview feature
tasks.withType(JavaCompile).configureEach {
    options.compilerArgs += '--enable-preview'
}

application {
    mainClass = 'Greeter'
    applicationDefaultJvmArgs += '--enable-preview'
}

In subfolder src/main/java, create a new file Greeter.java:

import alpha.nomagichttp.HttpServer;
import java.io.IOException;
import static alpha.nomagichttp.handler.RequestHandler.GET;
import static alpha.nomagichttp.message.Responses.text;

class Greeter {
    public static void main(String[] args) throws IOException, InterruptedException {
        HttpServer.create()
                  .add("/greeting",
                      GET().apply(request -> text("Hello Stranger!")))
                  .start(8080);
    }
}

Make sure you are using at least Java 21, then start the server:

foo@bar:projectfolder$ gradle run
> Task :run
Oct 15, 2023 11:30:13 AM alpha.nomagichttp.core.DefaultServer lambda$openOrFail$8
INFO: Opened server channel: sun.nio.ch.ServerSocketChannelImpl[/[0:0:0:0:0:0:0:0]:8080]
<=========----> 75% EXECUTING [1s]
> :run

In another terminal:

foo@bar:projectfolder$  curl localhost:8080/greeting
Hello Stranger!

In a real-world scenario where the Java runtime is used to start the application, the start-up time is pretty much instantaneous. Be prepared for uber-fast development cycles and running real HTTP exchanges in your test cases, because you can 🎉🙌

Getting Started

The NoMagicHTTP library is documented through detailed and exhaustive JavaDoc of an API that is discoverable and intuitive. JavaDoc is the contract, and meant to be the only documentation needed. Anything one reads outside of JavaDoc, is advisory only.

Subsequent sections discuss example code, which are packaged with the published JAR and can be executed simply by replacing the mainClass value in the previous Gradle build file. For example, to run the first example below:

application {
    mainClass = 'alpha.nomagichttp.examples.HelloWorld'
}

It is recommended to follow the links in each example as the source code contains useful commentary that explains the API used.

Hello World

If a port is not specified, the system will pick a port on the loopback address.

See code: src/main/java/.../HelloWorld.java

Run:

foo@bar:projectfolder$ gradle run
Listening on port 40863.

Make a request to the port in a new terminal window:

foo@bar:~$ curl -i localhost:40863/hello
HTTP/1.1 200 OK
Content-Type: text/plain; charset=utf-8
Content-Length: 12

Hello World!

Greet using name from request path

This example registers two routes in order to respond a greeting with a name taken from a path- or query parameter.

See code: src/main/java/.../GreetParameter.java

Run:

foo@bar:projectfolder$ gradle run
Listening on port 8080.

In a new terminal, run:

foo@bar:~$ curl -i localhost:8080/hello/John
HTTP/1.1 200 OK
Content-Type: text/plain; charset=utf-8
Content-Length: 11

Hello John!

Alternatively, one may pass the name as a query parameter:

foo@bar:projectfolder$ curl -i localhost:8080/hello?name=John

Greet using name from request body

This example will greet the user with a name taken as being the request body.

See code: src/main/java/.../GreetBody.java

Run:

foo@bar:projectfolder$ gradle run
Listening on port 8080.

In a new terminal, run:

foo@bar:~$ curl -i localhost:8080/hello -d John
HTTP/1.1 200 OK
Content-Type: text/plain; charset=utf-8
Content-Length: 11

Hello John!

Echo request headers

This example echoes back the request headers.

See code: src/main/java/.../EchoHeaders.java

Run:

foo@bar:projectfolder$ gradle run
Listening on port 8080.

In a new terminal, run:

foo@bar:~$ curl -i localhost:8080/echo \
    -H "My-Header: Value 1" \
    -H "My-Header: Value 2"
HTTP/1.1 204 No Content
Host: localhost:8080
User-Agent: curl/7.81.0
Accept: */*
My-Header: Value 1
My-Header: Value 2

Keep client informed

A final response may be preceded by any number of interim 1XX (Informational) responses. This is an excellent way to keep the client informed while processing lengthy requests (without the need for server-sent events, web sockets, long polling, et cetera).

See code: src/main/java/.../KeepClientInformed.java

Run:

foo@bar:projectfolder$ gradle run
Listening on port 8080.

In a new terminal, run:

foo@bar:~$ curl -i localhost:8080
HTTP/1.1 102 Processing
Time-Left: 3 second(s)

HTTP/1.1 102 Processing
Time-Left: 2 second(s)

HTTP/1.1 102 Processing
Time-Left: 1 second(s)

HTTP/1.1 204 No Content

About

Elegant Java library for HTTP request processing, using virtual threads.

Topics

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published