Gumdrop

Gumdrop HTTP Server & Client

Gumdrop provides a comprehensive HTTP implementation supporting HTTP/3, HTTP/2, and HTTP/1.x. The HTTP layer serves as the foundation for higher-level services including the servlet container and file server.

Contents

HTTP Versions

Gumdrop supports HTTP/3, HTTP/2, HTTP/1.1, and HTTP/1.0, with automatic version detection and negotiation. Originally built to support HTTP/1.1 as a native protocol, Gumdrop was first adapted to use the SPDY protocol developed by Google, and then, as that was deprecated and consensus established on the newer HTTP/2 protocol, Gumdrop embraced the efficiency and extensibility of the latter. With the standardisation of HTTP/3 over QUIC, Gumdrop now supports the full spectrum of modern HTTP.

HTTP/3

HTTP/3 (RFC 9114) runs over QUIC instead of TCP. QUIC provides built-in TLS 1.3 encryption, multiplexed streams without head-of-line blocking, and faster connection establishment (often 0-RTT). HTTP/3 is configured via a separate HTTP3Listener that listens on a UDP port. See HTTP/3 and QUIC below for details.

HTTP/2

Complete HTTP/2 implementation (RFC 7540) with multiplexing and header compression. HTTP/2 can be negotiated via ALPN during TLS handshake or via cleartext upgrade (h2c). See HTTP/2 Support below for details.

HTTP/1.1

Full HTTP/1.1 support including:

HTTP/1.0

Basic HTTP support for legacy clients. Connections are closed after each request/response unless explicitly kept alive.

HTTP/3 and QUIC

HTTP/3 is the latest major version of the Hypertext Transfer Protocol. Unlike HTTP/1.1 and HTTP/2, which run over TCP, HTTP/3 uses QUIC (RFC 9000) as its transport layer. QUIC provides:

Architecture

Gumdrop’s HTTP/3 support is built on the quiche library via JNI. The QUIC layer handles connection management, stream multiplexing, and congestion control natively, while the HTTP/3 framing (HEADERS, DATA, QPACK) is processed by Gumdrop’s HTTP3ServerHandler and H3Stream classes.

From the application’s perspective, HTTP/3 is transparent. The same HTTPRequestHandlerFactory and HTTPRequestHandler interfaces are used regardless of whether the request arrived over HTTP/3, HTTP/2, or HTTP/1.1. The only visible difference is the HTTP version reported by HTTPResponseState.getVersion().

Configuration

HTTP/3 is configured by adding an HTTP3Listener to an HTTP service. Because QUIC uses UDP, the HTTP/3 listener is independent of the TCP-based HTTPListener and can coexist with it:

<service id="web" class="org.bluezoo.gumdrop.servlet.ServletService">
  <!-- HTTP/1.1 and HTTP/2 over TCP -->
  <listener class="org.bluezoo.gumdrop.http.HTTPListener">
    <property name="port">443</property>
    <property name="secure">true</property>
    <property name="keystore-file">keystore.p12</property>
    <property name="keystore-pass">changeit</property>
  </listener>
  <!-- HTTP/3 over QUIC -->
  <listener class="org.bluezoo.gumdrop.http.h3.HTTP3Listener">
    <property name="port">443</property>
    <property name="cert-file">/etc/gumdrop/cert.pem</property>
    <property name="key-file">/etc/gumdrop/key.pem</property>
  </listener>
</service>

Note that the TCP and QUIC listeners can share the same port number (443 is conventional) because they use different transport protocols (TCP vs UDP).

PEM vs Keystore

QUIC uses BoringSSL (via the quiche native library) rather than the JDK’s JSSE, so HTTP/3 listeners require PEM-encoded certificates and keys rather than a Java keystore:

The TCP-based HTTPListener continues to use the standard Java keystore properties (keystore-file, keystore-pass, keystore-format). If you have a PKCS#12 keystore, you can extract PEM files with OpenSSL:

openssl pkcs12 -in keystore.p12 -nokeys -out cert.pem
openssl pkcs12 -in keystore.p12 -nocerts -nodes -out key.pem

Alt-Svc Discovery

When an HTTPService has both TCP and QUIC listeners, it automatically computes an Alt-Svc response header and injects it into HTTP/1.1 and HTTP/2 responses served by the TCP listener. This tells clients that HTTP/3 is available:

alt-svc: h3=":443"

Clients that support HTTP/3 (all major browsers, curl, etc.) will attempt to use QUIC on subsequent requests. If the QUIC connection fails or is not supported by the network, clients fall back to HTTP/2 or HTTP/1.1 transparently.

Protocol Compliance

The server validates incoming requests per RFC 9114:

On the client side:

