Contents

Overview

WebServer provides an API for creating HTTP servers. It uses virtual threads and can handle nearly unlimited concurrent requests.

Maven Coordinates

To enable WebServer add the following dependency to your project’s pom.xml (see Managing Dependencies).

<dependency>
    <groupId>io.helidon.webserver</groupId>
    <artifactId>helidon-webserver</artifactId>
</dependency>
Copied

Configuration

You can configure the WebServer either programmatically or by the Helidon configuration framework.

Configuring the WebServer in Your Code

The easiest way to configure the WebServer is in your application code.

WebServer.builder()
        .port(8080)
        .build()
        .start();
Copied

Configuring the WebServer in a Configuration File

You can also define the configuration in a file.

WebServer configuration file application.yaml
server:
  port: 8080
  host: "0.0.0.0"
Copied

Then, in your application code, load the configuration from that file.

WebServer initialization using the application.yaml file located on the classpath
Config config = Config.create(); 
WebServer.builder()
        .config(config.get("server")); 
Copied
  • application.yaml is a default configuration source loaded when YAML support is on classpath, so we can just use Config.create()
  • Server expects the configuration tree located on the node of server

Configuring TLS

Configure TLS either programmatically, or by the Helidon configuration framework.

Configuring TLS in Your Code

To configure TLS in WebServer programmatically create your keystore configuration and pass it to the WebServer builder.

Tls tls = Tls.builder()
        .privateKey(pk -> pk
                .keystore(keys -> keys.keystore(it -> it.resourcePath("private-key.p12"))
                        .passphrase("password".toCharArray())))
        .trust(trust -> trust
                .keystore(keys -> keys.keystore(it -> it.resourcePath("trust.p12"))))
        .build();

WebServer.builder()
        .tls(tls);
Copied

Configuring TLS in the Config File

It is also possible to configure TLS via the config file.

WebServer TLS configuration file application.yaml
server:
  tls:
    #Truststore setup
    trust:
      keystore:
        passphrase: "password"
        trust-store: true
        resource:
          resource-path: "keystore.p12"
    # Keystore with private key and server certificate
    private-key:
      keystore:
        passphrase: "password"
        resource:
          resource-path: "keystore.p12"
Copied

Then, in your application code, load the configuration from that file.

WebServer initialization using the application.yaml file located on the classpath
Config config = Config.create(); 
WebServer.builder()
        .config(config.get("server")); 
Copied
  • application.yaml is a default configuration source loaded when YAML support is on classpath, so we can just use Config.create()
  • Server expects the configuration tree located on the node of server

Or you can only create WebServerTls instance based on the config file.

WebServerTls instance based on application.yaml file located on the classpath
Config config = Config.create();
WebServer.builder()
        .tls(it -> it.config(config.get("server.tls")));
Copied

This can alternatively be configured with paths to PKCS#8 PEM files rather than KeyStores:

WebServer TLS configuration file application.yaml
server:
  tls:
    #Truststore setup
    trust:
      pem:
        certificates:
          resource:
            resource-path: "ca-bundle.pem"
    private-key:
      pem:
        key:
          resource:
            resource-path: "key.pem"
        cert-chain:
          resource:
            resource-path: "chain.pem"
Copied

Configuration Options

Type: io.helidon.webserver.WebServer

This is a standalone configuration type, prefix from configuration root: server

Configuration options

Optional configuration options
keytypedefault valuedescription
backlog

int

1024

Accept backlog.

@return backlog
connection-config 

Configuration of a connection (established from client against our server).

@return connection configuration
connection-options 

Options for connections accepted by this listener. This is not used to setup server connection.

@return socket options
content-encoding 

Configure the listener specific io.helidon.http.encoding.ContentEncodingContext. This method discards all previously registered ContentEncodingContext. If no content encoding context is registered, content encoding context of the webserver would be used.

@return content encoding context
features 

Server features allow customization of the server, listeners, or routings.

@return server features
host

string

0.0.0.0

Host of the default socket. Defaults to all host addresses (0.0.0.0).

@return host address to listen on (for the default socket)
idle-connection-period

Duration

PT2M

How often should we check for #idleConnectionTimeout(). Defaults to PT2M (2 minutes).

@return period of checking for idle connections
idle-connection-timeout

Duration

PT5M

How long should we wait before closing a connection that has no traffic on it. Defaults to PT5M (5 minutes). Note that the timestamp is refreshed max. once per second, so this setting would be useless if configured for shorter periods of time (also not a very good support for connection keep alive, if the connections are killed so soon anyway).

@return timeout of idle connections
max-concurrent-requests

int

-1

Limits the number of requests that can be executed at the same time (the number of active virtual threads of requests). Defaults to -1, meaning "unlimited" - what the system allows. Also make sure that this number is higher than the expected time it takes to handle a single request in your application, as otherwise you may stop in-progress requests.

@return number of requests that can be processed on this listener, regardless of protocol
max-in-memory-entity

int

131072

