CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/maven-io-helidon-webserver--helidon-webserver

High-performance HTTP server implementation built on Java 21 Virtual Threads for Helidon microservices framework

Pending
Overview
Eval results
Files

spi.mddocs/

Service Provider Interface (SPI)

Extension points for custom protocols, features, connection handling, and streaming capabilities. Enables third-party integrations and custom server functionality through well-defined service provider interfaces.

Capabilities

Server Features SPI

Service provider interface for extending server functionality with custom features.

/**
 * SPI for server features and extensions.
 */
interface ServerFeature {
    /**
     * Setup the server feature.
     * @param serverContext server context for configuration
     */
    void setup(ServerFeatureContext serverContext);
    
    /**
     * Get feature name.
     * @return feature name
     */
    String name();
    
    /**
     * Get feature weight for ordering.
     * @return feature weight (higher weight = later initialization)
     */
    default double weight() {
        return 100.0;
    }
    
    /**
     * Called before server starts.
     * @param server the server instance
     */
    default void beforeStart(WebServer server) {
        // Default implementation does nothing
    }
    
    /**
     * Called after server starts.
     * @param server the server instance
     */
    default void afterStart(WebServer server) {
        // Default implementation does nothing
    }
    
    /**
     * Called before server stops.
     * @param server the server instance
     */
    default void beforeStop(WebServer server) {
        // Default implementation does nothing
    }
    
    /**
     * Called after server stops.
     * @param server the server instance
     */
    default void afterStop(WebServer server) {
        // Default implementation does nothing
    }
}
/**
 * Provider for server features.
 */
interface ServerFeatureProvider {
    /**
     * Create server feature instance.
     * @param config feature configuration
     * @return server feature instance
     */
    ServerFeature create(Config config);
    
    /**
     * Get configuration type supported by this provider.
     * @return configuration class
     */
    Class<? extends Config> configType();
    
    /**
     * Get provider name.
     * @return provider name
     */
    String name();
}

Usage Examples:

// Custom metrics feature
public class MetricsServerFeature implements ServerFeature {
    private final MeterRegistry meterRegistry;
    private Timer requestTimer;
    
    public MetricsServerFeature(MeterRegistry meterRegistry) {
        this.meterRegistry = meterRegistry;
    }
    
    @Override
    public void setup(ServerFeatureContext context) {
        this.requestTimer = Timer.builder("http.requests")
            .description("HTTP request duration")
            .register(meterRegistry);
        
        // Add metrics filter to all HTTP routing
        context.routing(HttpRouting.class, routing -> {
            routing.addFilter(this::metricsFilter);
        });
    }
    
    private void metricsFilter(FilterChain chain, RoutingRequest req, RoutingResponse res) {
        Timer.Sample sample = Timer.start(meterRegistry);
        try {
            chain.proceed();
        } finally {
            sample.stop(requestTimer
                .tag("method", req.method().text())
                .tag("status", String.valueOf(res.status().code())));
        }
    }
    
    @Override
    public String name() {
        return "metrics";
    }
    
    @Override
    public double weight() {
        return 50.0; // Early in the chain
    }
}

// Feature provider
public class MetricsFeatureProvider implements ServerFeatureProvider {
    @Override
    public ServerFeature create(Config config) {
        MeterRegistry registry = createMeterRegistry(config);
        return new MetricsServerFeature(registry);
    }
    
    @Override
    public Class<? extends Config> configType() {
        return MetricsConfig.class;
    }
    
    @Override
    public String name() {
        return "metrics";
    }
}

Protocol Configuration SPI

Service provider interface for custom protocol implementations.

/**
 * Configuration for protocols.
 */
interface ProtocolConfig {
    /**
     * Get protocol name.
     * @return protocol name
     */
    String protocolName();
    
    /**
     * Get protocol version.
     * @return protocol version
     */
    String protocolVersion();
    
    /**
     * Get protocol configuration type.
     * @return configuration type
     */
    String configType();
    
