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>
Copied

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();
Copied

Configuring the WebServer in a Configuration File

You can also define the configuration in a file.

WebServer configuration file application.yaml
server:
  port: 8080
  bind-address: "0.0.0.0"
Copied

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

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

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

Optional configuration options
keytypedefault valuedescription
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-policy what happens if max buffer size is reached.

backpressure-strategy

BackpressureStrategy (LINEAR, AUTO_FLUSH, PREFETCH, UNBOUNDED)

LINEAR

Sets a backpressure strategy for the server to apply against user provided response upstream.

  • LINEAR - Data are requested one-by-one, in case buffer reaches watermark, no other data is requested.

  • AUTO_FLUSH - Data are requested one-by-one, in case buffer reaches watermark, no other data is requested.

  • PREFETCH - After first data chunk arrives, probable number of chunks needed to fill the buffer up to watermark is calculated and requested.

  • NONE - No backpressure is applied, Long.MAX_VALUE(unbounded) is requested from 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 true to print detailed feature information on startup.

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 0 then any available ephemeral port will be used.

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 false

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 CPU_COUNT * 2.

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.

Using Routing.Builder to specify how HTTP requests are handled
Routing routing = Routing.builder()
                         .get("/hello", (req, res) -> res.send("Hello World!")) 
                         .build();

WebServer webServer = WebServer.create(routing); 
Copied
  • Handle all GETs to /hello path. Send the Hello World! string.
  • Add the routing to the WebServer.

HTTP Method Routing

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

HTTP MethodRouting.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 */ })
Copied

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

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

  • /foo/{}/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()
Copied

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.

Use Routing.Builder.register to register your service
.register("/hello", new HelloService())
Copied
Service implementation
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
    }
}
Copied

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(); 
    })
    Copied
    • handler for any HTTP method using the /hello path
    • business logic implementation
    • forward the current request to the downstream handler
  • 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."); 
        }
    })
    Copied
    • handler for any HTTP method using the /hello path
    • custom logic
    • forward the current request to the downstream handler
    • forward the request to the error handler

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!"); 
})
Copied
  • handler that terminates the request handling for any HTTP method using the /hello path
  • send the response

Protocol Specific Routing

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

Routing based on HTTP version
.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")))
)
Copied

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 the Forwarded header

    • X_FORWARDED - uses the X-Forwarded-* headers

    • HOST - uses the Host header

  • what intermediate nodes to trust

When your application invokes request.requestedUri() Helidon iterates through the discovery types you set up for the receiving 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:

Requested URI set-up for the default server socket
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);
Copied
  • Create the AllowList describing the intermediate networks nodes to trust and not trust. Presumably the lbxxx.mycorp.com nodes are trusted load balancers except for the test load balancer lbtest, and no other nodes are trusted. AllowList accepts prefixes, suffixes, predicates, regex patterns, and exact matches. See the AllowList JavaDoc for complete information.
  • Use Forwarded first, then try X-Forwarded-* on each request.
  • Set the AllowList for trusted intermediaries.

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

Setting Up Requested URI Discovery using Configuration

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

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

Obtaining the Requested URI Information

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

Retrieving Requested URI Information
import io.helidon.common.http.UriInfo;

public class MyHandler implements Handler {

    @Override
    public void accept(ServerRequest req, ServerResponse res) {
        UriInfo uriInfo = req.requestedUri();
        // ...
    }
}
Copied

See the UriInfo JavaDoc for more information.

Error Handling

Error Routing

You may register an error handler for a specific Throwable in 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
Copied
  • Registers an error handler that handles MyException that are thrown from the upstream handlers
  • Finishes the request handling by sending a response

Error handlers are called when

  • an exception is thrown from a handler

  • req.next(ex) is called, where ex is an instance of Throwable

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());
    })
    Copied
  • or, forward the error handling to the downstream error handlers

    .error(Throwable.class, (req, res, ex) -> {
        // some logic
        req.next(ex);
    })
    Copied

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(); 
    }
})
Copied
  • Call a downstream error handler with the Throwable instance.
  • Here, req.next() is the same as req.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 HttpException are translated to their associated HTTP error codes.

    Reply with the 406 HTTP error code by throwing an exception
    (req, res) -> throw new HttpException("Amount of money must be greater than 0.", Http.Status.NOT_ACCEPTABLE_406) 
    Copied
  • Otherwise, 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>
Copied

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>
Copied

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());
Copied
  • Create a new StaticContentSupport object to serve data from the file system, and associate it with the "/pictures" context path.
  • Create a StaticContentSupport object to serve resources from the contextual ClassLoader. The specific classloader can be also defined. A builder lets you provide more configuration values.
  • index.html is the file that is returned if a directory is requested.

A 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>
Copied

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.

Jersey (JAX-RS) HelloWorld resource
@Path("/")
public class HelloWorld {

    @GET
    @Path("hello")
    public Response hello() {
        return Response.ok("Hello World!").build();
    }
}
Copied
Registering the HelloWorld resource
Routing.builder()
       .register("/jersey", 
                 JerseySupport.builder()
                              .register(HelloWorld.class) 
                              .build())
       .build();
Copied
  • Register the Jersey application at /jersey context root
  • The Jersey Application stays hidden and consists of a single HelloWorld resource 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.

Register the HelloWorld resource
Routing.builder()
       .register("/jersey", 
                 JerseySupport.builder(new MyApplication()) 
                              .build())
       .build();
Copied
  • Register the Jersey application at /jersey context root
  • MyApplication handles 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.

Injection of WebServer internal objects
@Path("/")
@RequestScoped
public class HelloWorld {
    @Context
    private ServerRequest request;

    @Context
    private ServerResponse response;
}
Copied

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>
Copied

