HTTP Server
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 |
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 injectWebServer.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 |
(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)
- 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),
- The configuration enables to customize the span tags and headers to read for span propagation,
- The accumulator is what will send/log/... the spans once aggregated, ensure to configure it as needed,
-
The
IdGenerator
provides the span/trace identifiers, it must be compatible with your collector (hex
for zipkin for example), - Finally the clock enables to timestamp the span and compute its duration.
IMPORTANT
|
if you reuse |