    /**
     * Get protocol weight for ordering.
     * @return protocol weight
     */
    default double weight() {
        return 100.0;
    }
}
/**
 * Provider for protocol configurations.
 */
interface ProtocolConfigProvider {
    /**
     * Get configuration type supported by this provider.
     * @return configuration class
     */
    Class<? extends ProtocolConfig> configType();
    
    /**
     * Create protocol configuration from generic config.
     * @param config configuration source
     * @return protocol configuration
     */
    ProtocolConfig create(Config config);
    
    /**
     * Get protocol type name.
     * @return protocol type
     */
    String protocolType();
}

Usage Examples:

// Custom HTTP/2 protocol configuration
public class Http2Config implements ProtocolConfig {
    private final int maxConcurrentStreams;
    private final int initialWindowSize;
    private final boolean enablePush;
    
    private Http2Config(Builder builder) {
        this.maxConcurrentStreams = builder.maxConcurrentStreams;
        this.initialWindowSize = builder.initialWindowSize;
        this.enablePush = builder.enablePush;
    }
    
    @Override
    public String protocolName() {
        return "HTTP";
    }
    
    @Override
    public String protocolVersion() {
        return "2.0";
    }
    
    @Override
    public String configType() {
        return "http2";
    }
    
    public int maxConcurrentStreams() { return maxConcurrentStreams; }
    public int initialWindowSize() { return initialWindowSize; }
    public boolean enablePush() { return enablePush; }
    
    public static Builder builder() { return new Builder(); }
    
    public static class Builder {
        private int maxConcurrentStreams = 100;
        private int initialWindowSize = 65535;
        private boolean enablePush = true;
        
        public Builder maxConcurrentStreams(int max) {
            this.maxConcurrentStreams = max;
            return this;
        }
        
        public Builder initialWindowSize(int size) {
            this.initialWindowSize = size;
            return this;
        }
        
        public Builder enablePush(boolean enable) {
            this.enablePush = enable;
            return this;
        }
        
        public Http2Config build() {
            return new Http2Config(this);
        }
    }
}

// HTTP/2 protocol provider
public class Http2ProtocolProvider implements ProtocolConfigProvider {
    @Override
    public Class<? extends ProtocolConfig> configType() {
        return Http2Config.class;
    }
    
    @Override
    public ProtocolConfig create(Config config) {
        return Http2Config.builder()
            .maxConcurrentStreams(config.get("max-concurrent-streams").asInt().orElse(100))
            .initialWindowSize(config.get("initial-window-size").asInt().orElse(65535))
            .enablePush(config.get("enable-push").asBoolean().orElse(true))
            .build();
    }
    
    @Override
    public String protocolType() {
        return "http2";
    }
}

Connection Management SPI

Service provider interfaces for custom connection handling and selection.

/**
 * Interface for server connections.
 */
interface ServerConnection {
    /**
     * Get connection channel.
     * @return connection channel
     */
    SocketChannel channel();
    
    /**
     * Get connection context.
     * @return connection context
     */
    ConnectionContext context();
    
    /**
     * Check if connection is secure.
     * @return true if secure (TLS)
     */
    boolean isSecure();
    
    /**
     * Get local address.
     * @return local socket address
     */
    SocketAddress localAddress();
    
    /**
     * Get remote address.
     * @return remote socket address
     */
    SocketAddress remoteAddress();
    
    /**
     * Close the connection.
     */
    void close();
    
    /**
     * Check if connection is active.
     * @return true if connection is active
     */
    boolean isActive();
    
    /**
     * Handle connection processing.
     */
    void handle();
    
    /**
     * Get connection protocol.
     * @return protocol name
     */
    String protocol();
}
/**
 * Selector for server connections.
 */
interface ServerConnectionSelector {
    /**
     * Select appropriate connection for request.
     * @param context connection context
     * @return selected connection
     */
    ServerConnection select(ConnectionContext context);
    
