Core Concepts
Request Lifecycle
Soklet provides two lifecycle hooks:
- RequestInterceptor for adjusting request flow (wrapping or intercepting)
- LifecycleObserver for read-only observation of system and request events
They're useful for tasks like:
- Logging requests and responses
- Performing authentication and authorization
- Modifying requests/responses before downstream processing occurs
- Wrapping downstream code in a database transaction
- Monitoring unexpected errors that occur during internal processing (see Event Logging)
This is similar to the Jakarta EE Servlet Filter concept, but provides additional functionality beyond "wrap the whole request".
Request Interceptor
Request Wrapping
Wraps around the whole "outside" of an entire request-handling flow. The "inside" of the flow is everything from Resource Method execution to writing response bytes to the client. For a more fine-grained approach, see Request Intercepting.
Wrapping happens before Soklet resolves which Resource Method should handle the request. This means you can rewrite the HTTP method or path by returning a modified request via the consumer and Soklet will route using the wrapped request. You must call requestProcessor.accept(...) exactly once before returning; otherwise Soklet logs an error and returns a 500 response.
Wrapping a request is useful when you'd like to store off request-scoped information that needs to be made easily accessible to other parts of the system - for example, a request's Locale and ZoneId.
To implement, a useful scoping mechanism is Java's ScopedValue<T> (Java 25+) or ThreadLocal<T>. The former is demonstrated below.
// Special scoped value so anyone can access the current Locale.
// For Java < 25, use ThreadLocal instead
public static final ScopedValue<Locale> CURRENT_LOCALE;
// Spin up the ScopedValue (or ThreadLocal)
static {
CURRENT_LOCALE = ScopedValue.newInstance();
}
SokletConfig config = SokletConfig.withServer(
Server.fromPort(8080)
).requestInterceptor(new RequestInterceptor() {
@Override
public void wrapRequest(
@NonNull ServerType serverType,
@NonNull Request request,
@NonNull Consumer<Request> requestProcessor
) {
// Make the locale accessible by other code during this request...
Locale locale = request.getLocales().get(0);
// ...by binding it to a ScopedValue (or ThreadLocal).
ScopedValue.where(CURRENT_LOCALE, locale).run(() -> {
// You must call this so downstream processing can proceed
requestProcessor.accept(request);
});
}
}).build();
// Special scoped value so anyone can access the current Locale.
// For Java < 25, use ThreadLocal instead
public static final ScopedValue<Locale> CURRENT_LOCALE;
// Spin up the ScopedValue (or ThreadLocal)
static {
CURRENT_LOCALE = ScopedValue.newInstance();
}
SokletConfig config = SokletConfig.withServer(
Server.fromPort(8080)
).requestInterceptor(new RequestInterceptor() {
@Override
public void wrapRequest(
@NonNull ServerType serverType,
@NonNull Request request,
@NonNull Consumer<Request> requestProcessor
) {
// Make the locale accessible by other code during this request...
Locale locale = request.getLocales().get(0);
// ...by binding it to a ScopedValue (or ThreadLocal).
ScopedValue.where(CURRENT_LOCALE, locale).run(() -> {
// You must call this so downstream processing can proceed
requestProcessor.accept(request);
});
}
}).build();
Then, elsewhere in your code while a request is being processed:
class ExampleService {
void accessCurrentLocale() {
// You now have access to the Locale bound to the logical scope
// (or Thread) without having to pass it down the call stack
Locale locale = CURRENT_LOCALE.orElse(Locale.getDefault());
}
}
class ExampleService {
void accessCurrentLocale() {
// You now have access to the Locale bound to the logical scope
// (or Thread) without having to pass it down the call stack
Locale locale = CURRENT_LOCALE.orElse(Locale.getDefault());
}
}
References:
Request Intercepting
Conceptually, when a request comes in, Soklet will:
- Invoke the appropriate Resource Method to acquire a response
- Send the response over the wire to the client
Request Intercepting provides programmatic control over those two steps.
Example use cases are:
- Wrapping Resource Methods with a database transaction (e.g. to ensure atomicity)
- Customizing responses prior to sending over the wire
For a more coarse-grained approach, see Request Wrapping.
You must call responseWriter.accept(...) exactly once before returning; otherwise Soklet logs an error and returns a 500 response.
SokletConfig config = SokletConfig.withServer(
Server.fromPort(8080)
).requestInterceptor(new RequestInterceptor() {
@Override
public void interceptRequest(
@NonNull ServerType serverType,
@NonNull Request request,
@Nullable ResourceMethod resourceMethod,
@NonNull Function<Request, MarshaledResponse> responseGenerator,
@NonNull Consumer<MarshaledResponse> responseWriter
) {
// Here's where you might start a DB transaction
MyDatabase.INSTANCE.beginTransaction();
// Step 1: Invoke the Resource Method and acquire its response
MarshaledResponse response = responseGenerator.apply(request);
// Commit the DB transaction before marshaling/sending the response
// to reduce contention by keeping "open" time short
MyDatabase.INSTANCE.commitTransaction();
// You might also perform systemwide response adjustments here.
// For example, set a special header via mutable copy
response = response.copy().headers((mutableHeaders) -> {
mutableHeaders.put("X-Powered-By", Set.of("Soklet"));
}).finish();
// Step 2: Send the finalized response over the wire
responseWriter.accept(response);
}
}).build();
SokletConfig config = SokletConfig.withServer(
Server.fromPort(8080)
).requestInterceptor(new RequestInterceptor() {
@Override
public void interceptRequest(
@NonNull ServerType serverType,
@NonNull Request request,
@Nullable ResourceMethod resourceMethod,
@NonNull Function<Request, MarshaledResponse> responseGenerator,
@NonNull Consumer<MarshaledResponse> responseWriter
) {
// Here's where you might start a DB transaction
MyDatabase.INSTANCE.beginTransaction();
// Step 1: Invoke the Resource Method and acquire its response
MarshaledResponse response = responseGenerator.apply(request);
// Commit the DB transaction before marshaling/sending the response
// to reduce contention by keeping "open" time short
MyDatabase.INSTANCE.commitTransaction();
// You might also perform systemwide response adjustments here.
// For example, set a special header via mutable copy
response = response.copy().headers((mutableHeaders) -> {
mutableHeaders.put("X-Powered-By", Set.of("Soklet"));
}).finish();
// Step 2: Send the finalized response over the wire
responseWriter.accept(response);
}
}).build();
References:
Lifecycle Observer
Soklet Start/Stop
Execute code immediately before and after a Soklet instance starts up and shuts down.
SokletConfig config = SokletConfig.withServer(
Server.fromPort(8080)
).lifecycleObserver(new LifecycleObserver() {
@Override
public void willStartSoklet(@NonNull Soklet soklet) {
// Perform startup tasks required prior to Soklet launch
MyPayrollSystem.INSTANCE.startLengthyWarmupProcess();
}
@Override
public void didStartSoklet(@NonNull Soklet soklet) {
// Soklet has fully started.
// Server (and optionally SSE server) are running
System.out.println("Soklet started.");
}
@Override
public void didFailToStartSoklet(
@NonNull Soklet soklet,
@NonNull Throwable throwable
) {
System.err.println("Soklet failed to start.");
throwable.printStackTrace();
}
@Override
public void willStopSoklet(@NonNull Soklet soklet) {
// Perform shutdown tasks required prior to Soklet teardown
MyPayrollSystem.INSTANCE.destroy();
}
@Override
public void didStopSoklet(@NonNull Soklet soklet) {
// Soklet has fully shut down
System.out.println("Soklet stopped.");
}
@Override
public void didFailToStopSoklet(
@NonNull Soklet soklet,
@NonNull Throwable throwable
) {
System.err.println("Soklet failed to stop cleanly.");
throwable.printStackTrace();
}
}).build();
SokletConfig config = SokletConfig.withServer(
Server.fromPort(8080)
).lifecycleObserver(new LifecycleObserver() {
@Override
public void willStartSoklet(@NonNull Soklet soklet) {
// Perform startup tasks required prior to Soklet launch
MyPayrollSystem.INSTANCE.startLengthyWarmupProcess();
}
@Override
public void didStartSoklet(@NonNull Soklet soklet) {
// Soklet has fully started.
// Server (and optionally SSE server) are running
System.out.println("Soklet started.");
}
@Override
public void didFailToStartSoklet(
@NonNull Soklet soklet,
@NonNull Throwable throwable
) {
System.err.println("Soklet failed to start.");
throwable.printStackTrace();
}
@Override
public void willStopSoklet(@NonNull Soklet soklet) {
// Perform shutdown tasks required prior to Soklet teardown
MyPayrollSystem.INSTANCE.destroy();
}
@Override
public void didStopSoklet(@NonNull Soklet soklet) {
// Soklet has fully shut down
System.out.println("Soklet stopped.");
}
@Override
public void didFailToStopSoklet(
@NonNull Soklet soklet,
@NonNull Throwable throwable
) {
System.err.println("Soklet failed to stop cleanly.");
throwable.printStackTrace();
}
}).build();
References:
LifecycleObserver::willStartSokletLifecycleObserver::didStartSokletLifecycleObserver::didFailToStartSokletLifecycleObserver::willStopSokletLifecycleObserver::didStopSokletLifecycleObserver::didFailToStopSoklet
Server Start/Stop
Execute code immediately before and after the Soklet-managed Server starts up and shuts down.
SokletConfig config = SokletConfig.withServer(
Server.fromPort(8080)
).lifecycleObserver(new LifecycleObserver() {
@Override
public void willStartServer(@NonNull Server server) {
// Perform startup tasks required prior to server launch
MyPayrollSystem.INSTANCE.startLengthyWarmupProcess();
}
@Override
public void didStartServer(@NonNull Server server) {
// Server has fully started up and is listening
System.out.println("Server started.");
}
@Override
public void didFailToStartServer(
@NonNull Server server,
@NonNull Throwable throwable
) {
System.err.println("Server failed to start.");
throwable.printStackTrace();
}
@Override
public void willStopServer(@NonNull Server server) {
// Perform shutdown tasks required prior to server teardown
MyPayrollSystem.INSTANCE.destroy();
}
@Override
public void didStopServer(@NonNull Server server) {
// Server has fully shut down
System.out.println("Server stopped.");
}
@Override
public void didFailToStopServer(
@NonNull Server server,
@NonNull Throwable throwable
) {
System.err.println("Server failed to stop cleanly.");
throwable.printStackTrace();
}
}).build();
SokletConfig config = SokletConfig.withServer(
Server.fromPort(8080)
).lifecycleObserver(new LifecycleObserver() {
@Override
public void willStartServer(@NonNull Server server) {
// Perform startup tasks required prior to server launch
MyPayrollSystem.INSTANCE.startLengthyWarmupProcess();
}
@Override
public void didStartServer(@NonNull Server server) {
// Server has fully started up and is listening
System.out.println("Server started.");
}
@Override
public void didFailToStartServer(
@NonNull Server server,
@NonNull Throwable throwable
) {
System.err.println("Server failed to start.");
throwable.printStackTrace();
}
@Override
public void willStopServer(@NonNull Server server) {
// Perform shutdown tasks required prior to server teardown
MyPayrollSystem.INSTANCE.destroy();
}
@Override
public void didStopServer(@NonNull Server server) {
// Server has fully shut down
System.out.println("Server stopped.");
}
@Override
public void didFailToStopServer(
@NonNull Server server,
@NonNull Throwable throwable
) {
System.err.println("Server failed to stop cleanly.");
throwable.printStackTrace();
}
}).build();
References:
LifecycleObserver::willStartServerLifecycleObserver::didStartServerLifecycleObserver::didFailToStartServerLifecycleObserver::willStopServerLifecycleObserver::didStopServerLifecycleObserver::didFailToStopServer
Connection Acceptance
These methods fire when Soklet is about to accept a TCP connection, after it accepts one, or when it fails to accept a connection attempt. Each callback provides a ServerType, a best-effort remote address, and (for failures) a ConnectionRejectionReason plus an optional exception.
SokletConfig config = SokletConfig.withServer(
Server.fromPort(8080)
).lifecycleObserver(new LifecycleObserver() {
@Override
public void willAcceptConnection(
@NonNull ServerType serverType,
@Nullable InetSocketAddress remoteAddress
) {
System.out.printf("Incoming %s connection from %s\n", serverType, remoteAddress);
}
@Override
public void didAcceptConnection(
@NonNull ServerType serverType,
@Nullable InetSocketAddress remoteAddress
) {
System.out.printf("Accepted %s connection from %s\n", serverType, remoteAddress);
}
@Override
public void didFailToAcceptConnection(
@NonNull ServerType serverType,
@Nullable InetSocketAddress remoteAddress,
@NonNull ConnectionRejectionReason reason,
@Nullable Throwable throwable
) {
System.out.printf("Failed to accept %s connection from %s (%s)\n", serverType, remoteAddress, reason);
}
}).build();
SokletConfig config = SokletConfig.withServer(
Server.fromPort(8080)
).lifecycleObserver(new LifecycleObserver() {
@Override
public void willAcceptConnection(
@NonNull ServerType serverType,
@Nullable InetSocketAddress remoteAddress
) {
System.out.printf("Incoming %s connection from %s\n", serverType, remoteAddress);
}
@Override
public void didAcceptConnection(
@NonNull ServerType serverType,
@Nullable InetSocketAddress remoteAddress
) {
System.out.printf("Accepted %s connection from %s\n", serverType, remoteAddress);
}
@Override
public void didFailToAcceptConnection(
@NonNull ServerType serverType,
@Nullable InetSocketAddress remoteAddress,
@NonNull ConnectionRejectionReason reason,
@Nullable Throwable throwable
) {
System.out.printf("Failed to accept %s connection from %s (%s)\n", serverType, remoteAddress, reason);
}
}).build();
References:
LifecycleObserver::willAcceptConnectionLifecycleObserver::didAcceptConnectionLifecycleObserver::didFailToAcceptConnection
Request Acceptance
These hooks fire as Soklet accepts requests for application-level handling and reads/parses them into Request instances.
SokletConfig config = SokletConfig.withServer(
Server.fromPort(8080)
).lifecycleObserver(new LifecycleObserver() {
@Override
public void willAcceptRequest(
@NonNull ServerType serverType,
@Nullable InetSocketAddress remoteAddress,
@Nullable String requestTarget
) {
System.out.printf("About to accept request: %s%n", requestTarget);
}
@Override
public void didAcceptRequest(
@NonNull ServerType serverType,
@Nullable InetSocketAddress remoteAddress,
@Nullable String requestTarget
) {
System.out.printf("Accepted request: %s%n", requestTarget);
}
@Override
public void didFailToAcceptRequest(
@NonNull ServerType serverType,
@Nullable InetSocketAddress remoteAddress,
@Nullable String requestTarget,
@NonNull RequestRejectionReason reason,
@Nullable Throwable throwable
) {
System.err.printf("Failed to accept request (%s): %s%n", reason, requestTarget);
}
@Override
public void willReadRequest(
@NonNull ServerType serverType,
@Nullable InetSocketAddress remoteAddress,
@Nullable String requestTarget
) {
System.out.printf("About to read request: %s%n", requestTarget);
}
@Override
public void didReadRequest(
@NonNull ServerType serverType,
@Nullable InetSocketAddress remoteAddress,
@Nullable String requestTarget
) {
System.out.printf("Read request: %s%n", requestTarget);
}
@Override
public void didFailToReadRequest(
@NonNull ServerType serverType,
@Nullable InetSocketAddress remoteAddress,
@Nullable String requestTarget,
@NonNull RequestReadFailureReason reason,
@Nullable Throwable throwable
) {
System.err.printf("Failed to read request (%s): %s%n", reason, requestTarget);
}
}).build();
SokletConfig config = SokletConfig.withServer(
Server.fromPort(8080)
).lifecycleObserver(new LifecycleObserver() {
@Override
public void willAcceptRequest(
@NonNull ServerType serverType,
@Nullable InetSocketAddress remoteAddress,
@Nullable String requestTarget
) {
System.out.printf("About to accept request: %s%n", requestTarget);
}
@Override
public void didAcceptRequest(
@NonNull ServerType serverType,
@Nullable InetSocketAddress remoteAddress,
@Nullable String requestTarget
) {
System.out.printf("Accepted request: %s%n", requestTarget);
}
@Override
public void didFailToAcceptRequest(
@NonNull ServerType serverType,
@Nullable InetSocketAddress remoteAddress,
@Nullable String requestTarget,
@NonNull RequestRejectionReason reason,
@Nullable Throwable throwable
) {
System.err.printf("Failed to accept request (%s): %s%n", reason, requestTarget);
}
@Override
public void willReadRequest(
@NonNull ServerType serverType,
@Nullable InetSocketAddress remoteAddress,
@Nullable String requestTarget
) {
System.out.printf("About to read request: %s%n", requestTarget);
}
@Override
public void didReadRequest(
@NonNull ServerType serverType,
@Nullable InetSocketAddress remoteAddress,
@Nullable String requestTarget
) {
System.out.printf("Read request: %s%n", requestTarget);
}
@Override
public void didFailToReadRequest(
@NonNull ServerType serverType,
@Nullable InetSocketAddress remoteAddress,
@Nullable String requestTarget,
@NonNull RequestReadFailureReason reason,
@Nullable Throwable throwable
) {
System.err.printf("Failed to read request (%s): %s%n", reason, requestTarget);
}
}).build();
References:
LifecycleObserver::willAcceptRequestLifecycleObserver::didAcceptRequestLifecycleObserver::didFailToAcceptRequestLifecycleObserver::willReadRequestLifecycleObserver::didReadRequestLifecycleObserver::didFailToReadRequestRequestReadFailureReasonRequestRejectionReason
Request Handling
These methods are fired at the very start of request processing and the very end, respectively.
SokletConfig config = SokletConfig.withServer(
Server.fromPort(8080)
).lifecycleObserver(new LifecycleObserver() {
@Override
public void didStartRequestHandling(
@NonNull ServerType serverType,
@NonNull Request request,
@Nullable ResourceMethod resourceMethod
) {
System.out.printf("Received request: %s\n", request);
// If there was no resourceMethod matching the request, expect a 404
if(resourceMethod != null)
System.out.printf("Request to be handled by: %s\n", resourceMethod);
else
System.out.println("This will be a 404.");
}
@Override
public void didFinishRequestHandling(
@NonNull ServerType serverType,
@NonNull Request request,
@Nullable ResourceMethod resourceMethod,
@NonNull MarshaledResponse marshaledResponse,
@NonNull Duration processingDuration,
@NonNull List<Throwable> throwables
) {
// We have access to a few things here...
// * marshaledResponse is what was ultimately sent
// over the wire
// * processingDuration is how long everything took,
// including sending the response to the client
// * throwables is the ordered list of exceptions
// thrown during execution (if any)
double millis = processingDuration.toNanos() / 1_000_000.0;
System.out.printf("Entire request took %dms\n", millis);
}
}).build();
SokletConfig config = SokletConfig.withServer(
Server.fromPort(8080)
).lifecycleObserver(new LifecycleObserver() {
@Override
public void didStartRequestHandling(
@NonNull ServerType serverType,
@NonNull Request request,
@Nullable ResourceMethod resourceMethod
) {
System.out.printf("Received request: %s\n", request);
// If there was no resourceMethod matching the request, expect a 404
if(resourceMethod != null)
System.out.printf("Request to be handled by: %s\n", resourceMethod);
else
System.out.println("This will be a 404.");
}
@Override
public void didFinishRequestHandling(
@NonNull ServerType serverType,
@NonNull Request request,
@Nullable ResourceMethod resourceMethod,
@NonNull MarshaledResponse marshaledResponse,
@NonNull Duration processingDuration,
@NonNull List<Throwable> throwables
) {
// We have access to a few things here...
// * marshaledResponse is what was ultimately sent
// over the wire
// * processingDuration is how long everything took,
// including sending the response to the client
// * throwables is the ordered list of exceptions
// thrown during execution (if any)
double millis = processingDuration.toNanos() / 1_000_000.0;
System.out.printf("Entire request took %dms\n", millis);
}
}).build();
References:
Response Writing
Monitor the response writing process - sending bytes over the wire. You can respond to successful writes or failed writes (e.g. unexpected client disconnect).
SokletConfig config = SokletConfig.withServer(
Server.fromPort(8080)
).lifecycleObserver(new LifecycleObserver() {
@Override
public void willWriteResponse(
@NonNull ServerType serverType,
@NonNull Request request,
@Nullable ResourceMethod resourceMethod,
@NonNull MarshaledResponse marshaledResponse
) {
// Access to marshaledResponse here lets us see exactly
// what will be going over the wire
byte[] body = marshaledResponse.getBody().orElse(new byte[] {});
System.out.printf("About to start writing response with " +
"a %d-byte body...\n", body.length);
}
@Override
public void didWriteResponse(
@NonNull ServerType serverType,
@NonNull Request request,
@Nullable ResourceMethod resourceMethod,
@NonNull MarshaledResponse marshaledResponse,
@NonNull Duration responseWriteDuration
) {
double millis = responseWriteDuration.toNanos() / 1_000_000.0;
System.out.printf("Took %dms to write response\n", millis);
}
@Override
public void didFailToWriteResponse(
@NonNull ServerType serverType,
@NonNull Request request,
@Nullable ResourceMethod resourceMethod,
@NonNull MarshaledResponse marshaledResponse,
@NonNull Duration responseWriteDuration,
@NonNull Throwable throwable
) {
double millis = responseWriteDuration.toNanos() / 1_000_000.0;
System.out.printf("Response write failed after %dms\n", millis);
// Use this to monitor trends in unexpected client disconnects
System.err.println("Exception occurred while writing response");
throwable.printStackTrace();
}
}).build();
SokletConfig config = SokletConfig.withServer(
Server.fromPort(8080)
).lifecycleObserver(new LifecycleObserver() {
@Override
public void willWriteResponse(
@NonNull ServerType serverType,
@NonNull Request request,
@Nullable ResourceMethod resourceMethod,
@NonNull MarshaledResponse marshaledResponse
) {
// Access to marshaledResponse here lets us see exactly
// what will be going over the wire
byte[] body = marshaledResponse.getBody().orElse(new byte[] {});
System.out.printf("About to start writing response with " +
"a %d-byte body...\n", body.length);
}
@Override
public void didWriteResponse(
@NonNull ServerType serverType,
@NonNull Request request,
@Nullable ResourceMethod resourceMethod,
@NonNull MarshaledResponse marshaledResponse,
@NonNull Duration responseWriteDuration
) {
double millis = responseWriteDuration.toNanos() / 1_000_000.0;
System.out.printf("Took %dms to write response\n", millis);
}
@Override
public void didFailToWriteResponse(
@NonNull ServerType serverType,
@NonNull Request request,
@Nullable ResourceMethod resourceMethod,
@NonNull MarshaledResponse marshaledResponse,
@NonNull Duration responseWriteDuration,
@NonNull Throwable throwable
) {
double millis = responseWriteDuration.toNanos() / 1_000_000.0;
System.out.printf("Response write failed after %dms\n", millis);
// Use this to monitor trends in unexpected client disconnects
System.err.println("Exception occurred while writing response");
throwable.printStackTrace();
}
}).build();
References:
LifecycleObserver::willWriteResponseLifecycleObserver::didWriteResponseLifecycleObserver::didFailToWriteResponse
Server-Sent Events
If your application configures a ServerSentEventServer, additional lifecycle hooks are available for SSE server and connection activity.
SSE Server Start/Stop
SokletConfig config = SokletConfig.withServer(
Server.fromPort(8080)
).serverSentEventServer(
ServerSentEventServer.fromPort(8081)
).lifecycleObserver(new LifecycleObserver() {
@Override
public void willStartServerSentEventServer(
@NonNull ServerSentEventServer serverSentEventServer
) {
System.out.println("SSE server starting.");
}
@Override
public void didStartServerSentEventServer(
@NonNull ServerSentEventServer serverSentEventServer
) {
System.out.println("SSE server started.");
}
@Override
public void didFailToStartServerSentEventServer(
@NonNull ServerSentEventServer serverSentEventServer,
@NonNull Throwable throwable
) {
System.err.println("SSE server failed to start.");
throwable.printStackTrace();
}
@Override
public void willStopServerSentEventServer(
@NonNull ServerSentEventServer serverSentEventServer
) {
System.out.println("SSE server stopping.");
}
@Override
public void didStopServerSentEventServer(
@NonNull ServerSentEventServer serverSentEventServer
) {
System.out.println("SSE server stopped.");
}
@Override
public void didFailToStopServerSentEventServer(
@NonNull ServerSentEventServer serverSentEventServer,
@NonNull Throwable throwable
) {
System.err.println("SSE server failed to stop cleanly.");
throwable.printStackTrace();
}
}).build();
SokletConfig config = SokletConfig.withServer(
Server.fromPort(8080)
).serverSentEventServer(
ServerSentEventServer.fromPort(8081)
).lifecycleObserver(new LifecycleObserver() {
@Override
public void willStartServerSentEventServer(
@NonNull ServerSentEventServer serverSentEventServer
) {
System.out.println("SSE server starting.");
}
@Override
public void didStartServerSentEventServer(
@NonNull ServerSentEventServer serverSentEventServer
) {
System.out.println("SSE server started.");
}
@Override
public void didFailToStartServerSentEventServer(
@NonNull ServerSentEventServer serverSentEventServer,
@NonNull Throwable throwable
) {
System.err.println("SSE server failed to start.");
throwable.printStackTrace();
}
@Override
public void willStopServerSentEventServer(
@NonNull ServerSentEventServer serverSentEventServer
) {
System.out.println("SSE server stopping.");
}
@Override
public void didStopServerSentEventServer(
@NonNull ServerSentEventServer serverSentEventServer
) {
System.out.println("SSE server stopped.");
}
@Override
public void didFailToStopServerSentEventServer(
@NonNull ServerSentEventServer serverSentEventServer,
@NonNull Throwable throwable
) {
System.err.println("SSE server failed to stop cleanly.");
throwable.printStackTrace();
}
}).build();
References:
LifecycleObserver::willStartServerSentEventServerLifecycleObserver::didStartServerSentEventServerLifecycleObserver::didFailToStartServerSentEventServerLifecycleObserver::willStopServerSentEventServerLifecycleObserver::didStopServerSentEventServerLifecycleObserver::didFailToStopServerSentEventServer
SSE Connections
SokletConfig config = SokletConfig.withServer(
Server.fromPort(8080)
).serverSentEventServer(
ServerSentEventServer.fromPort(8081)
).lifecycleObserver(new LifecycleObserver() {
@Override
public void willEstablishServerSentEventConnection(
@NonNull Request request,
@NonNull ResourceMethod resourceMethod
) {
System.out.printf("Establishing SSE connection for %s\n", request);
}
@Override
public void didEstablishServerSentEventConnection(
@NonNull ServerSentEventConnection serverSentEventConnection
) {
System.out.printf("SSE connection established: %s\n", serverSentEventConnection);
}
@Override
public void didFailToEstablishServerSentEventConnection(
@NonNull Request request,
@Nullable ResourceMethod resourceMethod,
@NonNull ServerSentEventConnection.HandshakeFailureReason reason,
@Nullable Throwable throwable
) {
System.err.println("SSE connection establishment failed.");
if (throwable != null)
throwable.printStackTrace();
}
@Override
public void willTerminateServerSentEventConnection(
@NonNull ServerSentEventConnection serverSentEventConnection,
@NonNull TerminationReason terminationReason,
@Nullable Throwable throwable
) {
System.out.printf("SSE connection terminating (%s)\n", terminationReason);
}
@Override
public void didTerminateServerSentEventConnection(
@NonNull ServerSentEventConnection serverSentEventConnection,
@NonNull Duration connectionDuration,
@NonNull TerminationReason terminationReason,
@Nullable Throwable throwable
) {
System.out.printf("SSE connection terminated after %dms (%s)\n",
connectionDuration.toMillis(), terminationReason);
}
}).build();
SokletConfig config = SokletConfig.withServer(
Server.fromPort(8080)
).serverSentEventServer(
ServerSentEventServer.fromPort(8081)
).lifecycleObserver(new LifecycleObserver() {
@Override
public void willEstablishServerSentEventConnection(
@NonNull Request request,
@NonNull ResourceMethod resourceMethod
) {
System.out.printf("Establishing SSE connection for %s\n", request);
}
@Override
public void didEstablishServerSentEventConnection(
@NonNull ServerSentEventConnection serverSentEventConnection
) {
System.out.printf("SSE connection established: %s\n", serverSentEventConnection);
}
@Override
public void didFailToEstablishServerSentEventConnection(
@NonNull Request request,
@Nullable ResourceMethod resourceMethod,
@NonNull ServerSentEventConnection.HandshakeFailureReason reason,
@Nullable Throwable throwable
) {
System.err.println("SSE connection establishment failed.");
if (throwable != null)
throwable.printStackTrace();
}
@Override
public void willTerminateServerSentEventConnection(
@NonNull ServerSentEventConnection serverSentEventConnection,
@NonNull TerminationReason terminationReason,
@Nullable Throwable throwable
) {
System.out.printf("SSE connection terminating (%s)\n", terminationReason);
}
@Override
public void didTerminateServerSentEventConnection(
@NonNull ServerSentEventConnection serverSentEventConnection,
@NonNull Duration connectionDuration,
@NonNull TerminationReason terminationReason,
@Nullable Throwable throwable
) {
System.out.printf("SSE connection terminated after %dms (%s)\n",
connectionDuration.toMillis(), terminationReason);
}
}).build();
References:
LifecycleObserver::willEstablishServerSentEventConnectionLifecycleObserver::didEstablishServerSentEventConnectionLifecycleObserver::didFailToEstablishServerSentEventConnectionLifecycleObserver::willTerminateServerSentEventConnectionLifecycleObserver::didTerminateServerSentEventConnection
SSE Event and Comment Writes
SokletConfig config = SokletConfig.withServer(
Server.fromPort(8080)
).serverSentEventServer(
ServerSentEventServer.fromPort(8081)
).lifecycleObserver(new LifecycleObserver() {
@Override
public void willWriteServerSentEvent(
@NonNull ServerSentEventConnection serverSentEventConnection,
@NonNull ServerSentEvent serverSentEvent
) {
System.out.printf("Writing SSE event %s\n", serverSentEvent.getEvent().orElse("message"));
}
@Override
public void didWriteServerSentEvent(
@NonNull ServerSentEventConnection serverSentEventConnection,
@NonNull ServerSentEvent serverSentEvent,
@NonNull Duration writeDuration
) {
System.out.printf("SSE event write took %dms\n", writeDuration.toMillis());
}
@Override
public void didFailToWriteServerSentEvent(
@NonNull ServerSentEventConnection serverSentEventConnection,
@NonNull ServerSentEvent serverSentEvent,
@NonNull Duration writeDuration,
@NonNull Throwable throwable
) {
System.err.println("SSE event write failed.");
throwable.printStackTrace();
}
@Override
public void willWriteServerSentEventComment(
@NonNull ServerSentEventConnection serverSentEventConnection,
@NonNull ServerSentEventComment serverSentEventComment
) {
System.out.printf("Writing SSE comment (%s): %s\n",
serverSentEventComment.getCommentType(),
serverSentEventComment.getComment().orElse("<heartbeat>"));
}
@Override
public void didWriteServerSentEventComment(
@NonNull ServerSentEventConnection serverSentEventConnection,
@NonNull ServerSentEventComment serverSentEventComment,
@NonNull Duration writeDuration
) {
System.out.printf("SSE comment write took %dms\n", writeDuration.toMillis());
}
@Override
public void didFailToWriteServerSentEventComment(
@NonNull ServerSentEventConnection serverSentEventConnection,
@NonNull ServerSentEventComment serverSentEventComment,
@NonNull Duration writeDuration,
@NonNull Throwable throwable
) {
System.err.println("SSE comment write failed.");
throwable.printStackTrace();
}
}).build();
SokletConfig config = SokletConfig.withServer(
Server.fromPort(8080)
).serverSentEventServer(
ServerSentEventServer.fromPort(8081)
).lifecycleObserver(new LifecycleObserver() {
@Override
public void willWriteServerSentEvent(
@NonNull ServerSentEventConnection serverSentEventConnection,
@NonNull ServerSentEvent serverSentEvent
) {
System.out.printf("Writing SSE event %s\n", serverSentEvent.getEvent().orElse("message"));
}
@Override
public void didWriteServerSentEvent(
@NonNull ServerSentEventConnection serverSentEventConnection,
@NonNull ServerSentEvent serverSentEvent,
@NonNull Duration writeDuration
) {
System.out.printf("SSE event write took %dms\n", writeDuration.toMillis());
}
@Override
public void didFailToWriteServerSentEvent(
@NonNull ServerSentEventConnection serverSentEventConnection,
@NonNull ServerSentEvent serverSentEvent,
@NonNull Duration writeDuration,
@NonNull Throwable throwable
) {
System.err.println("SSE event write failed.");
throwable.printStackTrace();
}
@Override
public void willWriteServerSentEventComment(
@NonNull ServerSentEventConnection serverSentEventConnection,
@NonNull ServerSentEventComment serverSentEventComment
) {
System.out.printf("Writing SSE comment (%s): %s\n",
serverSentEventComment.getCommentType(),
serverSentEventComment.getComment().orElse("<heartbeat>"));
}
@Override
public void didWriteServerSentEventComment(
@NonNull ServerSentEventConnection serverSentEventConnection,
@NonNull ServerSentEventComment serverSentEventComment,
@NonNull Duration writeDuration
) {
System.out.printf("SSE comment write took %dms\n", writeDuration.toMillis());
}
@Override
public void didFailToWriteServerSentEventComment(
@NonNull ServerSentEventConnection serverSentEventConnection,
@NonNull ServerSentEventComment serverSentEventComment,
@NonNull Duration writeDuration,
@NonNull Throwable throwable
) {
System.err.println("SSE comment write failed.");
throwable.printStackTrace();
}
}).build();
References:
LifecycleObserver::willWriteServerSentEventLifecycleObserver::didWriteServerSentEventLifecycleObserver::didFailToWriteServerSentEventLifecycleObserver::willWriteServerSentEventCommentLifecycleObserver::didWriteServerSentEventCommentLifecycleObserver::didFailToWriteServerSentEventComment
Event Logging
Soklet provides insight into unexpected errors that occur during internal processing that are not otherwise surfaced via LogEvent objects provided to LifecycleObserver::didReceiveLogEvent.
For example, you might want to specially monitor scenarios in which your ResponseMarshaler::forThrowable failed (that is, you attempted to write an error response for an exception that bubbled out, but that attempt threw an exception, forcing Soklet to write its own failsafe response) - you could do this by observing events with type LogEventType.RESPONSE_MARSHALER_FOR_THROWABLE_FAILED.
Your LifecycleObserver can listen for LogEvents like this:
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
SokletConfig config = SokletConfig.withServer(
Server.fromPort(8080)
).lifecycleObserver(new LifecycleObserver() {
// This example uses SLF4J. See https://www.slf4j.org
private final Logger logger =
LoggerFactory.getLogger("com.soklet.example.LifecycleObserver");
@Override
public void didReceiveLogEvent(@NonNull LogEvent logEvent) {
// These properties are available in LogEvent
LogEventType logEventType = logEvent.getLogEventType();
String message = logEvent.getMessage();
Optional<Throwable> throwable = logEvent.getThrowable();
Optional<Request> request = logEvent.getRequest();
Optional<ResourceMethod> resourceMethod = logEvent.getResourceMethod();
Optional<MarshaledResponse> marshaledResponse = logEvent.getMarshaledResponse();
// Log the message however you like
logger.warn(message, throwable.orElse(null));
}
}).build();
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
SokletConfig config = SokletConfig.withServer(
Server.fromPort(8080)
).lifecycleObserver(new LifecycleObserver() {
// This example uses SLF4J. See https://www.slf4j.org
private final Logger logger =
LoggerFactory.getLogger("com.soklet.example.LifecycleObserver");
@Override
public void didReceiveLogEvent(@NonNull LogEvent logEvent) {
// These properties are available in LogEvent
LogEventType logEventType = logEvent.getLogEventType();
String message = logEvent.getMessage();
Optional<Throwable> throwable = logEvent.getThrowable();
Optional<Request> request = logEvent.getRequest();
Optional<ResourceMethod> resourceMethod = logEvent.getResourceMethod();
Optional<MarshaledResponse> marshaledResponse = logEvent.getMarshaledResponse();
// Log the message however you like
logger.warn(message, throwable.orElse(null));
}
}).build();

