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.
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.
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 🎉🙌
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.
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!
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
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!
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
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