    /**
     * Release connection after use.
     * @param connection connection to release
     */
    void release(ServerConnection connection);
    
    /**
     * Close all connections managed by this selector.
     */
    void closeAll();
    
    /**
     * Get selector statistics.
     * @return connection statistics
     */
    ConnectionStats statistics();
}
/**
 * Provider for connection selectors.
 */
interface ServerConnectionSelectorProvider {
    /**
     * Create connection selector.
     * @param config selector configuration
     * @return connection selector
     */
    ServerConnectionSelector create(ProtocolConfig config);
    
    /**
     * Get provider name.
     * @return provider name
     */
    String name();
    
    /**
     * Get configuration type.
     * @return configuration class
     */
    Class<? extends ProtocolConfig> configType();
    
    /**
     * Check if this provider supports the given protocol.
     * @param protocol protocol name
     * @return true if supported
     */
    boolean supports(String protocol);
}

Usage Examples:

// Custom WebSocket connection implementation
public class WebSocketConnection implements ServerConnection {
    private final SocketChannel channel;
    private final ConnectionContext context;
    private final WebSocketHandler handler;
    private volatile boolean active = true;
    
    public WebSocketConnection(SocketChannel channel, ConnectionContext context, 
                              WebSocketHandler handler) {
        this.channel = channel;
        this.context = context;
        this.handler = handler;
    }
    
    @Override
    public void handle() {
        try {
            handler.onConnect(this);
            processWebSocketFrames();
        } catch (Exception e) {
            handler.onError(this, e);
        } finally {
            handler.onClose(this);
            active = false;
        }
    }
    
    @Override
    public String protocol() {
        return "websocket";
    }
    
    // Implementation of other ServerConnection methods...
}

// WebSocket connection selector
public class WebSocketConnectionSelector implements ServerConnectionSelector {
    private final Set<WebSocketConnection> activeConnections = ConcurrentHashMap.newKeySet();
    
    @Override
    public ServerConnection select(ConnectionContext context) {
        WebSocketConnection connection = new WebSocketConnection(
            context.channel(), context, new DefaultWebSocketHandler());
        activeConnections.add(connection);
        return connection;
    }
    
    @Override
    public void release(ServerConnection connection) {
        if (connection instanceof WebSocketConnection wsConn) {
            activeConnections.remove(wsConn);
        }
    }
    
    @Override
    public void closeAll() {
        activeConnections.forEach(ServerConnection::close);
        activeConnections.clear();
    }
}

HTTP Streaming SPI

Service provider interface for custom streaming response handlers.

/**
 * SPI for handling streaming responses.
 */
interface Sink {
    /**
     * Write data to sink.
     * @param data data to write
     */
    void writeData(DataChunk data);
    
    /**
     * Signal completion of stream.
     */
    void complete();
    
    /**
     * Signal error in stream.
     * @param throwable error that occurred
     */
    void error(Throwable throwable);
    
    /**
     * Check if sink is closed.
     * @return true if sink is closed
     */
    boolean isClosed();
    
    /**
     * Close the sink.
     */
    void close();
    
    /**
     * Get sink type.
     * @return sink type identifier
     */
    String type();
}
/**
 * Provider for sink implementations.
 */
interface SinkProvider {
    /**
     * Create sink for media type.
     * @param context sink provider context
     * @return sink instance
     */
    Sink create(SinkProviderContext context);
    
    /**
     * Check if this provider supports the media type.
     * @param mediaType media type to check
     * @return true if supported
     */
    boolean supports(MediaType mediaType);
    
    /**
     * Get provider name.
     * @return provider name
     */
    String name();
    
    /**
     * Get provider weight for ordering.
     * @return provider weight
     */
    default double weight() {
        return 100.0;
    }
}
/**
 * Context for sink providers.
 */
interface SinkProviderContext {
    /**
     * Get target media type.
     * @return media type
     */
    MediaType mediaType();
    