HTTP3Listener Properties

HTTP/2 Support

Gumdrop's HTTP/2 implementation provides all mandatory features of RFC 7540 plus several optional features.

Frame Types

All HTTP/2 frame types are supported:

HPACK Header Compression

Gumdrop includes a complete HPACK implementation (RFC 7541) for HTTP/2 header compression:

The HPACK encoder and decoder maintain separate dynamic tables for each direction of communication, with configurable maximum table sizes.

Frame Padding

For security-sensitive applications, HTTP/2 frame padding can be enabled to obscure message sizes. Padding adds random-length padding to DATA, HEADERS, and PUSH_PROMISE frames (RFC 7540 Section 6.1).

<service class="org.bluezoo.gumdrop.http.HTTPService">
  <listener class="org.bluezoo.gumdrop.http.HTTPListener">
    <property name="port">443</property>
    <property name="secure">true</property>
    <property name="frame-padding">64</property>
  </listener>
</service>

Connection Management (HTTP/2)

Server Handler API

Gumdrop provides an event-driven, non-blocking API for building HTTP servers. The API abstracts away HTTP version details (HTTP/3, HTTP/2, and HTTP/1.1), allowing handlers to focus on application logic while the framework handles framing, header compression, flow control, and connection management.

Core Interfaces

The server handler API consists of three main interfaces:

Event Sequence

Request handlers receive events in a well-defined order:

headers()              // request headers (:method, :path, etc.)
startRequestBody()     // if request has a body
requestBodyContent()   // one or more body chunks
endRequestBody()       // body complete
headers()              // trailer headers (if any)
requestComplete()      // stream closed from client

For requests without a body (GET, HEAD, etc.):

headers()              // request headers with END_STREAM
requestComplete()

Sending Responses

The HTTPResponseState interface provides methods for building responses:

headers()              // response headers (includes :status)
startResponseBody()    // if response has a body
responseBodyContent()  // one or more body chunks
endResponseBody()      // body complete
headers()              // trailer headers (optional)
complete()             // finish the response

Example: Hello World Handler

public class HelloHandler extends DefaultHTTPRequestHandler {
    
    @Override
    public void headers(HTTPResponseState state, Headers headers) {
        if ("GET".equals(headers.getMethod())) {
            Headers response = new Headers();
            response.status(HTTPStatus.OK);
            response.add("content-type", "text/plain");
            state.headers(response);
            state.startResponseBody();
            state.responseBodyContent(
                ByteBuffer.wrap("Hello, World!".getBytes()));
            state.endResponseBody();
            state.complete();
        }
    }
}

Request Routing with Factory

The factory receives initial headers before the handler is created, enabling routing decisions based on path, method, or other criteria:

public class MyFactory implements HTTPRequestHandlerFactory {
    
    public HTTPRequestHandler createHandler(HTTPResponseState state,
                                            Headers headers) {
        String path = headers.getPath();
        
        if (path.startsWith("/api/")) {
            return new ApiHandler();
        } else if (path.startsWith("/static/")) {
            return new StaticFileHandler(documentRoot);
        } else {
            return new DefaultHandler();
        }
    }
}

Programmatic Setup

Set the handler factory on the service:

HTTPService service = new HTTPService();
service.setHandlerFactory(new MyFactory());

HTTPListener listener = new HTTPListener();
listener.setPort(8080);
service.addListener(listener);
service.start();

Authentication

If a Realm is configured on the service, authentication is performed automatically before the factory is called. The authenticated principal is available via HTTPResponseState.getPrincipal().

Alternatively, the factory can perform authentication manually:

public HTTPRequestHandler createHandler(HTTPResponseState state,
                                        Headers headers) {
    String auth = headers.getValue("authorization");
    if (!isValidAuth(auth)) {
        Headers response = new Headers();
        response.status(HTTPStatus.UNAUTHORIZED);
        response.add("www-authenticate", "Bearer");
        state.headers(response);
        state.complete();
        return null;  // no handler needed
    }
    return new MyHandler();
}

HTTP/2 Server Push

For HTTP/2 connections, handlers can initiate server push to proactively send resources to the client:

@Override
public void headers(HTTPResponseState state, Headers headers) {
    // Push a stylesheet that we know the client will need
    Headers pushHeaders = new Headers();
    pushHeaders.add(":method", "GET");
    pushHeaders.add(":path", "/styles/main.css");
    pushHeaders.add(":scheme", state.getScheme());
    pushHeaders.add(":authority", headers.getValue(":authority"));
    
    if (state.pushPromise(pushHeaders)) {
        // Push was accepted; the pushed stream will be processed
        // through the factory like any other request
    }
    
    // Continue with the normal response...
}

