Contents
Overview
WebServer provides an asynchronous and reactive API for creating web applications. The API is inspired by popular NodeJS and Java frameworks.
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>Usage
The following sections describe how to use WebServer.
Configuration
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 webServer = WebServer.builder()
.bindAddress(InetAddress.getLocalHost())
.port(8080)
.build();Configuring the WebServer in a Configuration File
You can also define the configuration in a file.
application.yamlserver:
port: 8080
bind-address: "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 webServer = WebServer.create(routing, 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
Configuration Options
Configuration of the HTTP server.
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 | Configures a maximum length of the queue of incoming connections on the server socket. Default value is #DEFAULT_BACKLOG_SIZE. |
backpressure-buffer-size | long | Maximum length of the response data sending buffer can keep without flushing. Depends on | |
backpressure-strategy | BackpressureStrategy (LINEAR, AUTO_FLUSH, PREFETCH, UNBOUNDED) | LINEAR | Sets a backpressure strategy for the server to apply against user provided response upstream.
|
bind-address | string | Deprecated Configures local address where the server listens on with the server socket. If not configured, then listens an all local addresses. | |
enable-compression | boolean | false | Enable negotiation for gzip/deflate content encodings. Clients can request compression using the "Accept-Encoding" header. Default is `false` |
features.print-details | boolean | false | Set to |
host | string | A helper method that just calls #bindAddress(String). | |
max-header-size | int | 16384 | Maximal number of bytes of all header values combined. When a bigger value is received, a io.helidon.common.http.Http.Status#BAD_REQUEST_400 is returned. Default is `16384` |
max-initial-line-length | int | 4096 | Maximal number of characters in the initial HTTP line. Default is `4096` |
max-payload-size | long | Set a maximum payload size for a client request. Can prevent DoS attacks. | |
max-upgrade-content-length | int | 65536 | Set a maximum length of the content of an upgrade request. Default is `64*1024` |
port | int | 0 | Configures a server port to listen on with the server socket. If port is |
receive-buffer-size | int | Configures proposed value of the TCP receive window that is advertised to the remote peer on the server socket. If `0` then use implementation default. | |
continue-immediately | boolean | false | When true WebServer answers to expect continue with 100 continue immediately, not waiting for user to actually request the data. Default is |
requested-uri-discovery.enabled | boolean | true if 'types' or 'trusted-proxies' is set; false otherwise | Sets whether requested URI discovery is enabled for the socket. |
requested-uri-discovery.trusted-proxies | Assigns the settings governing the acceptance and rejection of forwarded headers from incoming requests to this socket. This setting automatically enables discovery for the socket. | ||
requested-uri-discovery.types | RequestedUriDiscoveryType[] (FORWARDED, X_FORWARDED, HOST) | FORWARDED if discovery is enabled; none otherwise | Assigns the front-end URI discovery type(s) this socket should use. This setting automatically enables discovery for the socket. |
sockets | Adds an additional named server socket configuration. As a result, the server will listen on multiple ports. An additional named server socket may have a dedicated Routing configured through io.helidon.webserver.WebServer.Builder#addNamedRouting(String, Routing). | ||
timeout-millis | long | 0 | Socket timeout in milliseconds |
tls | Configures SSL for this socket. When configured, the server enforces SSL configuration. If this method is called, any other method except for #tls(java.util.function.Supplier)¨ and repeated invocation of this method would be ignored. If this method is called again, the previous configuration would be ignored. | ||
worker-count | int | Sets a count of threads in pool used to process HTTP requests. Default value is Configuration key: `workers` |
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.
Basics
Routing also supports Error Routing which binds Java Throwable to the handling logic.
Configure HTTP request routing using Routing.Builder.
Routing routing = Routing.builder()
.get("/hello", (req, res) -> res.send("Hello World!"))
.build();
WebServer webServer = WebServer.create(routing); - Handle all GETs to
/hellopath. Send theHello World!string. - Add the
routingto the WebServer.
HTTP Method Routing
Routing.Builder lets you specify how to handle each HTTP method. For example:
| HTTP Method | Routing.Builder example |
|---|---|
| GET | .get((req, res) -> { /* handler */ }) |
| PUT | .put((req, res) -> { /* handler */ }) |
| POST | .post((req, res) -> { /* handler */ }) |
| HEAD | .head((req, res) -> { /* handler */ }) |
| DELETE | .delete((req, res) -> { /* handler */ }) |
| TRACE | .trace((req, res) -> { /* handler */ }) |
| OPTIONS | .options((req, res) -> { /* handler */ }) |
| any method | .any((req, res) -> { /* handler */ }) |
| multiple methods | .anyOf(List.of(Http.Method.GET, Http.Method.POST), (req, res) -> { /* handler */ }) |
| custom method | .anyOf(Set.of(Http.RequestMethod.create("CUSTOM")), (req, res) -> { /* handler */ }) |
Path Matcher Routing
You can combine HTTP method routing with request path matching.
Routing.builder()
.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/{}/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:.+}. A matcher is not a true URI template (as defined by RFC) but this convenience is in sync with the Apiary templates/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.
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.
Request Predicate
Use the RequestPredicate utility class to identify more criteria. You can construct (build) a predicate based on typical request criteria such as content type, or the existence of a header or cookie. You can also construct a handler that only processes requests accepted by the predicate. All other requests are nexted, meaning that they are routed to the next valid handler.
.post("/foo",
RequestPredicate.create()
.containsHeader("my-gr8-header")
.accepts(MediaType.TEXT_PLAIN)
.and(this::isUserAuthenticated)
.thenApply((req, resp) -> {
// Some logic
})
.otherwise((req, resp) -> { /* Otherwise logic */ }); // Optional. Default logic is req.next()Organizing Code into Services
By implementing the Service interface you can organize your code into one or more services, each with its own path prefix and set of handlers.
Routing.Builder.register to register your service.register("/hello", new HelloService())public class HelloService implements Service {
@Override
public void update(Routing.Rules rules) {
rules.get("/subpath", this::getHandler);
}
private void getHandler(ServerRequest request,
ServerResponse response) {
// Some logic
}
}In this example, the GET handler matches requests to /hello/subpath.
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
request.next()method.Throw an exception or call
request.next(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.
Handler as a Filter
The handler forwards the request to the downstream handlers by nexting. There are two options:
call
req.next().any("/hello", (req, res) -> { // filtering logic req.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
call
req.next(throwable)to forward the handling to the error handling.any("/hello", (req, res) -> { // filtering logic (e.g., validating parameters) if (userParametersOk()) { req.next(); } else { req.next(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
The handling logic can explicitly forward the execution to a different thread. This is the reason why returning from the handler can’t automatically trigger calling the next handler.
Sending a response
To complete the request handling, you must send a response by calling the res.send() method.
.get("/hello", (req, res) -> {
// terminating logic
res.status(Http.Status.ACCEPTED_201);
res.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.
.routing(r -> r
.get("/any-version", (req, res) -> res.send("HTTP Version " + req.version()))
.route(Http1Route.route(GET, "/version-specific", (req, res) -> res.send("HTTP/1.1 route")))
.route(Http2Route.route(GET, "/version-specific", (req, res) -> res.send("HTTP/2 route")))
)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 sockets 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 socket, 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 WebServer.Builder:
import io.helidon.common.configurable.AllowList;
import static io.helidon.webserver.SocketConfiguration.RequestedUriDiscoveryType.FORWARDED;
import static io.helidon.webserver.SocketConfiguration.RequestedUriDiscoveryType.X_FORWARDED;
AllowList trustedProxies = AllowList.builder()
.addAllowedPattern(Pattern.compile("lb.+\\.mycorp\\.com"))
.addDenied("lbtest.mycorp.com")
.build();
WebServer.Builder builder = WebServer.builder()
.defaultSocket(s -> s
.host("localhost")
.port(0)
.requestedUriDiscoveryTypes(List.of(FORWARDED, X_FORWARDED))
.trustedProxies(trustedProxies))
.addRouting(yourRouting)
.config(serverConfig);- 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.http.UriInfo;
public class MyHandler implements Handler {
@Override
public void accept(ServerRequest req, ServerResponse 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 the Routing.Builder method.
Routing routing = Routing.builder()
.error(MyException.class, (req, res, ex) -> {
// handle the error, set the HTTP status code
res.send(errorDescriptionObject);
})
.build- 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
req.next(ex)is called, whereexis an instance ofThrowable
As with the standard handlers, the error handler must either
send a response
.error(MyException.class, (req, res, ex) -> { res.status(Http.Status.BAD_REQUEST_400); res.send("Unable to parse request. Message: " + ex.getMessage()); })content_copyor, forward the error handling to the downstream error handlers
.error(Throwable.class, (req, res, ex) -> { // some logic req.next(ex); })content_copy
Error handling can’t be forwarded to the standard handlers. In fact, invoking req.next(ex) or req.next() in an error handler are equivalent.
.error(Throwable.class, (req, res, ex) -> {
if (condition) {
req.next(ex);
} else {
req.next();
}
})- Call a downstream error handler with the
Throwableinstance. - Here,
req.next()is the same asreq.next(ex). In both cases, the downstream error handler is called.
Default Error Handling
If no user-defined error handler is matched, or if the last error handler of the exception called req.next(), 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 exception(req, res) -> throw new HttpException("Amount of money must be greater than 0.", Http.Status.NOT_ACCEPTABLE_406)content_copyOtherwise, the exceptions are translated to an Internal Server Error HTTP error code
500.
Supported Technologies
HTTP/2 Support
Helidon supports HTTP/2 upgrade from HTTP/1, HTTP/2 without 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.StaticContentSupport class to serve files and classpath resources. StaticContentSupport can be created for any readable directory or classpath context root and registered on a path in Routing.
You can combine dynamic handlers with StaticContentSupport 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.builder()
.register("/pictures", StaticContentSupport.create(Paths.get("/some/WEB/pics")))
.register("/", StaticContentSupport.builder("/static-content")
.welcomeFileName("index.html")
.build());- Create a new
StaticContentSupportobject to serve data from the file system, and associate it with the"/pictures"context path. - Create a
StaticContentSupportobject 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 StaticContentSupport 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.
Jersey (JAX-RS) Support
Maven Coordinates
To enable Jersey (JAX-RS) Support add the following dependency to your project’s pom.xml.
<dependency>
<groupId>io.helidon.webserver</groupId>
<artifactId>helidon-webserver-jersey</artifactId>
</dependency>JAX-RS Support
You can register a Jersey (JAX-RS) application at a context root using the JerseySupport class.
Registering a Jersey Application
To register a Jersey application at a context root, use the JerseySupport class and its JerseySupport.Builder builder.
JerseySupport can register the JAX-RS resources directly.
HelloWorld resource@Path("/")
public class HelloWorld {
@GET
@Path("hello")
public Response hello() {
return Response.ok("Hello World!").build();
}
}HelloWorld resourceRouting.builder()
.register("/jersey",
JerseySupport.builder()
.register(HelloWorld.class)
.build())
.build();- Register the Jersey application at
/jerseycontext root - The Jersey
Applicationstays hidden and consists of a singleHelloWorldresource class
As a result, an HTTP GET request to /jersey/hello would yield a Hello World! response string.
Registering a JAX-RS Application
You can also register the JAX-RS Application object.
HelloWorld resourceRouting.builder()
.register("/jersey",
JerseySupport.builder(new MyApplication())
.build())
.build();- Register the Jersey application at
/jerseycontext root MyApplicationhandles requests made to /jersey context root.
Accessing WebServer Internals from a JAX-RS Application
You can inject WebServer request and response objects into your JAX-RS application using @Context.
@Path("/")
@RequestScoped
public class HelloWorld {
@Context
private ServerRequest request;
@Context
private ServerResponse response;
}JSON 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.media</groupId>
<artifactId>helidon-media-jsonp</artifactId>
</dependency>Usage
To enable JSON-P support, first register it with the web server. Then you can add routes that handle and return JSON.
JsonpSupport jsonbSupport = JsonpSupport.create();
WebServer webServer = WebServer.builder()
.addMediaSupport(jsonpSupport)
.build();- Register JsonpSupport to enable transformation from and to
JsonObjectobjects - Register that JsonpSupport instance to enable automatic deserialization of Java objects from and serialization of Java objects to JSON.
private static final JsonBuilderFactory JSON_FACTORY = Json.createBuilderFactory(Collections.emptyMap());
private void sayHello(ServerRequest req, ServerResponse res, JsonObject json) {
JsonObject msg = JSON_FACTORY.createObjectBuilder()
.add("message", "Hello " + json.getString("name"))
.build();
res.send(msg);
}- Using a
JsonBuilderFactoryis more efficient thanJson.createObjectBuilder() - JsonObject is passed to handler
- Create a JsonObject using JSON-P to hold return data
- Send JsonObject in response
curl --noproxy '*' -X POST -H "Content-Type: application/json" \
http://localhost:8080/sayhello -d '{"name":"Joe"}'{"message":"Hello Joe"}Configuring Json Reader/Writer factories
To configure JSON-P JsonReaderFactory and JsonWriterFactory that are used by the JsonpSupport instance, create the JsonpSupport object:
JsonpSupport with the provided configurationJsonpSupport.create(Map.of(JsonGenerator.PRETTY_PRINTING, false));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.media</groupId>
<artifactId>helidon-media-jsonp</artifactId>
</dependency>Usage
To enable JSON-B support, first create and register a JsonbSupport instance with a WebServer.Builder.
JsonbSupport via WebServerJsonbSupport jsonbSupport = JsonbSupport.create();
WebServer webServer = WebServer.builder()
.addMediaSupport(jsonbSupport)
.build();- Create a
JsonbSupportinstance. This instance may be reused freely. - Register that
JsonbSupportinstance to enable automatic deserialization of Java objects from and serialization of Java objects to JSON.
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(final String name) {
this.name = name;
}
}Then you can set up a Handler like this:
Handler that works with Java objects instead of raw JSONfinal Routing routing =
routingBuilder.post("/echo",
Handler.create(Person.class,
(req, res, person) -> res.send(person))))
.build();- Set up a route for
POSTrequests using theRouting.Builder#post(String, Handler…)method - Use the
Handler#create(Class, Handler.EntityHandler)method to install aHandler.EntityHandlerthat works withPersoninstances. - This
Handler.EntityHandlerconsumes aPersoninstance (person) 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"}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.media</groupId>
<artifactId>helidon-media-jackson</artifactId>
</dependency>Usage
To enable Jackson support, first create and register a JacksonSupport instance with a WebServer.Builder.
JacksonSupport via WebServerJacksonSupport jacksonSupport = JacksonSupport.create();
WebServer webServer = WebServer.builder()
.addMediaSupport(jacksonSupport)
.build();- Create a
JacksonSupportinstance. This instance may be reused freely. - Register that
JacksonSupportinstance to enable automatic deserialization of Java objects from and serialization of Java objects to JSON.
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(final String name) {
this.name = name;
}
}Then you can set up a Handler like this:
Handler that works with Java objects instead of raw JSONfinal Routing routing =
routingBuilder.post("/echo",
Handler.create(Person.class,
(req, res, person) -> res.send(person))))
.build();- Set up a route for
POSTrequests using theRouting.Builder#post(String, Handler…)method - Use the
Handler#create(Class, Handler.EntityHandler)method to install aHandler.EntityHandlerthat works withPersoninstances. - This
Handler.EntityHandlerconsumes aPersoninstance (person) 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"}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 Service and as such is executed in the order it is registered with WebServer routing. This implies that if you register it last and another Service or Handler finishes the request, the service will not be invoked.
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
Access log is configured in your code by registering it as a service with Routing
Routing.builder()
.register(AccessLogSupport.create(config.get("server.access-log")))
.get("/greet", myService)The order of registration is significant - make sure AccessLogSupport is registered first (even before security, tracing etc.).
Configuring Access Log in a configuration file
Access log can be configured as follows:
server:
port: 8080
access-log:
format: "%h %l %u %t %r %s %b %{Referer}i"All options shown above are also available programmatically when using builder.
Configuration Options
The following configuration options can be defined:
| Config key | Default value | Builder method | Description |
|---|---|---|---|
enabled | true | enabled(boolean) | When this option is set to false, access logging will be disabled |
logger-name | io.helidon.webserver.AccessLog | loggerName(String) | Name of the logger to use when writing log entries |
format | helidon | helidonLogFormat(), commonLogFormat(), add(AccessLogEntry entry) | Configuration of access log output, when helidon is defined, the Helidon log format (see below) is used. Can be configured to explicitly define log entries (see below as well) |
exclude-paths | N/A | excludePaths(List<String>) | List of path patterns to exclude from access log. Path pattern syntax is as defined in io.helidon.webserver.PathMatcher. Can be used to exclude paths such as /health or /metrics to avoid cluttering log. |
Supported Log Formats
Supported Log Entries
The following log entries are supported in Helidon:
| Config format | Class (to use with builder) | Description |
|---|---|---|
| %h | HostLogEntry | IP address of the remote host |
| %l | UserIdLogEntry | Client identity, always undefined in Helidon |
| %u | UserLogEntry | The username of logged-in user (when Security is used) |
| %t | TimestampLogEntry | The current timestamp |
| %r | RequestLineLogEntry | The request line (method, path and HTTP version) |
| %s | StatusLogEntry | The HTTP status returned to the client |
| %b | SizeLogEntry | The response entity size (if available) |
| %D | TimeTakenLogEntry | The time taken in microseconds |
| %T | TimeTakenLogEntry | The time taken in seconds |
%{header-name}i | HeaderLogEntry | Value of a header (can have multiple such specification to write multiple headers) |
Currently we only support the entries defined above, with NO support for free text.
Helidon Log Format
When format is set to helidon, the format used is:
"%h %u %t %r %s %b %D"
The entries logged:
- IP Address
- Username of a logged-in user
- Timestamp
- Request Line
- HTTP Status code
- Entity size
- Time taken (microseconds)
Access log example:
192.168.0.104 - [18/Jun/2019:22:28:55 +0200] "GET /greet/test HTTP/1.1" 200 53
0:0:0:0:0:0:0:1 - [18/Jun/2019:22:29:00 +0200] "GET /metrics/vendor HTTP/1.1" 200 1658
0:0:0:0:0:0:0:1 jack [18/Jun/2019:22:29:07 +0200] "PUT /greet/greeting HTTP/1.1" 200 21
0:0:0:0:0:0:0:1 jill [18/Jun/2019:22:29:12 +0200] "PUT /greet/greeting HTTP/1.1" 403 0
0:0:0:0:0:0:0:1 - [18/Jun/2019:22:29:17 +0200] "PUT /greet/greeting HTTP/1.1" 401 0TLS Configuration
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.
KeyConfig keyConfig = KeyConfig.keystoreBuilder()
//Whether this keystore is also trust store
.trustStore()
//Keystore location/name
.keystore(Resource.create("keystore.p12"))
//Password to the keystore
.keystorePassphrase("password")
.build();
WebServer.builder()
.tls(WebServerTls.builder()
.trust(keyConfig)
.privateKey(keyConfig)
.build())
.build();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 webClient = WebServer.create(routing, config.get("server"));Or you can only create WebServerTls instance based on the config file.
application.yaml file located on the classpathConfig config = Config.create();
WebServerTls.builder()
.config(config.get("server.tls"))
.build();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.WebServerTls
Configuration options
| key | type | default value | description |
|---|---|---|---|
private-key | Configure private key to use for SSL context. |
| key | type | default value | description |
|---|---|---|---|
cipher-suite | string[] | Set allowed cipher suite. If an empty collection is set, an exception is thrown since it is required to support at least some ciphers. | |
client-auth | ClientAuthentication (REQUIRE, OPTIONAL, NONE) | none | Configures whether client authentication will be required or not. |
enabled | boolean | true | Can be used to disable TLS even if keys are configured. |
session-cache-size | long | Set the size of the cache used for storing SSL session objects. | |
session-timeout-seconds | long | Set the timeout for the cached SSL session objects, in seconds. | |
trust | Set the trust key configuration to be used to validate certificates. |
HTTP Compression
HTTP compression 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 Compression
HTTP compression in the Helidon WebServer is disabled by default. It can sometimes interfere with certain applications that use streaming, even if a compression encoding has not been negotiated with the client.
It can be enabled either programmatically or via configuration, and it can also be enabled on a per-socket basis. When configured at the server level, it applies only to the default socket.
Programmatically, simply use the enableCompression method during server creation:
WebServer.builder()
.port(8080)
.routing(...)
.enableCompression(true) // compression enabled
.build()Or use a config file as follows and make sure the WebServer is created using it:
application.yamlserver:
port: 8080
enable-compression: trueHTTP 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.
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
.create(Routing.builder()
.any((req, res) -> res.send("It works!")))
.start()
.await(10, TimeUnit.SECONDS);
System.out.println("Server started at: http://localhost:" + webServer.port());
}- For any kind of request, at any path, respond with
It works!. - Start the server.
- Wait for the server to start while throwing possible errors as runtime exceptions.
- The server is bound to a random free port.