Usage

To enable JSON-P support, first register it with the web server. Then you can add routes that handle and return JSON.

Configure JsonpSupport and use it for reading and writing of entities
JsonpSupport jsonbSupport = JsonpSupport.create(); 
WebServer webServer = WebServer.builder()
    .addMediaSupport(jsonpSupport) 
    .build();
Copied
  • Register JsonpSupport to enable transformation from and to JsonObject objects
  • Register that JsonpSupport instance to enable automatic deserialization of Java objects from and serialization of Java objects to JSON.
Handler that receives and returns JSON objects
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);                            
}
Copied
  • Using a JsonBuilderFactory is more efficient than Json.createObjectBuilder()
  • JsonObject is passed to handler
  • Create a JsonObject using JSON-P to hold return data
  • Send JsonObject in response
Example of posting JSON to sayHello endpoint
curl --noproxy '*' -X POST -H "Content-Type: application/json" \
    http://localhost:8080/sayhello -d '{"name":"Joe"}'
Copied
Response body
{"message":"Hello Joe"}
Copied

Configuring Json Reader/Writer factories

To configure JSON-P JsonReaderFactory and JsonWriterFactory that are used by the JsonpSupport instance, create the JsonpSupport object:

Create JsonpSupport with the provided configuration
JsonpSupport.create(Map.of(JsonGenerator.PRETTY_PRINTING, false));
Copied

JSON-B Support

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

Maven Coordinates

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

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

Usage

To enable JSON-B support, first create and register a JsonbSupport instance with a WebServer.Builder.

Registration of the JsonbSupport via WebServer
JsonbSupport jsonbSupport = JsonbSupport.create(); 
WebServer webServer = WebServer.builder()
    .addMediaSupport(jsonbSupport) 
    .build();
Copied
  • Create a JsonbSupport instance. This instance may be reused freely.
  • Register that JsonbSupport instance 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:

Hypothetical Person class
public class Person {

    private String name;

    public Person() {
        super();
    }

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

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

Then you can set up a Handler like this:

A Handler that works with Java objects instead of raw JSON
final Routing routing =
    routingBuilder.post("/echo", 
                        Handler.create(Person.class, 
                                       (req, res, person) -> res.send(person)))) 
    .build();
Copied
Example of posting JSON to the /echo endpoint
curl --noproxy '*' -X POST -H "Content-Type: application/json" \
    http://localhost:8080/echo -d '{"name":"Joe"}'
{"name":"Joe"}
Copied

Jackson Support

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

Maven Coordinates

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

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

Usage

To enable Jackson support, first create and register a JacksonSupport instance with a WebServer.Builder.

Registration of the JacksonSupport via WebServer
JacksonSupport jacksonSupport = JacksonSupport.create(); 
WebServer webServer = WebServer.builder()
    .addMediaSupport(jacksonSupport) 
    .build();
Copied
  • Create a JacksonSupport instance. This instance may be reused freely.
  • Register that JacksonSupport instance 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:

Hypothetical Person class
public class Person {

    private String name;

    public Person() {
        super();
    }

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

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

Then you can set up a Handler like this:

A Handler that works with Java objects instead of raw JSON
final Routing routing =
    routingBuilder.post("/echo", 
                        Handler.create(Person.class, 
                                       (req, res, person) -> res.send(person)))) 
    .build();
Copied
Example of posting JSON to the /echo endpoint
curl --noproxy '*' -X POST -H "Content-Type: application/json" \
    http://localhost:8080/echo -d '{"name":"Joe"}'
Copied
Response body
{"name":"Joe"}
Copied

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>
Copied

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)
Copied

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:

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

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

Configuration Options

The following configuration options can be defined:

Config keyDefault valueBuilder methodDescription
enabledtrueenabled(boolean)When this option is set to false, access logging will be disabled
logger-nameio.helidon.webserver.AccessLogloggerName(String)Name of the logger to use when writing log entries
formathelidonhelidonLogFormat(), 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-pathsN/AexcludePaths(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 formatClass (to use with builder)Description
%hHostLogEntryIP address of the remote host
%lUserIdLogEntryClient identity, always undefined in Helidon
%uUserLogEntryThe username of logged-in user (when Security is used)
%tTimestampLogEntryThe current timestamp
%rRequestLineLogEntryThe request line (method, path and HTTP version)
%sStatusLogEntryThe HTTP status returned to the client
%bSizeLogEntryThe response entity size (if available)
%DTimeTakenLogEntryThe time taken in microseconds
%TTimeTakenLogEntryThe time taken in seconds
%{header-name}iHeaderLogEntryValue 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:

  1. IP Address
  2. Username of a logged-in user
  3. Timestamp
  4. Request Line
  5. HTTP Status code
  6. Entity size
  7. 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 0
Copied

TLS 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();
Copied

Configuring TLS in the config file

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

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

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

WebServer initialization using the application.yaml file located on the classpath
Config config = Config.create();
WebServer webClient = WebServer.create(routing, config.get("server"));
Copied

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();
WebServerTls.builder()
    .config(config.get("server.tls"))
    .build();
Copied

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

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

Configuration options

Type: io.helidon.webserver.WebServerTls

Configuration options

Required configuration options
keytypedefault valuedescription
private-key 

Configure private key to use for SSL context.

Optional configuration options
keytypedefault valuedescription
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. 0 to use the default value.

session-timeout-seconds

long

 

Set the timeout for the cached SSL session objects, in seconds. 0 to use the default value.

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()
Copied

Or use a config file as follows and make sure the WebServer is created using it:

WebServer HTTP Compression configuration file application.yaml
server:
  port: 8080
  enable-compression: true
Copied

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.

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()); 
}
Copied
  • 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.

Reference