This page is the canonical Chappe reference: Maven artifacts, exported JPMS modules, the major public API, the CLI YAML schema, CLI flags, and the pre-compression Maven plugin. For RFC conformance see Concepts and chappe-conformance in the repository.
Maven artifacts
| Artifact | Role |
|---|---|
|
Public API: |
|
HTTP/1.1 + HTTP/2 layer, TLS, |
|
Server engine (virtual threads, protocol detection, lifecycle, |
|
Standalone |
|
Integration tests (113 tests). |
|
HTTP conformance (45 tests against RFC 9110/9112/9113). |
|
Comparative benchmarks (Jetty, Helidon, JDK HttpServer). |
|
Maven plugin: O(1) index + |
|
Usage samples. |
JPMS modules
| Module | Exports / declarations |
|---|---|
|
|
|
|
|
|
|
|
chappe-api does not opens anything. No module exposes its internals through reflection: every dependency goes through the exports listed above.
Major public API (io.vidocq.chappe.api)
Server
public sealed interface Server extends AutoCloseable {
void start();
void stop();
int port();
static ServerBuilder builder();
}
ServerBuilder:
-
port(int) -
bindAddress(String) -
tls(SSLContext) -
alpnProtocols(String…) -
handler(Handler) -
executor(Executor)(default: virtual threads) -
build()returns aServer
Router
Router.builder()
.get(path, handler)
.post(path, handler)
.put(path, handler)
.delete(path, handler)
.patch(path, handler)
.head(path, handler) // also auto for GET
.options(path, handler)
.group(prefix, sub -> sub.filter(...).get(...))
.mount(prefix, handler)
.filter(filter)
.notFound(handler)
.build();
Behavior:
-
automatic
405 Method Not Allowedwith theAllowheader when the path matches but not the method. -
trailing slash transparent:
/usersand/users/map to the same route. -
percent-decoding on path params:
/users/John%20Doe→pathParam("id") = "John Doe".
Filter
public interface Filter {
Handler apply(Handler next);
static Filter addHeader(String name, String value);
static Filter addHeaderIf(BooleanSupplier when, String name, String value);
static Filter addHeaderIfEnv(String envVar, String expected, String name, String value);
static Filter gzip();
static Filter gzip(int threshold);
static Filter accessLog(); (1)
static Filter accessLog(Consumer<String> sink); (2)
default Filter andThen(Filter next);
}
| 1 | Logs every request to System.out in extended Apache Combined Log Format (see Observability). |
| 2 | Variant with a custom sink (useful to redirect into an application logger). |
BuildInfo
public final class BuildInfo {
public static String version(); // e.g. "0.1.0-SNAPSHOT"
public static String gitCommit(); // e.g. "8d670fb0"
public static String buildTimestamp(); // e.g. "2026-05-09T19:43:04Z"
public static String javaVersion(); // e.g. "25.0.3"
public static String serverHeader(); // e.g. "Chappe/0.1.0-SNAPSHOT+8d670fb0 (2026-05-09T19:43:04Z)"
}
Loaded once at startup from META-INF/chappe-build.properties (generated by Maven resource filtering plus git-commit-id-maven-plugin when building chappe-api). Used automatically by the HTTP layer to inject the Server and X-Chappe-Build headers.
StaticFileHandler
StaticFileHandler.builder()
.addPath(Path) // filesystem (zero-copy sendfile)
.addClasspath(String resourceRoot) // classpath (jar, module)
.cacheInMemory(boolean) // ETag cache for classpath resources
.cacheControl(String)
.preferPrecompressed(boolean) // .br / .gz sidecars when Accept-Encoding allows
.spaFallback(String) // 200 on 404 (SPA mode)
.notFoundFile(String) // 404 on 404 (mutually exclusive)
.indexFiles(String...)
.build();
AcceptEncoding
record Entry(String coding, double qvalue) {}
List<Entry> AcceptEncoding.parse(String header);
boolean AcceptEncoding.accepts(String header, String coding);
RFC 9110 §12.5.3 algorithm: q-values, wildcard, implicit identity.
chappe serve CLI configuration
Flags
| Flag | Effect |
|---|---|
|
load a YAML file |
|
override |
|
override |
|
override |
|
override |
|
enable SPA mode (status 200 on 404) |
|
override |
|
enable/disable compression |
|
enable/disable Apache CLF access log on stdout |
|
add a header (repeatable, merged into |
|
help |
Flags override YAML values.
Environment variables
| Variable | Effect |
|---|---|
|
enables the |
|
enables the access log (equivalent to |
YAML schema
server:
port: 8080
bind: 0.0.0.0
static:
root: /var/www/site
fallback: /404.html # status 404
spa-fallback: /index.html # status 200 (mutually exclusive with fallback)
index-files: [index.html]
cache-control: "max-age=3600, public"
gzip: true # negotiated through Accept-Encoding
headers:
always: # injected on every response
X-Content-Type-Options: "nosniff"
Referrer-Policy: "strict-origin-when-cross-origin"
staging: # active when env var STAGING=true
X-Robots-Tag: "noindex, nofollow"
logging:
level: INFO
access-log: false
Documented subset: nested maps, inline lists [a, b] and block - item, string/int/bool scalars, # comments. No anchors, no !! tags, no multilines.
Observability
Build identification headers
Every HTTP response from Chappe carries two headers identifying the exact binary that produced it:
Server: Chappe/0.1.0-SNAPSHOT+8d670fb0 (2026-05-09T19:43:04Z)
X-Chappe-Build: Chappe/0.1.0-SNAPSHOT+8d670fb0 (2026-05-09T19:43:04Z)
Format: Chappe/<Maven version>+<short Git hash>(<build timestamp ISO-8601 UTC>). Read once at startup via BuildInfo (see public API).
-
Serveris the standard header. It can be rewritten by an upstream reverse proxy (NPM, openresty, Cloudflare). -
X-Chappe-Buildmirrors the same value in a non-standard header that proxies usually forward untouched — useful to identify the binary behind a proxy that sets its ownServer.
Idempotency: if the application has already set either header on the Response, Chappe will not overwrite it.
Access log
Enabled via --access-log (CLI), CHAPPE_ACCESS_LOG=true (env), or logging.access-log: true (YAML). CLI overrides env, env overrides YAML.
Format: extended Apache Combined Log Format on System.out (captured by Docker/Portainer):
1.2.3.4 - yann.blazart@gmail.com [09/May/2026:18:50:54 +0000] "GET /a.png HTTP/1.1" 200 877719 12ms
Field resolution:
-
Client IP:
X-Forwarded-For(first hop) →X-Real-IP→Request.remoteAddress()→-. -
User:
X-Forwarded-User→Gap-Auth(oauth2-proxy) →-. -
Size:
Response.body().contentLength()(or-for streamed/chunked bodies). -
Duration: nanoseconds measured by the outermost filter wrapper, captures the final status even if the handler throws.
Available as a public API filter via Filter.accessLog() / Filter.accessLog(Consumer<String>); the CLI launcher applies it as the outermost wrapper.
Maven plugin: pre-compression
<plugin>
<groupId>io.vidocq.chappe</groupId>
<artifactId>chappe-static-index-maven-plugin</artifactId>
<executions>
<execution>
<goals><goal>index</goal></goals>
<configuration>
<compress>gzip</compress> <!-- default: none -->
<compressThreshold>1024</compressThreshold>
</configuration>
</execution>
</executions>
</plugin>
Generates:
-
META-INF/chappe-static-index.properties— O(1) index (path → size + ETag). -
.gzsidecars for files above the threshold.
Served zero-copy by StaticFileHandler.preferPrecompressed(true).
HTTP capabilities
| Spec | Status | Notes |
|---|---|---|
RFC 9110 (HTTP Semantics) |
OK |
Date, HEAD/204/304, 405+Allow, Host, 100-continue |
RFC 9112 (HTTP/1.1) |
OK |
request parsing, chunked, keep-alive, pipelining |
RFC 9113 (HTTP/2) |
OK |
framing, flow control, lifecycle, GOAWAY, SETTINGS, CONTINUATION |
RFC 7541 (HPACK) |
OK |
static table, dynamic table, Huffman encode/decode |
RFC 9114 (HTTP/3) |
planned |
see lien:https://codeberg.org/Vidocq/chappe/blob/main/BUG.md[BUG.md] |
RFC 8441 (WebSocket H2) |
planned |
tracked in BUG.md |
Compatibility
-
Java: 25 (LTS), no preview flag required (
ScopedValueis final since JEP 506). -
Maven: 3.9.16 (POM
modelVersion 4.0.0). -
JPMS strict: no classpath, no
--add-opens.
Bugs and benchmarks
-
lien:https://codeberg.org/Vidocq/chappe/blob/main/BUG.md[BUG.md] — reproducible bugs, status, minimal repro.
-
lien:https://codeberg.org/Vidocq/chappe/blob/main/BENCH.md[BENCH.md] — performance numbers, hardware, JVM, command.
Source
All code and roadmap live at https://codeberg.org/Vidocq/chappe.