    /**
     * Get response headers.
     * @return response headers
     */
    WritableHeaders<?> headers();
    
    /**
     * Get response output stream.
     * @return output stream
     */
    OutputStream outputStream();
    
    /**
     * Get server response.
     * @return server response
     */
    ServerResponse response();
    
    /**
     * Get request context.
     * @return request context
     */
    Context context();
}

Usage Examples:

// Custom JSON streaming sink
public class JsonStreamingSink implements Sink {
    private final OutputStream outputStream;
    private final ObjectMapper objectMapper;
    private boolean closed = false;
    private boolean firstWrite = true;
    
    public JsonStreamingSink(OutputStream outputStream, ObjectMapper objectMapper) {
        this.outputStream = outputStream;
        this.objectMapper = objectMapper;
        try {
            outputStream.write('['); // Start JSON array
        } catch (IOException e) {
            throw new RuntimeException("Failed to initialize JSON stream", e);
        }
    }
    
    @Override
    public void writeData(DataChunk data) {
        if (closed) throw new IllegalStateException("Sink is closed");
        
        try {
            if (!firstWrite) {
                outputStream.write(',');
            }
            firstWrite = false;
            
            objectMapper.writeValue(outputStream, data.data());
            outputStream.flush();
        } catch (IOException e) {
            error(e);
        }
    }
    
    @Override
    public void complete() {
        if (!closed) {
            try {
                outputStream.write(']'); // End JSON array
                outputStream.flush();
            } catch (IOException e) {
                // Log error but don't throw
            } finally {
                close();
            }
        }
    }
    
    @Override
    public String type() {
        return "json-streaming";
    }
    
    // Implementation of other Sink methods...
}

// JSON streaming sink provider
public class JsonStreamingSinkProvider implements SinkProvider {
    private final ObjectMapper objectMapper = new ObjectMapper();
    
    @Override
    public Sink create(SinkProviderContext context) {
        context.headers().contentType(MediaType.APPLICATION_JSON);
        context.headers().set("Transfer-Encoding", "chunked");
        
        return new JsonStreamingSink(context.outputStream(), objectMapper);
    }
    
    @Override
    public boolean supports(MediaType mediaType) {
        return MediaType.APPLICATION_JSON.test(mediaType);
    }
    
    @Override
    public String name() {
        return "json-streaming";
    }
    
    @Override
    public double weight() {
        return 200.0; // Higher priority than default
    }
}

HTTP/1.1 Upgrade SPI

Service provider interface for HTTP/1.1 protocol upgrades (WebSocket, HTTP/2, etc.).

/**
 * SPI for HTTP/1.1 protocol upgrades.
 */
interface Http1Upgrader {
    /**
     * Check if this upgrader supports the requested protocol.
     * @param protocol protocol name from Upgrade header
     * @return true if supported
     */
    boolean supports(String protocol);
    
    /**
     * Perform protocol upgrade.
     * @param request HTTP/1.1 request with upgrade headers
     * @param response HTTP/1.1 response for upgrade response
     * @param connection underlying connection
     * @return upgraded connection handler
     */
    UpgradeResult upgrade(Http1ServerRequest request, 
                         Http1ServerResponse response,
                         Http1Connection connection);
    
    /**
     * Get upgrade protocol name.
     * @return protocol name
     */
    String protocolName();
    
    /**
     * Get upgrader weight for selection priority.
     * @return upgrader weight
     */
    default double weight() {
        return 100.0;
    }
}
/**
 * Provider for HTTP/1.1 upgrade implementations.
 */
interface Http1UpgradeProvider {
    /**
     * Create upgrader instance.
     * @param config upgrade configuration
     * @return upgrader instance
     */
    Http1Upgrader create(Config config);
    
    /**
     * Get supported protocol name.
     * @return protocol name
     */
    String protocolName();
    
    /**
     * Get provider name.
     * @return provider name
     */
    String name();
}

Usage Examples:

