This page documents the implementation: what happens between the moment an HTTP connection lands on Chappe and the moment a Servlet writes its response. It complements Concepts (vocabulary) and Reference (API).

Big picture: three layers, two JPMS modules

Diagram

Request sequence

Diagram

No body copy: ServletOutputStreamImpl writes straight into the Chappe Body. The ServletInputStream reads straight from the Chappe frame parser.

Threading model

  • One virtual thread per request. Chappe’s accept() already produces one VT per connection. Foy stays on that thread until service() returns.

  • No platform pool. No ExecutorService to size.

  • Async: no extra cost. request.startAsync() captures the current VT; the program may detach the response, but the container reserves no platform thread.

  • ThreadLocal is used sparingly through ScopedValue (JEP 506) on the Chappe side. Foy inherits that context without pinning.

The bridge never pin`s a VT on a Java monitor — every critical section uses `ReentrantLock or lock-free structures. See BENCH.md for the numbers.

Container lifecycle

Diagram

fireContextInitialized() and fireContextDestroyed() are exposed by FoyChappeBoot.Mounted — the caller picks the exact moment (typically right after mount / right before unmount).

CDI integration through Vauban

foy-cdi-vauban works in two phases:

  1. build-time (Vauban) — Vauban’s annotation processor generates bean producers for ServletContext, HttpServletRequest and HttpSession. No reflection, all Class-File API (JEP 484).

  2. runtime (Foy)FoyVaubanBootstrap.beanManager() returns the BeanManager injected into FoyChappeBoot. On every request, ServletDispatcher pushes the current HttpServletRequest instance into the RequestScoped context.

Benefit: @Inject HttpServletRequest req works in any Servlet or Filter, with no runtime dynamic proxy.

Implementation choices

Choice Justification

No runtime classpath scan

All @WebServlet / @WebFilter / @WebListener are known at compile time through Vauban. Startup is O(1) in the number of beans, not O(N) in the classpath.

No runtime reflection

Aligns with the Vidocq ecosystem’s principles. AOT-friendly (GraalVM, Leyden CDS).

No body copy

ServletOutputStreamImpl and ServletInputStreamImpl wire directly onto the Chappe Body.

requires io.vidocq.chappe.api in foy-core

Temporary M1 coupling — to be replaced in M2 by a FoyHttpExchange SPI inside foy-api, opening the door to other transports.

No runtime META-INF/services

Servlet SPIs (e.g. ServletContainerInitializer) are not loaded by scan; they go through the Vauban BeanManager.

Known limits

  • Cross-context dispatch (ServletContext.getContext): not implemented — see TCK: Jakarta Servlet 6.1.

  • WebSocket Servlet 6.1: // TODO@user: planned, no date.

  • JSP: not supported, not planned.

  • Form-based authentication: // TODO@user: in progress.

For comparative numbers (Tomcat, Jetty), see BENCH.md.