Cette page démarre un serveur HTTP Chappe en moins de 30 lignes de Java, puis enchaîne sur un handler typé, le service de fichiers statiques et un test de charge minimal avec wrk.

Pré-requis

  • Java 25 — Temurin recommandé.

  • Maven 3.9.16 — épinglé via .sdkmanrc à la racine du repo chappe.

  • sdk env au niveau du repo charge la bonne version JDK et Maven. Si mvn échoue avec modelVersion 4.1.0 not supported, repasser à Maven 4 : sdk use maven 3.9.16.

  • Optionnel : wrk pour le test de charge final (brew install wrk ou équivalent).

Coordonnées Maven

<dependency>
    <groupId>io.vidocq.chappe</groupId>
    <artifactId>chappe-api</artifactId>
    <version>0.1.0-SNAPSHOT</version>
</dependency>
<dependency>
    <groupId>io.vidocq.chappe</groupId>
    <artifactId>chappe-core</artifactId>
    <version>0.1.0-SNAPSHOT</version>
    <scope>runtime</scope>
</dependency>
<dependency>
    <groupId>io.vidocq.chappe</groupId>
    <artifactId>chappe-http</artifactId>
    <version>0.1.0-SNAPSHOT</version>
    <scope>runtime</scope>
</dependency>

chappe-api est l’API publique compilée en compile. chappe-core (moteur serveur) et chappe-http (protocoles HTTP/1.1 et HTTP/2) sont en runtime — découverts via ServiceLoader (ServerProvider).

Hello world

import io.vidocq.chappe.api.*;

void main() {
    var router = Router.builder()
        .get("/", _ -> Response.ok("Hello, Chappe!"))
        .get("/users/{id}", req -> Response.ok("User " + req.pathParams().get("id")))
        .build();

    try (var server = Server.builder()
            .port(8080)
            .handler(router)
            .build()) {
        server.start();
        Thread.currentThread().join();
    }
}

L’instance Server est AutoCloseable ; start() ouvre le socket d’écoute et lance la boucle d’acceptation sur un virtual thread.

void main() (sans String[] args) est valide en Java 25 grâce à JEP 512 (Compact Source Files). Pour un main standard, écrire public static void main(String[] args).

Premier handler typé

Pour lire un body JSON, écrire un POST, retourner un statut explicite :

import io.vidocq.chappe.api.*;
import java.nio.charset.StandardCharsets;

Router api = Router.builder()
    .post("/users", req -> {
        var body = new String(
            req.body().asInputStream().readAllBytes(),
            StandardCharsets.UTF_8
        );
        return Response.builder()
            .status(StatusCode.CREATED)
            .header("Content-Type", "application/json")
            .body("{\"received\": " + body + "}")
            .build();
    })
    .delete("/users/{id}", req -> Response.of(StatusCode.NO_CONTENT))
    .build();

Servir un répertoire statique

var staticHandler = StaticFileHandler.builder()
    .addPath(java.nio.file.Path.of("./public"))
    .addClasspath("static")
    .cacheInMemory(true)
    .cacheControl("max-age=3600, public")
    .build();

Server.builder().port(8080).handler(staticHandler).build().start();

StaticFileHandler détecte le MIME (26+ types), répond 304 Not Modified sur If-Modified-Since ou If-None-Match, et bloque le path traversal.

Build et lancement

sdk env                        # charge JDK 25 + Maven 3.9.16
./mvnw -ntp install -DskipTests
java \
     --module-path target/lib:target/myapp.jar \
     --module myapp/io.example.Main

Validation par wrk

Une fois le serveur en écoute sur 8080, valider qu’il accepte une charge minimale :

wrk -t4 -c100 -d10s http://localhost:8080/

Pour les chiffres de référence comparés à Jetty, Helidon et JDK HttpServer, voir lien:https://codeberg.org/Vidocq/chappe/blob/main/BENCH.md[BENCH.md].

Diagramme : démarrage d’un serveur

Diagram

Et après ?