WebServer
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.
WebServer configuration file application.yaml:
server:
port: 8080
host: "0.0.0.0"
Then, in your application code, load the configuration from that file.
WebServer initialization using the application.yaml file located on the
classpath:
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.
WebServer TLS configuration file application.yaml:
Then, in your application code, load the configuration from that file.
WebServer initialization using the application.yaml file located on the
classpath:
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")));
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"
Configuration Options
| Key | Type | Default | Description |
|---|---|---|---|
restore- | Boolean | true | Copy and restore response headers before and after passing a request to Jersey for processing |
concurrency- | Limit | Concurrency limit to use to limit concurrent execution of incoming requests | |
content- | Content | Configure the listener specific io. | |
media- | Media | Configure the listener specific io. | |
max- | Long | -1 | Maximal number of bytes an entity may have |
features | List< | Server features allow customization of the server, listeners, or routings | |
use- | Boolean | true | If set to true, use NIO socket channel, instead of a socket |
protocols- | Boolean | true | Whether to enable automatic service discovery for protocols |
enable- | Boolean | false | Enable proxy protocol support for this socket |
host | String | 0. | Host of the default socket |
write- | Integer | 0 | Number of buffers queued for write operations |
sockets | Map< | Socket configurations | |
protocols | List< | Configuration of protocols | |
max- | Integer | -1 | Limits the number of connections that can be opened at a single point in time |
bind- | String | The address to bind to | |
idle- | Duration | PT5M | How long should we wait before closing a connection that has no traffic on it |
shutdown- | Duration | PT0. | Grace period in ISO 8601 duration format to allow running tasks to complete before listener's shutdown |
max- | Integer | -1 | Limits the number of requests that can be executed at the same time (the number of active virtual threads of requests) |
features- | Boolean | true | Whether to enable automatic service discovery for features |
shutdown- | Boolean | true | When true the webserver registers a shutdown hook with the JVM Runtime |
error- | Error | Configuration for this listener's error handling | |
concurrency- | Boolean | false | Whether to enable automatic service discovery for concurrency- |
backlog | Integer | 1024 | Accept backlog |
max- | Integer | 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 |
ignore- | Boolean | If set to true, any named routing configured that does not have an associated named listener will NOT cause an exception to be thrown (default behavior is to throw an exception) | |
smart- | Boolean | false | If enabled and #write is greater than 1, then start with async writes but possibly switch to sync writes if async queue size is always below a certain threshold |
connection- | Socket | Options for connections accepted by this listener | |
port | Integer | 0 | Port of the default socket |
requested- | Requested | Requested URI discovery context | |
idle- | Duration | PT2M | How often should we check for #idle |
name | String | @default | Name of this socket |
tls | Tls | Listener TLS configuration | |
write- | Integer | 4096 | Initial buffer size in bytes of java. created internally to write data to a socket connection |
Deprecated Options
| Key | Type | Description |
|---|---|---|
connection- | Connection | Configuration of a connection (established from client against our server) |
receive- | Integer | Listener receive buffer size |
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.
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:
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/{*}- 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.
/foo/bar request is
not routed to .post('/foo', ...)./ 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.
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());
Service implementation:
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.
Server Lifecycle
In Helidon 4 your HttpService can interpose on the server lifecycle by
overriding the beforeStart and afterStop methods:
Helidon 4.x server lifecycle:
static class MyService implements HttpService {
@Override
public void beforeStart() {
System.out.println("MyService: Helidon WebServer is starting!");
}
@Override
public void afterStop() {
System.out.println("MyService: Helidon WebServer has stopped.");
}
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() - throw an exception to forward to error handling
Sending a Response
To complete the request handling, you must send a response by calling the
res.send() method.
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 responseProtocol-Specific Routing
Handling routes based on the protocol version is possible by registering specific routes on routing builder.
Routing based on HTTP version:
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:
Requested URI set-up for the default server socket:
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""
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();
// ...
});
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.
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()); }); - or throw an exception
routing.error(MyException.class, (req, res, ex) -> { // some logic throw ex; });
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 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); }); - Otherwise, the exceptions are translated to an Internal Server Error HTTP
error code
500.
Direct Error Handling
There are a number of scenarios where errors can be detected before the request routing phase is initiated, some of these include: error validating requests (e.g. a bad URI), CORS rejections, invalid payloads, unsupported HTTP versions, etc. For all these type of events, Helidon provides the so-called direct handlers. The complete list of events that are handled in this way is defined by the enum EventType.
Direct handlers can be configured independently for each port exposed by the
Webserver; similar to other config, if configured directly on the Webserver they
will only apply to the default port. For more information see
directHandlers method in ListenerConfig.
The following example shows how to register a custom handler for a request that is deemed invalid before the routing phase stars. The custom handler in this example simply returns a status code of 400 and a message that references the server log.
Register a direct handler for bad requests in the Webserver:
public static void main(String[] args) {
WebServer server = WebServer.builder()
.directHandlers(DirectHandlers.builder()
.addHandler(EventType.BAD_REQUEST, new MyDirectHandler())
.build())
.build()
.start();
}
static class MyDirectHandler implements DirectHandler {
@Override
public TransportResponse handle(TransportRequest transportRequest,
EventType eventType,
Status status,
ServerResponseHeaders serverResponseHeaders,
String s) {
return DirectHandler.TransportResponse.builder()
.status(Status.BAD_REQUEST_400)
.entity("Bad request, see server log")
.build();
}
}
Default Direct Error Handler
Helidon includes a default direct handler that offers basic support for all
these events out of the box. This default handler supports a couple of config
properties that control logging and error reporting: these are includeEntity
and logAllMessages. The former controls how data reflection from the request
is handled, while the latter controls logging of potentially sensitive
information. Both of these flags are set to false by default to prevent any
data leak either in the response or in the server log.
The default direct handler’s settings in the Webserver can be controlled via config:
Configuring error handling on default port:
server:
error-handling:
include-entity: true
log-all-messages: true
With these settings, the default error handler on the default Webserver port will log all messages and may include reflected user data in error response entities.
Note: Even though some request data can be reflected back in responses when
include-entity is set to true, Helidon will always ensure that it is
properly encoded to prevent common HTML attacks.
Any other port defined in your application may include an error-handling
section to configure the default handler behavior on that port.
TLS Configuration Options
| Key | Type | Default | Description |
|---|---|---|---|
trust | List< | List of certificates that form the trust manager | |
session- | Duration | PT24H | SSL session timeout |
internal- | String | Provider of the key stores used internally to create a key and trust manager factories | |
manager | Tls | The Tls manager | |
endpoint- | String | HTTPS | Identification algorithm for SSL endpoints |
private- | Keys | Private key to use | |
key- | String | Algorithm of the key manager factory used when private key is defined | |
manager- | Boolean | false | Whether to enable automatic service discovery for manager |
secure- | String | Provider to use when creating a new secure random | |
session- | Integer | 20480 | SSL session cache size |
enabled | Boolean | true | Flag indicating whether Tls is enabled |
revocation | Revocation | Certificate revocation check configuration | |
protocol | String | TLS | Configure the protocol used to obtain an instance of javax. |
provider | String | Use explicit provider to obtain an instance of javax. | |
client- | Tls | NONE | Configure requirement for mutual TLS |
cipher- | List< | Enabled cipher suites for TLS communication | |
internal- | String | Type of the key stores used internally to create a key and trust manager factories | |
trust- | String | Trust manager factory algorithm | |
trust- | Boolean | false | Trust any certificate provided by the other side of communication |
protocols | List< | Enabled protocols for TLS communication | |
secure- | String | Algorithm to use when creating a new secure random |
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.
Configuration options
| Key | Type | Default | Description |
|---|---|---|---|
records | List< | List of propagation records | |
weight | Double | 1100. | Weight of the context feature |
sockets | List< | List of sockets to register this feature on |
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:
Access Log configuration file:
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.
Configuration options
| Key | Type | Default | Description |
|---|---|---|---|
format | String | The format for log entries (similar to the Apache Log) | |
logger- | String | io. | Name of the logger used to obtain access log logger from System# |
weight | Double | 1000. | Weight of the access log feature |
sockets | List< | List of sockets to register this feature on | |
enabled | Boolean | true | Whether this feature will be enabled |
See the manifest for all available types.
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.
request.content().hasEntity() returns true by default. It
returns false only if the request’s header frame includes the END_STREAM
flag or the Content‑Length header is present with a value of 0.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
Static content is served through a StaticContentFeature. As with other server
features, it can be configured through config, or registered with server config
builder.
Static content supports serving of files from classpath, or from any readable
directory on the file system. Each content handler must include a location, and
can provide a context that will be registered with the WebServer (defaults to
/).
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
(/):
server feature using WebServerConfig.Builder:
Static content can also be registered using the configuration of server feature.
If you use Config with your webserver setup, you can register the same static
content using configuration:
server:
features:
static-content:
path:
- context: "/pictures"
location: "/some/WEB/pics"
classpath:
- context: "/"
welcome: "index.html"
location: "/static-content"
See Static Content Feature Configuration Reference for details of configuration options.
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 * |
| Gson | GsonSupport | io.helidon.http.media:helidon-http-media-gson | 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
Handler that receives and returns JSON objects:
Example of posting JSON to sayHello endpoint:
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>
Configuration
It is possible to configure the Jsonb instance via programmatic or configuration-based approach. When configured over the configuration, all the configured value types need to be selected correctly according to the JSON-B spec and placed to the right section.
Configuration options
| Key | Type | Description |
|---|---|---|
boolean- | Map< | Jsonb boolean configuration properties |
class- | Map< | Jsonb Class configuration properties |
properties | Map< | Jsonb String configuration properties |
Example
Example JSON-B configuration:
jsonb:
boolean-properties:
jsonb.null-values: true
properties:
jsonb.property-naming-strategy: "LOWER_CASE_WITH_DASHES"
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;
}
}
Then you can set up a Handler like this:
A Handler that works with Java objects instead of raw JSON:
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"}
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>
Configuration
It is possible to configure the Jackson ObjectMapper instance via programmatic or configuration-based approach.
Configuration options
| Key | Type | Description |
|---|---|---|
properties | Map< | Jackson configuration properties |
Example
Example Jackson configuration:
jackson:
properties:
FAIL_ON_UNKNOWN_PROPERTIES: false
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;
}
}
Then you can set up a Handler like this:
A Handler that works with Java objects instead of raw JSON:
Example of posting JSON to the /echo endpoint:
curl --noproxy '*' -X POST -H "Content-Type: application/json" \
http://localhost:8080/echo -d '{"name":"Joe"}'
Response body:
{"name":"Joe"}
Gson Support
The WebServer supports Gson. When this support is enabled, Java objects will be serialized to and deserialized from JSON automatically using Gson.
Maven Coordinates
To enable Gson Support add the following dependency to your project’s pom.xml.
<dependency>
<groupId>io.helidon.http.media</groupId>
<artifactId>helidon-http-media-gson</artifactId>
</dependency>
Configuration
It is possible to configure the Gson instance via programmatic or configuration-based approach.
Configuration options
| Key | Type | Description |
|---|---|---|
properties | Map< | Gson configuration properties |
Example
Example Gson configuration:
gson:
properties:
serialize-nulls: false
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
Gson.
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;
}
}
Then you can set up a Handler like this:
A Handler that works with Java objects instead of raw JSON:
Example of posting JSON to the /echo endpoint:
curl --noproxy '*' -X POST -H "Content-Type: application/json" \
http://localhost:8080/echo -d '{"name":"Joe"}'
Response body:
{"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.
Handlers can encode the response and set the appropriate header to preempt
encoding by the WebServer. For instance, if a Handler sets the
Content-Encoding: gzip header then the response will not be additionally
compressed.
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:
Configuration options
| Key | Type | Default | Description |
|---|---|---|---|
content- | List< | List of content encodings that should be used | |
content- | Boolean | true | Whether to enable automatic service discovery for content- |
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: true
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) {
// ...
}
});
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.
Accessing Proxy Protocol V2 Data
The binary (V2) version of the Proxy Protocol includes additional information
beyond that found in the text (V1) protocol version. The V2 version exposes a
proxy command type (LOCAL or PROXY), allows source and destination addresses to
be Unix domain sockets, and supports structured metadata using Tag-Length-Value
(TLV) encoded structures. Helidon makes this additional information available
through the ProxyProtocolV2Data interface, which extends ProxyProtocolData.
To access the V2 data, check whether the ProxyProtocolData object obtained
from the request implements the ProxyProtocolV2Data interface:
rules.get("/", (req, res) -> {
ProxyProtocolData data = req.proxyProtocolData().orElse(null);
// The data object will be an instance of ProxyProtocolV2Data if V2 of the Proxy Protocol
// was used by the upstream proxy.
if (data instanceof ProxyProtocolV2Data v2Data) {
// PROXY or LOCAL?
ProxyProtocolV2Data.Command command = v2Data.command();
// Will be either an InetSocketAddress (for IPv4 or IPv6) or a UnixDomainSocketAddress.
SocketAddress sourceSocketAddress = v2Data.sourceSocketAddress();
SocketAddress destSocketAddress = v2Data.destSocketAddress();
// Contains all of the Tag-Length-Value objects from the Proxy Protocol header.
List<ProxyProtocolV2Data.Tlv> tlvData = v2Data.tlvs();
}
});
Additional Information
Here is the code for a minimalist web application that runs on a random free port: