HTTP server module provides an abstraction over an Apache Tomcat server.

Dependency

<dependency>
  <groupId>io.yupiik.fusion</groupId>
  <artifactId>fusion-http-server</artifactId>
  <version>${fusion.version}</artifactId>
</dependency>
IMPORTANT

annotations - design API - is in fusion-build-api and is only useful at build time.

TIP
set tomcat-catalina in scope runtime to avoid to show the internal API if you don't need them, will also avoid to let your IDE complete javakarta.* transitive dependencies of tomcat.

Usage

By default, if you use Fusion IoC, the webservice will be started. You can customize the configuration listening for WebServer.Configuration event.

Defining an endpoint can be done creating an Endpoint bean and implementing the matcher (matches) and handler which will return a Response thanks the builder:

@Bean
public class Greeting implements Endpoint {
    @Override
    public boolean matches(final Request request) {
        return "GET".equals(request.method());
    }

    @Override
    public CompletionStage<Response> handle(final Request request) {
        return completedFuture(Response.of()
                .body("{\"hello\":true}")
                .build());
    }
}

Configuration

Most of the configuration can be customized using an event listener on WebServer.Configuration and unwrapping the instance as a TomcatWebServerConfiguration you have access to a full Tomcat server customization (HTTP/2.0, WebSocket and so on).

However, there are a few system properties/environment variables (uppercased and with underscores instead of dots) you can set:

  • To skip the initialization of the server at startup: fusion.http-server.start=[true|false]. This can be useful to not start the server in tests for example,

  • To set the HTTP port to use: fusion.http-server.port=<port>, note that setting 0 will make the port random and you can inject WebServer.Configuration to read its value,

  • To set the host to use: fusion.http-server.host=<host>,

  • To set the access log pattern to use: fusion.http-server.accessLogPattern=<...>, see Tomcat documentation for pattern details,

  • To set the webapp directory: fusion.http-server.base=/path/to/www. It can be useful to serve static websites if you configure the right servlets,

  • To set the fusion default servlet mapping: fusion.http-server.fusionServletMapping=/. It can be useful if you want to bind it to a subcontext to use standard servlets for other things like serving base directory,

  • To set if UTF-8 is enforced (default) or not over the requests/responses: fusion.http-server.utf8Setup=true.

Finally, you can also set by default the deployment of a monitoring server (for health checks or metrics depending your application). This is done setting fusion.http-server.monitoring.enabled to true, optionally fusion.http-server.monitoring.port to something else than 8081. The deployed endpoints in this context will be the MonitoringEndpoint bean instances.

Here is a sample MonitoringEndpoint:

@DefaultScoped
public class Health implements MonitoringEndpoint {
    private final TransactionManager transactionManager;

    public Health(final TransactionManager transactionManager) {
        this.transactionManager = transactionManager;
    }

    @Override
    public boolean matches(final Request request) {
        return "GET".equalsIgnoreCase(request.method()) && "/metrics".equalsIgnoreCase(request.path());
    }

    @Override
    public CompletionStage<Response> unsafeHandle(final Request request) {
        return transactionManager
                .read(c -> {
                    try {
                        if (!c.isValid(30_000)) {
                            return Optional.of(fail("Timeout"));
                        }
                        return Optional.<CompletionStage<Response>>empty();
                    } catch (final SQLException e) {
                        return Optional.of(fail(e.getMessage()));
                    }
                })
                .orElseGet(() -> completedStage(Response.of().body("OK").build()));
    }
}

High level API

The first option to define an endpoint as a bean - automatically picked - is to use Endpoint.of API:

@Bean
public Endpoint myGreetingEndpoint() {
    return Endpoint.of(
            // matching impl
            request -> "GET".equals(request.method()) &&
                request.path().startsWith("/greet") &&
                request.query() != null &&
                request.query().startsWith("name="),
            // endpoint impl - can be delegated to any bean
            request -> completableStage(Response.of()
                .status(200)
                .header("content-type", "application/json")
                .body(jsonMapper.toString(
                        new Greet(request.query().substring("name=".length()))))
                .build()));
}

The alternative is to use @HttpMatcher API:

@HttpMatcher(method = "GET", pathMatching = EXACT, path = "/greet")
public CompletionStage<Response> myGreetingEndpoint() {
    return completableStage(Response.of()
        .status(200)
        .header("content-type", "application/json")
        .body(jsonMapper.toString(
                new Greet(request.query().substring("name=".length()))))
        .build());
}
TIP

if your endpoint is fully synchronous you can drop the CompletionStage wrapper: public CompletionStage<Response> myGreetingEndpoint();. You can also pass as first parameter a Request parameter.

(Open) Tracing

fusion-tracing module provides a Tomcat valve you can set up on your web container to add tracing capabilities to your Tomcat:

serverConfiguration
    .unwrap(TomcatWebServerConfiguration.class)
    .setContextCustomizers(List.of(c -> c.getPipeline() (1)
        .addValve(new TracingValve( (1)
            new ServerTracingConfiguration(), (2)
            new AccumulatingSpanCollector().setOnFlush(...), (3)
            new IdGenerator(IdGenerator.Type.HEX), (4)
            systemUTC())))); (5)
  1. Add the valve to the context pipeline, it is recommended to add it as early as possible (just after error report and access log valve in general),
  2. The configuration enables to customize the span tags and headers to read for span propagation,
  3. The accumulator is what will send/log/... the spans once aggregated, ensure to configure it as needed,
  4. The IdGenerator provides the span/trace identifiers, it must be compatible with your collector ( hex for zipkin for example),
  5. Finally the clock enables to timestamp the span and compute its duration.
IMPORTANT

if you reuse AccumulatingSpanCollector, it is automatically closed with the valve "stop" phase. You can combine the accumulator with ZipkinFlusher or OpenTelemetryFlusher onFlush implementation to flush to a zipkin collector v2.