If the entity is expected to be smaller that this number of bytes, it would be buffered in memory to optimize performance when writing it. If bigger, streaming will be used.

Note that for some entity types we cannot use streaming, as they are already fully in memory (String, byte[]), for such
cases, this option is ignored.
Default is 128Kb.
@return maximal number of bytes to buffer in memory for supported writers
max-payload-size

long

-1

Maximal number of bytes an entity may have. If io.helidon.http.HeaderNames#CONTENT_LENGTH is used, this is checked immediately, if io.helidon.http.HeaderValues#TRANSFER_ENCODING_CHUNKED is used, we will fail when the number of bytes read would exceed the max payload size. Defaults to unlimited (-1).

@return maximal number of bytes of entity
max-tcp-connections

int

-1

Limits the number of connections that can be opened at a single point in time. Defaults to -1, meaning "unlimited" - what the system allows.

@return number of TCP connections that can be opened to this listener, regardless of protocol
media-context 

Configure the listener specific io.helidon.http.media.MediaContext. This method discards all previously registered MediaContext. If no media context is registered, media context of the webserver would be used.

@return media context
name

string

@default

Name of this socket. Defaults to @default. Must be defined if more than one socket is needed.

@return name of the socket
port

int

0

Port of the default socket. If configured to 0 (the default), server starts on a random port.

@return port to listen on (for the default socket)
protocols

io.helidon.webserver.spi.ProtocolConfig[] (service provider interface)

 

Configuration of protocols. This may be either protocol selectors, or protocol upgraders from HTTP/1.1. As the order is not important (providers are ordered by weight by default), we can use a configuration as an object, such as: <pre> protocols: providers: http_1_1: max-prologue-length: 8192 http_2: max-frame-size: 4096 websocket: …​. </pre>

@return all defined protocol configurations, loaded from service loader by default
receive-buffer-size

int

 

Listener receive buffer size.

@return buffer size in bytes
shutdown-grace-period

Duration

PT0.5S

Grace period in ISO 8601 duration format to allow running tasks to complete before listener’s shutdown. Default is 500 milliseconds. Configuration file values example: PT0.5S, PT2S.

@return grace period
shutdown-hook

boolean

true

When true the webserver registers a shutdown hook with the JVM Runtime.

Defaults to true. Set this to false such that a shutdown hook is not registered.
@return whether to register a shutdown hook
sockets 

Socket configurations. Note that socket named WebServer#DEFAULT_SOCKET_NAME cannot be used, configure the values on the server directly.

@return map of listener configurations, except for the default one
tls 

Listener TLS configuration.

@return tls of this configuration
write-buffer-size

int

512

Initial buffer size in bytes of java.io.BufferedOutputStream created internally to write data to a socket connection. Default is 512.

@return initial buffer size used for writing
write-queue-length

int

0

Number of buffers queued for write operations.

@return maximal number of queued writes, defaults to 0

Routing

Routing lets you use request matching criteria to bind requests to a handler that implements your custom business logic. Matching criteria include one or more HTTP Method(s) and, optionally, a request path matcher. Use the RequestPredicate class to specify more routing criteria.

Routing Basics

Routing also supports Error Routing which binds Java Throwable to the handling logic.

Configure HTTP request routing using HttpRouting.Builder.

Using HttpRouting.Builder to specify how HTTP requests are handled
WebServer.builder()
        .routing(it -> it
                .get("/hello", (req, res) -> res.send("Hello World!"))) 
        .build(); 
Copied
  • Handle all GETs to /hello path. Send the Hello World! string.
  • Create a server instance with the provided routing

HTTP Method Routing

HttpRouting.Builder lets you specify how to handle each HTTP method. For example:

HTTP MethodHttpRouting.Builder example
GET.get(handler)
PUT.put(handler)
POST.post(handler)
HEAD.head(handler)
DELETE.delete(handler)
TRACE.trace(handler)
OPTIONS.options(handler)
any method.any(handler)
multiple methods.route(Method.predicate(Method.GET, Method.POST), path, handler)
custom method.route(Method.create("CUSTOM"), handler)

Path Matcher Routing

You can combine HTTP method routing with request path matching.

routing.post("/some/path", (req, res) -> { /* handler */ });
Copied