Server push returns false for HTTP/1.x connections or if the client has disabled push via settings.

Endpoint Information

The HTTPResponseState provides access to endpoint details:

Streaming Responses

For server-sent events or other streaming scenarios:

public class StreamHandler extends DefaultHTTPRequestHandler {
    
    @Override
    public void headers(HTTPResponseState state, Headers headers) {
        Headers response = new Headers();
        response.status(HTTPStatus.OK);
        response.add("content-type", "text/event-stream");
        state.headers(response);
        state.startResponseBody();
        
        // Keep reference to state for async sends
        eventEmitter.subscribe(event -> {
            state.responseBodyContent(
                ByteBuffer.wrap(event.toSSE().getBytes()));
        });
    }
}

Flow Control and Backpressure

When a handler produces response data faster than the network can deliver it, or receives request body data faster than it can process it, backpressure prevents unbounded buffering. HTTPResponseState provides three methods that work uniformly across HTTP/1.1, HTTP/2, and HTTP/3:

// Streaming a large file with write-side backpressure
state.startResponseBody();
sendNextChunk(state, channel);

void sendNextChunk(HTTPResponseState state, ReadableByteChannel src) {
    ByteBuffer buf = ByteBuffer.allocate(8192);
    int n = src.read(buf);
    if (n > 0) {
        buf.flip();
        state.responseBodyContent(buf);
        state.onWritable(() -> sendNextChunk(state, src));
    } else {
        state.endResponseBody();
        state.complete();
    }
}
// Throttling request body consumption with read-side backpressure
public void requestBodyContent(HTTPResponseState state, ByteBuffer data) {
    if (!processor.ready()) {
        state.pauseRequestBody();
        processor.onReady(() -> state.resumeRequestBody());
        return;
    }
    processor.consume(data);
}

For HTTP/2 and HTTP/3, backpressure is per-stream: pausing one stream does not affect other streams on the same connection. For HTTP/1.1, it operates at the connection level via TCP flow control.

Error Handling

Use cancel() to abort a stream on error:

@Override
public void requestBodyContent(HTTPResponseState state, ByteBuffer data) {
    if (data.remaining() > MAX_BODY_SIZE) {
        Headers response = new Headers();
        response.status(HTTPStatus.REQUEST_ENTITY_TOO_LARGE);
        state.headers(response);
        state.complete();
        state.cancel();  // abort the stream
    }
}

Protocol Upgrades

HTTP/2 Cleartext Upgrade (h2c)

For non-TLS connections, HTTP/2 can be negotiated via the HTTP/1.1 Upgrade mechanism. The client sends:

GET / HTTP/1.1
Host: example.com
Connection: Upgrade, HTTP2-Settings
Upgrade: h2c
HTTP2-Settings: AAMAAABkAARAAAAAAAIAAAAA

If the server supports HTTP/2, it responds with 101 Switching Protocols and both sides switch to HTTP/2 framing:

HTTP/1.1 101 Switching Protocols
Connection: Upgrade
Upgrade: h2c

The HTTP2-Settings header contains base64url-encoded SETTINGS frame payload, allowing the client to communicate initial settings before the upgrade completes.

Connection Management

Gumdrop provides several mechanisms for managing HTTP/1.1 persistent connections efficiently (RFC 9112 section 9):

Idle Connection Timeout

The idle-timeout-ms property configures how long a persistent connection can remain idle before the server closes it (RFC 9112 section 9.8). The timer resets each time data is received. A value of 0 disables the timeout.

Graceful Shutdown

The max-requests-per-connection property limits how many requests are served on a single persistent connection. When the limit is reached, the server adds a Connection: close header to the response, signalling the client to close the connection after the current exchange (RFC 9112 section 9.6). A value of 0 means unlimited.

Expect: 100-continue

When a client sends an Expect: 100-continue header, the server sends an interim 100 Continue response before the client transmits the request body. This avoids wasting bandwidth on large uploads the server would ultimately reject (RFC 9110 section 10.1.1).

OPTIONS * and TRACE

The server handles OPTIONS * requests directly, returning a 200 OK with an Allow header listing the supported methods (RFC 9110 section 9.3.7). The TRACE method is supported but disabled by default for security; set trace-method-enabled to true to enable it (RFC 9110 section 9.3.8).

Configuration

HTTPService Properties

HTTP services (ServletService, WebDAVService, etc.) support the following service-level properties:

HTTPListener Properties

HTTPListener is the TCP listener for HTTP/1.1 and HTTP/2. It inherits standard listener properties and adds HTTP-specific options:

HTTP3Listener Properties

HTTP3Listener is the QUIC listener for HTTP/3. Because QUIC uses BoringSSL rather than the JDK TLS stack, certificate configuration uses PEM files instead of a keystore:

