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>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();Configuring the WebServer in a Configuration File
You can also define the configuration in a file.
application.yamlserver:
port: 8080
host: "0.0.0.0"Then, in your application code, load the configuration from that file.
application.yaml file located on the classpathConfig config = Config.create();
WebServer.builder()
.config(config.get("server")); application.yamlis a default configuration source loaded when YAML support is on classpath, so we can just useConfig.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);Configuring TLS in the Config File
It is also possible to configure TLS via the config file.
application.yamlserver:
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"Then, in your application code, load the configuration from that file.
application.yaml file located on the classpathConfig config = Config.create();
WebServer.builder()
.config(config.get("server")); application.yamlis a default configuration source loaded when YAML support is on classpath, so we can just useConfig.create()- Server expects the configuration tree located on the node of
server
Or you can only create WebServerTls instance based on the config file.
application.yaml file located on the classpathConfig config = Config.create();
WebServer.builder()
.tls(it -> it.config(config.get("server.tls")));This can alternatively be configured with paths to PKCS#8 PEM files rather than KeyStores:
application.yamlserver:
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"Configuration Options
Type: io.helidon.webserver.WebServer
This is a standalone configuration type, prefix from configuration root: server
Configuration options
| key | type | default value | description |
|---|---|---|---|
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 | io.helidon.webserver.spi.ServerFeature[] (service provider interface) Such as: | 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 ( @return host address to listen on (for the default socket) |
idle-connection-period | Duration | PT2M | How often should we check for #idleConnectionTimeout(). Defaults to @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 @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 @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 ( @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 @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 @return name of the socket |
port | int | 0 | Port of the default socket. If configured to @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 @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 @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 @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.
WebServer.builder()
.routing(it -> it
.get("/hello", (req, res) -> res.send("Hello World!")))
.build(); - Handle all GETs to
/hellopath. Send theHello 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 Method | HttpRouting.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 */ });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/fooor 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);
}));- The route is specified for
GETandPOSTrequests - 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.
HttpRouting.Builder.register to register your servicerouting.register("/hello", new HelloService());class HelloService implements HttpService {
@Override
public void routing(HttpRules rules) {
rules.get("/subpath", (req, res) -> {
// Some logic
});
}
}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
}
});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(); });content_copy- handler for any HTTP method using the
/hellopath - business logic implementation
- forward the current request to the downstream handler
- handler for any HTTP method using the
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."); } });content_copy- handler for any HTTP method using the
/hellopath - custom logic
- forward the current request to the downstream handler
- forward the request to the error handler
- handler for any HTTP method using the
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!");
});- handler that terminates the request handling for any HTTP method using the
/hellopath - send the response
Protocol-Specific Routing
Handling routes based on the protocol version is possible by registering specific routes on routing builder.
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"))); - An HTTP route registered on
/any-versionpath that prints the version of HTTP protocol - An HTTP/1.1 route registered on
/version-specificpath - An HTTP/2 route registered on
/version-specificpath
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 theForwardedheaderX_FORWARDED- uses theX-Forwarded-*headersHOST- uses theHostheader
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:
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)); - Create the
AllowListdescribing the intermediate networks nodes to trust and not trust. Presumably thelbxxx.mycorp.comnodes are trusted load balancers except for the test load balancerlbtest, and no other nodes are trusted.AllowListaccepts prefixes, suffixes, predicates, regex patterns, and exact matches. See theAllowListJavaDoc for complete information. - Use
Forwardedfirst, then tryX-Forwarded-*on each request. - Set the
AllowListfor 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:
server:
port: 0
requested-uri-discovery:
types: FORWARDED,X_FORWARDED
trusted-proxies:
allow:
pattern: "lb.*\\.mycorp\\.com"
deny:
exact: "lbtest.mycorp.com""Obtaining the Requested URI Information
Your code obtains the requested URI information from the Helidon server request object:
import io.helidon.common.tls.Tls;
import io.helidon.common.uri.UriInfo;
rules.get((req, res) -> {
UriInfo uriInfo = req.requestedUri();
// ...
});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);
});- Registers an error handler that handles
MyExceptionthat 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()); });content_copyor throw an exception
routing.error(MyException.class, (req, res, ex) -> { // some logic throw ex; });content_copy
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
HttpExceptionare translated to their associated HTTP error codes.Reply with the406HTTP error code by throwing an exceptionrules.get((req, res) -> { throw new HttpException( "Amount of money must be greater than 0.", Status.NOT_ACCEPTABLE_406); });content_copyOtherwise, the exceptions are translated to an Internal Server Error HTTP error code
500.
Configuration Options
Type: io.helidon.common.tls.Tls
Configuration options
| key | type | default value | description |
|---|---|---|---|
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 @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.
| Feature | Weight |
|---|---|
| Context | 1100 |
| Access Log | 1000 |
| Tracing | 900 |
| CORS | 850 |
| Security | 800 |
| Routing (all handlers and filters) | 100 |
| OpenAPI | 90 |
| Observability | 80 |
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>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
contextThis type provides the following service implementations:
io.helidon.webserver.spi.ServerFeatureProvider
Configuration options
| key | type | default value | description |
|---|---|---|---|
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: @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>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());Configuring Access Log in a Configuration File
Access log can be configured as follows:
server:
port: 8080
features:
access-log:
format: "%h %l %u %t %r %s %b %{Referer}i"All options shown below are also available programmatically when using builder.
AccessLogFeature (webserver.accesslog) Configuration
Type: io.helidon.webserver.accesslog.AccessLogFeature
access-logThis type provides the following service implementations:
io.helidon.webserver.spi.ServerFeatureProvider
Configuration options
| key | type | default value | description |
|---|---|---|---|
enabled | boolean | true | Whether this feature will be enabled. @return whether enabled |
format | string | The format for log entries (similar to the Apache @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 @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: @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>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>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());- Create a new
StaticContentServiceobject to serve data from the file system, and associate it with the"/pictures"context path. - Create a
StaticContentServiceobject to serve resources from the contextualClassLoader. The specific classloader can be also defined. A builder lets you provide more configuration values. index.htmlis 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());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 type | TypeName | Maven groupId:artifactId | Supported Java type(s) |
|---|---|---|---|
| JSON-P | JsonpSupport | io.helidon.http.media:helidon-http-media-jsonp | JsonObject, JsonArray |
| JSON-B | JsonbSupport | io.helidon.http.media:helidon-http-media-jsonb | Any * |
| Jackson | JacksonSupport | io.helidon.http.media:helidon-http-media-jackson | Any * |
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>Usage
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);
});- Using a
JsonBuilderFactoryis more efficient thanJson.createObjectBuilder() - Get the request entity as
JsonObject - Create a new
JsonObjectfor the response entity - Send
JsonObjectin response
curl --noproxy '*' -X POST -H "Content-Type: application/json" \
http://localhost:8080/sayhello -d '{"name":"Joe"}'{"message":"Hello Joe"}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>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:
Person classpublic class Person {
private String name;
public Person() {
super();
}
public String getName() {
return this.name;
}
public void setName(String name) {
this.name = name;
}
}Then you can set up a Handler like this:
Handler that works with Java objects instead of raw JSONrules.post("/echo", (req, res) -> {
res.send(req.content().as(Person.class));
});- This handler consumes a
Personinstance and simply echoes it back. Note that there is not working with raw JSON here.
/echo endpointcurl --noproxy '*' -X POST -H "Content-Type: application/json" \
http://localhost:8080/echo -d '{"name":"Joe"}'
{"name":"Joe"}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>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:
Person classpublic class Person {
private String name;
public Person() {
super();
}
public String getName() {
return this.name;
}
public void setName(String name) {
this.name = name;
}
}Then you can set up a Handler like this:
Handler that works with Java objects instead of raw JSONrules.post("/echo", (req, res) -> {
res.send(req.content().as(Person.class));
});- This handler consumes a
Personinstance and simply echoes it back. Note that there is no working with raw JSON here.
/echo endpointcurl --noproxy '*' -X POST -H "Content-Type: application/json" \
http://localhost:8080/echo -d '{"name":"Joe"}'{"name":"Joe"}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()));Or use a config file using the following options:
Type: io.helidon.http.encoding.ContentEncodingContext
Configuration options
| key | type | default value | description |
|---|---|---|---|
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 type | TypeName | Maven groupId:artifactId |
|---|---|---|
| gzip | GzipEncoding | io.helidon.http.encoding:helidon-http-encoding-gzip |
| deflate | DeflateSupport | io.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);Declaratively, support for the Proxy Protocol is enabled as follows:
server:
port: 8080
host: 0.0.0.0
enable-proxy-protocol: trueAccessing 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) {
// ...
}
});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());
}- 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.