You can use path pattern instead of path with the following syntax:

  • /foo/bar/baz - Exact path match against resolved path even with non-usual characters

  • /foo/* - convenience method to match /foo or any subpath (but not /foobar)

  • /foo/{}/baz - {} Unnamed regular expression segment ([^/]+)

  • /foo/{var}/baz - Named regular expression segment ([^/]+)

  • /foo/{var:\d+} - Named regular expression segment with a specified expression

  • /foo/{:\d+} - Unnamed regular expression segment with a specified expression

  • /foo/{var}` - Convenience shortcut for `{var:.}

  • /foo/{}` - Convenience shortcut for unnamed segment with regular expression `{:.}

  • /foo[/bar] - An optional block, which translates to the /foo(/bar)? regular expression

  • /* or /foo* - * Wildcard character can be matched with any number of characters.

Path (matcher) routing is exact. For example, a /foo/bar request is not routed to .post('/foo', …​).

Always start path and path patterns with the / character.

For more precise setup of path, you can use factory methods on io.helidon.http.PathMatchers and register using HttpRouting.Builder.route(Predicate<Method>, PathMatcher, Handler) method.

Using full HttpRoute

To have more control over selecting which requests should be handled by a specific route, you can use the io.helidon.webserver.http.HttpRoute interface using its Builder.

routing.route(HttpRoute.builder()
                      .path("/hello")
                      .methods(Method.POST, Method.PUT) 
                      .handler((req, res) -> {
                          String requestEntity = req.content().as(String.class);
                          res.send(requestEntity); 
                      }));
Copied
  • The route is specified for GET and POST requests
  • The handler consumes the request payload and echoes it back

Organizing Code into Services

By implementing the io.helidon.webserver.http.HttpService interface you can organize your code into one or more services, each with its own path prefix and set of handlers.

Use HttpRouting.Builder.register to register your service
routing.register("/hello", new HelloService());
Copied
Service implementation
class HelloService implements HttpService {
    @Override
    public void routing(HttpRules rules) {
        rules.get("/subpath", (req, res) -> {
            // Some logic
        });
    }
}
Copied

In this example, the GET handler matches requests to /hello/subpath.

Using HttpFeature

By implementing the io.helidon.webserver.http.HttpFeature interface, you can organize multiple routes and/or filters into a feature, that will be setup according to its defined io.helidon.common.Weight (or using io.helidon.common.Weighted).

Each service has access to the routing builder. HTTP Features are configured for each routing builder. If there is a need to configure a feature for multiple sockets, you can use Server Feature instead.

Request Handling

Implement the logic to handle requests to WebServer in a Handler, which is a FunctionalInterface. Handlers:

  • Process the request and send a response.

  • Act as a filter and forward requests to downstream handlers using the response.next() method.

  • Throw an exception to begin error handling.

Process Request and Produce Response

Each Handler has two parameters. ServerRequest and ServerResponse.

  • Request provides access to the request method, URI, path, query parameters, headers and entity.

  • Response provides an ability to set response code, headers, and entity.

Filtering

Filtering can be done either using a dedicated Filter, or through routes.

Filter

You can register a io.helidon.webserver.http.Filter with HTTP routing to handle filtering in interception style.

A simple filter example:

routing.addFilter((chain, req, res) -> {
    try {
        chain.proceed();
    } finally {
        // do something for any finished request
    }
});
Copied

Routes

The handler forwards the request to the downstream handlers by nexting. There are two options:

  • call res.next()

    rules.any("/hello", (req, res) -> { 
        // filtering logic  
        res.next(); 
    });
    Copied
    • handler for any HTTP method using the /hello path
    • business logic implementation
    • forward the current request to the downstream handler
  • throw an exception to forward to error handling

    rules.any("/hello", (req, res) -> { 
        // filtering logic (e.g., validating parameters) 
        if (userParametersOk()) {
            res.next(); 
        } else {
            throw new IllegalArgumentException("Invalid parameters."); 
        }
    });
    Copied
    • handler for any HTTP method using the /hello path
    • custom logic
    • forward the current request to the downstream handler
    • forward the request to the error handler

Sending a Response

To complete the request handling, you must send a response by calling the res.send() method.

one of the variants of send method MUST be invoked in the same thread the request is started in; as we run in Virtual Threads, you can simply wait for any asynchronous tasks that must complete before sending a response

rules.get("/hello", (req, res) -> { 
    // terminating logic
    res.status(Status.ACCEPTED_202)
            .send("Saved!"); 
});
Copied
  • handler that terminates the request handling for any HTTP method using the /hello path
  • send the response

Protocol-Specific Routing

Handling routes based on the protocol version is possible by registering specific routes on routing builder.

Routing based on HTTP version
rules.get("/any-version", (req, res) -> res.send("HTTP Version " + req.prologue().protocolVersion())) 
        .route(Http1Route.route(Method.GET, "/version-specific", (req, res) -> res.send("HTTP/1.1 route"))) 
        .route(Http2Route.route(Method.GET, "/version-specific", (req, res) -> res.send("HTTP/2 route"))); 
Copied
  • An HTTP route registered on /any-version path that prints the version of HTTP protocol
  • An HTTP/1.1 route registered on /version-specific path
  • An HTTP/2 route registered on /version-specific path

While Http1Route for Http/1 is always available with Helidon webserver, other routes like Http2Route for HTTP/2 needs to be added as additional dependency.

Requested URI Discovery

Proxies and reverse proxies between an HTTP client and your Helidon application mask important information (for example Host header, originating IP address, protocol) about the request the client sent. Fortunately, many of these intermediary network nodes set or update either the standard HTTP Forwarded header or the non-standard X-Forwarded-* family of headers to preserve information about the original client request.

Helidon’s requested URI discovery feature allows your application—​and Helidon itself—​to reconstruct information about the original request using the Forwarded header and the X-Forwarded-* family of headers.

When you prepare the connections in your server you can include the following optional requested URI discovery settings:

  • enabled or disabled

  • which type or types of requested URI discovery to use:

    • FORWARDED - uses the Forwarded header

    • X_FORWARDED - uses the X-Forwarded-* headers

    • HOST - uses the Host header

  • what intermediate nodes to trust

When your application invokes request.requestedUri() Helidon iterates through the discovery types you set up for the receiving connection, gathering information from the corresponding header(s) for that type. If the request does not have the corresponding header(s), or your settings do not trust the intermediate nodes reflected in those headers, then Helidon tries the next discovery type you set up. Helidon uses the HOST discovery type if you do not set up discovery yourself or if, for a particular request, it cannot assemble the request information using any discovery type you did set up for the socket.

Setting Up Requested URI Discovery Programmatically

To set up requested URI discovery on the default socket for your server, use the WebServerConfig.Builder:

Requested URI set-up for the default server socket
import io.helidon.common.configurable.AllowList;
import jakarta.json.Json;
import jakarta.json.JsonBuilderFactory;
import jakarta.json.JsonObject;

import static io.helidon.http.RequestedUriDiscoveryContext.RequestedUriDiscoveryType.FORWARDED;
import static io.helidon.http.RequestedUriDiscoveryContext.RequestedUriDiscoveryType.X_FORWARDED;

AllowList trustedProxies = AllowList.builder()
        .addAllowedPattern(Pattern.compile("lb.+\\.mycorp\\.com"))
        .addDenied("lbtest.mycorp.com")
        .build(); 

WebServer.builder()
        .requestedUriDiscoveryContext(it -> it
                .addDiscoveryType(FORWARDED) 
                .addDiscoveryType(X_FORWARDED)
                .trustedProxies(trustedProxies)); 
Copied
  • Create the AllowList describing the intermediate networks nodes to trust and not trust. Presumably the lbxxx.mycorp.com nodes are trusted load balancers except for the test load balancer lbtest, and no other nodes are trusted. AllowList accepts prefixes, suffixes, predicates, regex patterns, and exact matches. See the AllowList JavaDoc for complete information.
  • Use Forwarded first, then try X-Forwarded-* on each request.
  • Set the AllowList for trusted intermediaries.

If you build your server with additional sockets, you can control requested URI discovery separately for each.

Setting Up Requested URI Discovery using Configuration

You can also use configuration to set up the requested URI discovery behavior. The following example replicates the settings assigned programmatically in the earlier code example:

Configuring requested URI behavior
server:
  port: 0
  requested-uri-discovery:
    types: FORWARDED,X_FORWARDED
    trusted-proxies:
      allow:
        pattern: "lb.*\\.mycorp\\.com"
      deny:
        exact: "lbtest.mycorp.com""
Copied

Obtaining the Requested URI Information

Your code obtains the requested URI information from the Helidon server request object:

Retrieving Requested URI Information
import io.helidon.common.tls.Tls;
import io.helidon.common.uri.UriInfo;

rules.get((req, res) -> {
    UriInfo uriInfo = req.requestedUri();
    // ...
});
Copied

See the UriInfo JavaDoc for more information.

Error Handling

Error Routing

You may register an error handler for a specific Throwable in a HttpRouting.Builder method.

routing.error(MyException.class, (req, res, ex) -> { 
    // handle the error, set the HTTP status code
    res.send(errorDescriptionObject); 
});
Copied
  • Registers an error handler that handles MyException that are thrown from the upstream handlers
  • Finishes the request handling by sending a response

Error handlers are called when

  • an exception is thrown from a handler

As with the standard handlers, the error handler must either

  • send a response

    routing.error(MyException.class, (req, res, ex) -> {
        res.status(Status.BAD_REQUEST_400);
        res.send("Unable to parse request. Message: " + ex.getMessage());
    });
    Copied
  • or throw an exception

    routing.error(MyException.class, (req, res, ex) -> {
        // some logic
        throw ex;
    });
    Copied

Exceptions thrown from error handlers are not error handled, and will end up in an InternalServerError.

Default Error Handling

If no user-defined error handler is matched, or if the error handler of the exception threw an exception, then the exception is translated to an HTTP response as follows:

  • Subtypes of HttpException are translated to their associated HTTP error codes.

    Reply with the 406 HTTP error code by throwing an exception
    rules.get((req, res) -> {
        throw new HttpException(
                "Amount of money must be greater than 0.",
                Status.NOT_ACCEPTABLE_406); 
    });
    Copied
  • Otherwise, the exceptions are translated to an Internal Server Error HTTP error code 500.

Configuration Options

Type: io.helidon.common.tls.Tls

Configuration options

Optional configuration options
keytypedefault valuedescription
cipher-suite

string[]

 

Enabled cipher suites for TLS communication.

@return cipher suits to enable, by default (or if list is empty), all available cipher suites
        are enabled
client-auth

TlsClientAuth (REQUIRED, OPTIONAL, NONE)

NONE

Configure requirement for mutual TLS.

@return what type of mutual TLS to use, defaults to TlsClientAuth#NONE
enabled

boolean

true

Flag indicating whether Tls is enabled.

@return enabled flag
endpoint-identification-algorithm

string

HTTPS

Identification algorithm for SSL endpoints.

@return configure endpoint identification algorithm, or set to `NONE`
        to disable endpoint identification (equivalent to hostname verification).
        Defaults to `Tls#ENDPOINT_IDENTIFICATION_HTTPS`
internal-keystore-provider

string

 

Provider of the key stores used internally to create a key and trust manager factories.

@return keystore provider, if not defined, provider is not specified
internal-keystore-type

string

 

Type of the key stores used internally to create a key and trust manager factories.

@return keystore type, defaults to java.security.KeyStore#getDefaultType()
key-manager-factory-algorithm

string

 

Algorithm of the key manager factory used when private key is defined. Defaults to javax.net.ssl.KeyManagerFactory#getDefaultAlgorithm().

@return algorithm to use
manager

io.helidon.common.tls.TlsManager (service provider interface)

 

The Tls manager. If one is not explicitly defined in the config then a default manager will be created.

@return the tls manager of the tls instance
@see ConfiguredTlsManager
private-key

PrivateKey

 

Private key to use. For server side TLS, this is required. For client side TLS, this is optional (used when mutual TLS is enabled).

@return private key to use
protocol

string

TLS

Configure the protocol used to obtain an instance of javax.net.ssl.SSLContext.

@return protocol to use, defaults to `DEFAULT_PROTOCOL`
protocols

string[]

 

Enabled protocols for TLS communication. Example of valid values for TLS protocol: TLSv1.3, TLSv1.2

@return protocols to enable, by default (or if list is empty), all available protocols are enabled
provider

string

 

Use explicit provider to obtain an instance of javax.net.ssl.SSLContext.

@return provider to use, defaults to none (only #protocol() is used by default)
revocation 

Certificate revocation check configuration.

@return certificate revocation configuration
secure-random-algorithm

string

 

Algorithm to use when creating a new secure random.

@return algorithm to use, by default uses java.security.SecureRandom constructor
secure-random-provider

string

 

Provider to use when creating a new secure random. When defined, #secureRandomAlgorithm() must be defined as well.

@return provider to use, by default no provider is specified
session-cache-size

int

1024

SSL session cache size.

@return session cache size, defaults to 1024
session-timeout

Duration

PT30M

SSL session timeout.

@return session timeout, defaults to 30 minutes
trust

X509Certificate[]

 

List of certificates that form the trust manager.

@return certificates to be trusted
trust-all

boolean

false

Trust any certificate provided by the other side of communication.

<b>This is a dangerous setting: </b> if set to `true`, any certificate will be accepted, throwing away
most of the security advantages of TLS. <b>NEVER</b> do this in production.
@return whether to trust all certificates, do not use in production
trust-manager-factory-algorithm

string

 

Trust manager factory algorithm.

@return algorithm to use

Server Features

Server features provide additional functionality to the WebServer, through modification of the server configuration, listener configuration, or routing.

A server feature can be added by implementing io.helidon.webserver.spi.ServerFeature. Server features support automated discovery, as long as the implementation is available through Java ServiceLoader. Server features can also be added through configuration, as can be seen above in Configuration Options, configuration key features.

All features (both ServerFeature and HttpFeature) honor weight of the feature (defined either through @Weight annotation, or by implementing Weighted interface) when registering routes, HttpService, or Filter to the routing.

The following table shows available server features and their weight. The highest weight is always registered (and invoked) first.

FeatureWeight
Context1100
Access Log1000
Tracing900
CORS850
Security800
Routing (all handlers and filters)100
OpenAPI90
Observability80

Context

Context feature adds a filter that executes all requests within the context of io.helidon.common.context.Context. A Context instance is available on ServerRequest even if this feature is not added. This feature adds support for obtaining request context through io.helidon.common.context.Contexts.context().

This feature will provide the same behavior as previous versions of Helidon. Since Helidon 4.0.0, this feature is not automatically added.

To enable execution of routes within Context, add the following dependency to project’s pom.xml:

<dependency>
    <groupId>io.helidon.webserver</groupId>
    <artifactId>helidon-webserver-context</artifactId>
</dependency>
Copied

Context feature can be configured, all options shown below are also available both in config, and programmatically when using builder.

ContextFeature (webserver.context) Configuration

Type: io.helidon.webserver.context.ContextFeature

Config key
context
Copied

This type provides the following service implementations:

  • io.helidon.webserver.spi.ServerFeatureProvider

Configuration options

Optional configuration options
keytypedefault valuedescription
sockets

string[]

 

List of sockets to register this feature on. If empty, it would get registered on all sockets.

@return socket names to register on, defaults to empty (all available sockets)
weight

double

1100.0

Weight of the context feature. As it is used by other features, the default is quite high: io.helidon.webserver.context.ContextFeature#WEIGHT.

@return weight of the feature

Access Log

Access logging in Helidon is done by a dedicated module that can be added to WebServer and configured.

Access logging is a Helidon WebServer ServerFeature. Access Log feature has a very high weight, so it is registered before other features (such as security) that may terminate a request. This is to ensure the log contains all requests with appropriate status codes.

To enable Access logging add the following dependency to project’s pom.xml:

<dependency>
    <groupId>io.helidon.webserver</groupId>
    <artifactId>helidon-webserver-access-log</artifactId>
</dependency>
Copied

Configuring Access Log in Your Code

AccessLogFeature is discovered automatically by default, and configured through server.features.access-log. You can also configure this feature in code by registering it with WebServer (which will replace the discovered feature).

WebServer.builder()
        .addFeature(AccessLogFeature.builder()
                            .commonLogFormat()
                            .build());
Copied

Configuring Access Log in a Configuration File

Access log can be configured as follows:

Access Log configuration file
server:
  port: 8080
  features:
    access-log:
      format: "%h %l %u %t %r %s %b %{Referer}i"
Copied

All options shown below are also available programmatically when using builder.

AccessLogFeature (webserver.accesslog) Configuration

Type: io.helidon.webserver.accesslog.AccessLogFeature

Config key
access-log
Copied

This type provides the following service implementations:

  • io.helidon.webserver.spi.ServerFeatureProvider

Configuration options

Optional configuration options
keytypedefault valuedescription
enabled

boolean

true

Whether this feature will be enabled.

@return whether enabled
format

string

 

The format for log entries (similar to the Apache LogFormat). <table class="config"> <caption>Log format elements</caption> <tr> <td>%h</td> <td>IP address of the remote host</td> <td>HostLogEntry</td> </tr> <tr> <td>%l</td> <td>The client identity. This is always undefined in Helidon.</td> <td>UserIdLogEntry</td> </tr> <tr> <td>%u</td> <td>User ID as asserted by Helidon Security.</td> <td>UserLogEntry</td> </tr> <tr> <td>%t</td> <td>The timestamp</td> <td>TimestampLogEntry</td> </tr> <tr> <td>%r</td> <td>The request line ("GET /favicon.ico HTTP/1.0")</td> <td>RequestLineLogEntry</td> </tr> <tr> <td>%s</td> <td>The status code returned to the client</td> <td>StatusLogEntry</td> </tr> <tr> <td>%b</td> <td>The entity size in bytes</td> <td>SizeLogEntry</td> </tr> <tr> <td>%D</td> <td>The time taken in microseconds (start of request until last byte written)</td> <td>TimeTakenLogEntry</td> </tr> <tr> <td>%T</td> <td>The time taken in seconds (start of request until last byte written), integer</td> <td>TimeTakenLogEntry</td> </tr> <tr> <td>%{header-name}i</td> <td>Value of header header-name</td> <td>HeaderLogEntry</td> </tr> </table>

@return format string, such as `%h %l %u %t %r %b %{Referer`i}
logger-name

string

io.helidon.webserver.AccessLog

Name of the logger used to obtain access log logger from System#getLogger(String). Defaults to AccessLogFeature#DEFAULT_LOGGER_NAME.

@return name of the logger to use
sockets

string[]

 

List of sockets to register this feature on. If empty, it would get registered on all sockets. The logger used will have the expected logger with a suffix of the socket name.

@return socket names to register on, defaults to empty (all available sockets)
weight

double

1000.0

Weight of the access log feature. We need to log access for anything happening on the server, so weight is high: io.helidon.webserver.accesslog.AccessLogFeature#WEIGHT.

@return weight of the feature

Supported Technologies

HTTP/2 Support

Helidon supports HTTP/2 upgrade from HTTP/1, HTTP/2 without prior knowledge, HTTP/2 with prior knowledge, and HTTP/2 with ALPN over TLS. HTTP/2 support is enabled in WebServer by default when it’s artifact is available on classpath.

Maven Coordinates

To enable HTTP/2 support add the following dependency to your project’s pom.xml.

<dependency>
    <groupId>io.helidon.webserver</groupId>
    <artifactId>helidon-webserver-http2</artifactId>>
</dependency>
Copied

Static Content Support

+Use the io.helidon.webserver.staticcontent.StaticContentService class to serve files and classpath resources. StaticContentService can be created for any readable directory or classpath context root and registered on a path in HttpRouting.

You can combine dynamic handlers with StaticContentService objects: if no file matches the request path, then the request is forwarded to the next handler.

Maven Coordinates

To enable Static Content Support add the following dependency to your project’s pom.xml.

<dependency>
    <groupId>io.helidon.webserver</groupId>
    <artifactId>helidon-webserver-static-content</artifactId>
</dependency>
Copied

Registering Static Content

To register static content based on a file system (/pictures), and classpath (/):

routing.register("/pictures", StaticContentService.create(Paths.get("/some/WEB/pics"))) 
        .register("/", StaticContentService.builder("/static-content") 
                .welcomeFileName("index.html") 
                .build());
Copied
  • Create a new StaticContentService object to serve data from the file system, and associate it with the "/pictures" context path.
  • Create a StaticContentService object to serve resources from the contextual ClassLoader. The specific classloader can be also defined. A builder lets you provide more configuration values.
  • index.html is the file that is returned if a directory is requested.

A StaticContentService object can be created using create(…​) factory methods or a builder. The builder lets you provide more configuration values, including welcome file-name and mappings of filename extensions to media types.

Media types support

WebServer and WebClient share the HTTP media support of Helidon, and any supported media type can be used in both. The media type support is automatically discovered from classpath. Programmatic support is of course enabled as well through MediaContext.

Customized media support for WebServer

WebServer.builder()
        .mediaContext(it -> it
                .mediaSupportsDiscoverServices(false)
                .addMediaSupport(JsonpSupport.create())
                .build());
Copied

Each registered (or discovered) media support adds support for writing and reading entities of a specific type.

The following table lists JSON media supports:

Media typeTypeNameMaven groupId:artifactIdSupported Java type(s)
JSON-PJsonpSupportio.helidon.http.media:helidon-http-media-jsonpJsonObject, JsonArray
JSON-BJsonbSupportio.helidon.http.media:helidon-http-media-jsonbAny *
JacksonJacksonSupportio.helidon.http.media:helidon-http-media-jacksonAny *
  • JSON-B and Jackson have lower weight, so they are used only when no other media type matched the object being written or read

JSON-P Support

The WebServer supports JSON-P. When enabled, you can send and receive JSON-P objects transparently.

Maven Coordinates

To enable JSON Support add the following dependency to your project’s pom.xml.

<dependency>
    <groupId>io.helidon.http.media</groupId>
    <artifactId>helidon-http-media-jsonp</artifactId>
</dependency>
Copied

Usage

Handler that receives and returns JSON objects
static final JsonBuilderFactory JSON_FACTORY = Json.createBuilderFactory(Map.of()); 

rules.post("/hello", (req, res) -> {
    JsonObject requestEntity = req.content().as(JsonObject.class); 
    JsonObject responseEntity = JSON_FACTORY.createObjectBuilder() 
            .add("message", "Hello " + requestEntity.getString("name"))
            .build();
    res.send(responseEntity); 
});
Copied
  • Using a JsonBuilderFactory is more efficient than Json.createObjectBuilder()
  • Get the request entity as JsonObject
  • Create a new JsonObject for the response entity
  • Send JsonObject in response
Example of posting JSON to sayHello endpoint
curl --noproxy '*' -X POST -H "Content-Type: application/json" \
    http://localhost:8080/sayhello -d '{"name":"Joe"}'
Copied
Response body
{"message":"Hello Joe"}
Copied

JSON-B Support

The WebServer supports the JSON-B specification. When this support is enabled, Java objects will be serialized to and deserialized from JSON automatically using Yasson, an implementation of the JSON-B specification.

Maven Coordinates

To enable JSON-B Support add the following dependency to your project’s pom.xml.

<dependency>
    <groupId>io.helidon.http.media</groupId>
    <artifactId>helidon-http-media-jsonb</artifactId>
</dependency>
Copied

Usage

Now that automatic JSON serialization and deserialization facilities have been set up, you can register a Handler that works with Java objects instead of raw JSON. Deserialization from and serialization to JSON will be handled according to the JSON-B specification.

Suppose you have a Person class that looks like this:

Hypothetical Person class
public class Person {

    private String name;

    public Person() {
        super();
    }

    public String getName() {
        return this.name;
    }

    public void setName(String name) {
        this.name = name;
    }
}
Copied

Then you can set up a Handler like this:

A Handler that works with Java objects instead of raw JSON
rules.post("/echo", (req, res) -> {
    res.send(req.content().as(Person.class)); 
});
Copied
  • This handler consumes a Person instance and simply echoes it back. Note that there is not working with raw JSON here.
Example of posting JSON to the /echo endpoint
curl --noproxy '*' -X POST -H "Content-Type: application/json" \
    http://localhost:8080/echo -d '{"name":"Joe"}'
{"name":"Joe"}
Copied

Jackson Support

The WebServer supports Jackson. When this support is enabled, Java objects will be serialized to and deserialized from JSON automatically using Jackson.

Maven Coordinates

To enable Jackson Support add the following dependency to your project’s pom.xml.

<dependency>
    <groupId>io.helidon.http.media</groupId>
    <artifactId>helidon-http-media-jackson</artifactId>
</dependency>
Copied

Usage

Now that automatic JSON serialization and deserialization facilities have been set up, you can register a Handler that works with Java objects instead of raw JSON. Deserialization from and serialization to JSON will be handled by Jackson.

Suppose you have a Person class that looks like this:

Hypothetical Person class
public class Person {

    private String name;

    public Person() {
        super();
    }

    public String getName() {
        return this.name;
    }

    public void setName(String name) {
        this.name = name;
    }
}
Copied

Then you can set up a Handler like this:

A Handler that works with Java objects instead of raw JSON
rules.post("/echo", (req, res) -> {
    res.send(req.content().as(Person.class)); 
});
Copied
  • This handler consumes a Person instance and simply echoes it back. Note that there is no working with raw JSON here.
Example of posting JSON to the /echo endpoint
curl --noproxy '*' -X POST -H "Content-Type: application/json" \
    http://localhost:8080/echo -d '{"name":"Joe"}'
Copied
Response body
{"name":"Joe"}
Copied

HTTP Content Encoding

HTTP encoding can improve bandwidth utilization and transfer speeds in certain scenarios. It requires a few extra CPU cycles for compressing and uncompressing, but these can be offset if data is transferred over low-bandwidth network links.

A client advertises the compression encodings it supports at request time, and the WebServer responds by selecting an encoding it supports and setting it in a header, effectively negotiating the content encoding of the response. If none of the advertised encodings is supported by the WebServer, the response is returned uncompressed.

Configuring HTTP Encoding

HTTP encoding support is discovered automatically by WebServer from the classpath, or it can be customized programmatically.

Encoding can be configured per socket.

Disabling discovery and registering a Gzip encoding support:

WebServer.builder()
        .contentEncoding(it -> it
        .contentEncodingsDiscoverServices(false)
        .addContentEncoding(GzipEncoding.create()));
Copied

Or use a config file using the following options:

Type: io.helidon.http.encoding.ContentEncodingContext

Configuration options

Optional configuration options
keytypedefault valuedescription
content-encodings

io.helidon.http.encoding.ContentEncoding[] (service provider interface)

 

List of content encodings that should be used. Encodings configured here have priority over encodings discovered through service loader.

@return list of content encodings to be used (such as `gzip,deflate`)

The following providers are currently available (simply add the library on the classpath):

Encoding typeTypeNameMaven groupId:artifactId
gzipGzipEncodingio.helidon.http.encoding:helidon-http-encoding-gzip
deflateDeflateSupportio.helidon.http.encoding:helidon-http-encoding-deflate

HTTP Compression Negotiation

HTTP compression negotiation is controlled by clients using the Accept-Encoding header. The value of this header is a comma-separated list of encodings. The WebServer will select one of these encodings for compression purposes; it currently supports gzip and deflate.

For example, if the request includes Accept-Encoding: gzip, deflate, and HTTP compression has been enabled as shown above, the response shall include the header Content-Encoding: gzip and a compressed payload.

Proxy Protocol Support

The Proxy Protocol provides a way to convey client information across reverse proxies or load balancers which would otherwise be lost given that new connections are established for each network hop. Often times, this information can be carried in HTTP headers, but not all proxies support this feature. Helidon is capable of parsing a proxy protocol header (i.e., a network preamble) that is based on either V1 or V2 of the protocol, thus making client information available to service developers.

Proxy Protocol support is enabled via configuration, and can be done either declaratively or programmatically. Once enabled, every new connection on the corresponding port MUST be preambled by a proxy header for the connection not to be rejected as invalid --that is, proxy headers are never optional.

Programmatically, support for the Proxy Protocol is enabled as follows:

WebServer.builder()
        .enableProxyProtocol(true);
Copied

Declaratively, support for the Proxy Protocol is enabled as follows:

server:
  port: 8080
  host: 0.0.0.0
  enable-proxy-protocol: true
Copied

Accessing Proxy Protocol Data

There are two ways in which the header data can be accessed in your application. One way is by obtaining the protocol data directly from a request as shown next:

rules.get("/", (req, res) -> {
    ProxyProtocolData data = req.proxyProtocolData().orElse(null);
    if (data != null
        && data.family() == ProxyProtocolData.Family.IPv4
        && data.protocol() == ProxyProtocolData.Protocol.TCP
        && data.sourceAddress().equals("192.168.0.1")
        && data.destAddress().equals("192.168.0.11")
        && data.sourcePort() == 56324
        && data.destPort() == 443) {
        // ...
    }
});
Copied

Every request associated with a certain connection shall have access to the Proxy Protocol data received when the connection was opened.

Alternatively, the WebServer also makes the original client source address and source port available in the HTTP headers X-Forwarded-For and X-Forwarded-Port, respectively. In some cases, it is just simpler to inspect these headers instead of getting the complete ProxyProtocolData instance as shown above.

Additional Information

Here is the code for a minimalist web application that runs on a random free port:

public static void main(String[] args) {
    WebServer webServer = WebServer.builder()
            .routing(it -> it.any((req, res) -> res.send("It works!"))) 
            .build() 
            .start(); 

    System.out.println("Server started at: http://localhost:" + webServer.port()); 
}
Copied
  • For any kind of request, at any path, respond with It works!.
  • Build the server with the provided configuration
  • Start the server (and wait for it to open the port).
  • The server is bound to a random free port.

Reference