Combined HTTP/3 + HTTP/2 + HTTP/1.1

A typical production configuration exposes both TCP (HTTP/1.1, HTTP/2) and QUIC (HTTP/3) on the same port number. The TCP listener handles traditional connections while the QUIC listener handles HTTP/3 connections on UDP. The service automatically sets the Alt-Svc header on TCP responses so that clients can discover and migrate to HTTP/3:

<service id="web" class="org.bluezoo.gumdrop.servlet.ServletService">
  <listener class="org.bluezoo.gumdrop.http.HTTPListener">
    <property name="port">443</property>
    <property name="secure">true</property>
    <property name="keystore-file">keystore.p12</property>
    <property name="keystore-pass">changeit</property>
  </listener>
  <listener class="org.bluezoo.gumdrop.http.h3.HTTP3Listener">
    <property name="port">443</property>
    <property name="cert-file">cert.pem</property>
    <property name="key-file">key.pem</property>
  </listener>
</service>

Authentication

Gumdrop supports HTTP authentication methods:

Authentication is configured via an HTTPAuthenticationProvider which can be shared across multiple services or customised per service. It is integrated into the centralised org.bluezoo.gumdrop.auth.Realm-based facility for managing authentication and authorisation across all services running in the framework.

HTTP Client

Gumdrop includes a non-blocking HTTP client that shares the same event-driven architecture as the server. The client supports HTTP/3, HTTP/2, and HTTP/1.1 with automatic protocol negotiation.

SelectorLoop Affinity

When an HTTP client connection is initiated by a Gumdrop server connection, it can be assigned to the same Gumdrop worker SelectorLoop as is used by used by the server connection. This provides several advantages:

This makes the HTTP client particularly well-suited for server-side use cases such as proxying, service-to-service communication, and telemetry export where the client operates within the context of an existing Gumdrop server.

The existing OpenTelemetry implementation within Gumdrop uses this exact mechanism for efficiency, and server processes that need to call REST endpoints, for instance, can easily benefit from high performance asynchronous non-blocking connections to them via this feature.

Features

Connection Pooling

setConnectionPool(ClientEndpointPool) enables endpoint reuse with SelectorLoop affinity. Pool targets are keyed by (host, port, secure, loop). When a pooled connection is available, the client skips the connect/TLS handshake and reuses the existing endpoint.

Certificate Pinning

setTrustManager(X509TrustManager) configures a custom trust manager for outbound TLS. Use PinnedCertTrustManager for SHA-256 fingerprint pinning.

Alt-Svc Discovery

setAltSvcEnabled(true) enables parsing of Alt-Svc response headers. The client can automatically upgrade to HTTP/3 when the server advertises QUIC availability.

Usage (HTTP/2 and HTTP/1.1)

HTTPClient client = new HTTPClient("example.com", 443);
client.setSecure(true);

client.connect(new HTTPClientHandler() {
    public void onConnected(Endpoint endpoint) {
        HTTPRequest request = client.get("/api/data");
        request.header("Accept", "application/json");
        request.send(new DefaultHTTPResponseHandler() {
            public void ok(HTTPResponse response) {
                System.out.println("Status: " + response.getStatus());
            }
            public void responseBodyContent(ByteBuffer data) {
                // Process response body
            }
            public void close() {
                System.out.println("Response complete");
                client.close();
            }
            public void failed(Exception ex) {
                ex.printStackTrace();
            }
        });
    }
    public void onSecurityEstablished(SecurityInfo info) { }
    public void onError(Exception cause) { cause.printStackTrace(); }
    public void onDisconnected() { }
});

Usage (HTTP/3)

To use HTTP/3, enable it on the client before connecting. The client uses QUIC with ALPN “h3” instead of TCP:

HTTPClient client = new HTTPClient("example.com", 443);
client.setH3Enabled(true);

client.connect(new HTTPClientHandler() {
    public void onConnected(Endpoint endpoint) {
        HTTPRequest request = client.get("/api/data");
        request.header("Accept", "application/json");
        request.send(responseHandler);
    }
    public void onSecurityEstablished(SecurityInfo info) { }
    public void onError(Exception cause) { cause.printStackTrace(); }
    public void onDisconnected() { }
});

The request and response APIs are identical regardless of the underlying protocol version. The only difference is how the client is configured before connecting.

All handler callbacks are invoked on the client connection’s assigned worker thread. Long-running operations within handlers should be offloaded to avoid blocking the SelectorLoop.


← Back to Main Page | File Server & WebDAV | WebSocket Service & Client | SMTP Server & Client | IMAP Server | POP3 Server | DNS Server | Telemetry

Gumdrop HTTP Server