// WebSocket upgrade implementation
public class WebSocketUpgrader implements Http1Upgrader {
    @Override
    public boolean supports(String protocol) {
        return "websocket".equalsIgnoreCase(protocol);
    }
    
    @Override
    public UpgradeResult upgrade(Http1ServerRequest request, 
                                Http1ServerResponse response,
                                Http1Connection connection) {
        // Validate WebSocket upgrade headers
        Optional<String> key = request.headers().first("Sec-WebSocket-Key");
        Optional<String> version = request.headers().first("Sec-WebSocket-Version");
        
        if (key.isEmpty() || !"13".equals(version.orElse(""))) {
            return UpgradeResult.failed("Invalid WebSocket upgrade request");
        }
        
        // Generate WebSocket accept key
        String acceptKey = generateWebSocketAcceptKey(key.get());
        
        // Send upgrade response
        response.status(101)
                .header("Upgrade", "websocket")
                .header("Connection", "Upgrade")
                .header("Sec-WebSocket-Accept", acceptKey)
                .send();
        
        // Return upgraded connection handler
        return UpgradeResult.success(new WebSocketConnectionHandler(connection));
    }
    
    @Override
    public String protocolName() {
        return "websocket";
    }
    
    private String generateWebSocketAcceptKey(String key) {
        // WebSocket key generation logic
        String magic = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11";
        return Base64.getEncoder().encodeToString(
            MessageDigest.getInstance("SHA-1")
                .digest((key + magic).getBytes()));
    }
}

// WebSocket upgrade provider
public class WebSocketUpgradeProvider implements Http1UpgradeProvider {
    @Override
    public Http1Upgrader create(Config config) {
        return new WebSocketUpgrader();
    }
    
    @Override
    public String protocolName() {
        return "websocket";
    }
    
    @Override
    public String name() {
        return "websocket-upgrade";
    }
}

SPI Registration and Discovery

Service Loading

Helidon WebServer uses Java's ServiceLoader mechanism to discover SPI implementations:

// META-INF/services/io.helidon.webserver.spi.ServerFeatureProvider
com.example.MetricsFeatureProvider
com.example.TracingFeatureProvider

// META-INF/services/io.helidon.webserver.spi.ProtocolConfigProvider
com.example.Http2ProtocolProvider
com.example.GrpcProtocolProvider

// META-INF/services/io.helidon.webserver.http.spi.SinkProvider
com.example.JsonStreamingSinkProvider
com.example.XmlStreamingSinkProvider

// META-INF/services/io.helidon.webserver.http1.spi.Http1UpgradeProvider
com.example.WebSocketUpgradeProvider
com.example.Http2UpgradeProvider

Custom SPI Implementation

// Example: Custom authentication feature
@AutoService(ServerFeatureProvider.class)
public class AuthFeatureProvider implements ServerFeatureProvider {
    
    @Override
    public ServerFeature create(Config config) {
        return new AuthServerFeature(
            config.get("auth.jwt-secret").asString().orElse("default-secret"),
            config.get("auth.token-expiry").as(Duration.class).orElse(Duration.ofHours(1))
        );
    }
    
    @Override
    public Class<? extends Config> configType() {
        return AuthConfig.class;
    }
    
    @Override
    public String name() {
        return "authentication";
    }
}

// Usage in server configuration
WebServerConfig serverConfig = WebServerConfig.builder()
    .port(8080)
    .routing(HttpRouting.builder()
        .get("/secure", (req, res) -> {
            // This will be protected by the auth feature
            res.send("Secure content");
        })
        .build())
    .build();

// The auth feature is automatically loaded and applied
WebServer server = WebServer.create(serverConfig).start();

Install with Tessl CLI

npx tessl i tessl/maven-io-helidon-webserver--helidon-webserver

docs

configuration.md

http-routing.md

http-services.md

http1-protocol.md

index.md

request-response.md

server-management.md

spi.md

tile.json