001/* 002 * Copyright 2022-2026 Revetware LLC. 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * http://www.apache.org/licenses/LICENSE-2.0 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 */ 016 017package com.soklet; 018 019import org.jspecify.annotations.NonNull; 020import org.jspecify.annotations.Nullable; 021 022import java.io.PrintWriter; 023import java.io.StringWriter; 024import java.net.InetSocketAddress; 025import java.time.Duration; 026import java.util.List; 027 028/** 029 * Read-only hook methods for observing system and request lifecycle events. 030 * <p> 031 * Note: some of these methods are "fail-fast" - exceptions thrown will bubble out and stop execution - and for others, 032 * Soklet will catch exceptions and surface separately via {@link #didReceiveLogEvent(LogEvent)}. 033 * <p> 034 * A standard threadsafe implementation can be acquired via the {@link #defaultInstance()} factory method. 035 * <p> 036 * Full documentation is available at <a href="https://www.soklet.com/docs/request-lifecycle">https://www.soklet.com/docs/request-lifecycle</a>. 037 * 038 * @author <a href="https://www.revetkn.com">Mark Allen</a> 039 */ 040public interface LifecycleObserver { 041 /** 042 * Called before a {@link Soklet} instance starts. 043 */ 044 default void willStartSoklet(@NonNull Soklet soklet) { 045 // No-op by default 046 } 047 048 /** 049 * Called after a {@link Soklet} instance starts. 050 */ 051 default void didStartSoklet(@NonNull Soklet soklet) { 052 // No-op by default 053 } 054 055 /** 056 * Called after a {@link Soklet} instance was asked to start, but failed due to an exception. 057 */ 058 default void didFailToStartSoklet(@NonNull Soklet soklet, 059 @NonNull Throwable throwable) { 060 // No-op by default 061 } 062 063 /** 064 * Called before a {@link Soklet} instance stops. 065 */ 066 default void willStopSoklet(@NonNull Soklet soklet) { 067 // No-op by default 068 } 069 070 /** 071 * Called after a {@link Soklet} instance stops. 072 */ 073 default void didStopSoklet(@NonNull Soklet soklet) { 074 // No-op by default 075 } 076 077 /** 078 * Called after a {@link Soklet} instance was asked to stop, but failed due to an exception. 079 */ 080 default void didFailToStopSoklet(@NonNull Soklet soklet, 081 @NonNull Throwable throwable) { 082 // No-op by default 083 } 084 085 /** 086 * Called before the server starts. 087 */ 088 default void willStartHttpServer(@NonNull HttpServer httpServer) { 089 // No-op by default 090 } 091 092 /** 093 * Called after the server starts. 094 */ 095 default void didStartHttpServer(@NonNull HttpServer httpServer) { 096 // No-op by default 097 } 098 099 /** 100 * Called after a {@link HttpServer} instance was asked to start, but failed due to an exception. 101 */ 102 default void didFailToStartHttpServer(@NonNull HttpServer httpServer, 103 @NonNull Throwable throwable) { 104 // No-op by default 105 } 106 107 /** 108 * Called before the server stops. 109 */ 110 default void willStopHttpServer(@NonNull HttpServer httpServer) { 111 // No-op by default 112 } 113 114 /** 115 * Called after the server stops. 116 */ 117 default void didStopHttpServer(@NonNull HttpServer httpServer) { 118 // No-op by default 119 } 120 121 /** 122 * Called after a {@link HttpServer} instance was asked to stop, but failed due to an exception. 123 */ 124 default void didFailToStopHttpServer(@NonNull HttpServer httpServer, 125 @NonNull Throwable throwable) { 126 // No-op by default 127 } 128 129 /** 130 * Called when a server is about to accept a new TCP connection. 131 * 132 * @param serverType the server type that is accepting the connection 133 * @param remoteAddress the best-effort remote address, or {@code null} if unavailable 134 */ 135 default void willAcceptConnection(@NonNull ServerType serverType, 136 @Nullable InetSocketAddress remoteAddress) { 137 // No-op by default 138 } 139 140 /** 141 * Called after a server accepts a new TCP connection. 142 * 143 * @param serverType the server type that accepted the connection 144 * @param remoteAddress the best-effort remote address, or {@code null} if unavailable 145 */ 146 default void didAcceptConnection(@NonNull ServerType serverType, 147 @Nullable InetSocketAddress remoteAddress) { 148 // No-op by default 149 } 150 151 /** 152 * Called after a server fails to accept a new TCP connection. 153 * 154 * @param serverType the server type that failed to accept the connection 155 * @param remoteAddress the best-effort remote address, or {@code null} if unavailable 156 * @param reason the failure reason 157 * @param throwable an optional underlying cause, or {@code null} if not applicable 158 */ 159 default void didFailToAcceptConnection(@NonNull ServerType serverType, 160 @Nullable InetSocketAddress remoteAddress, 161 @NonNull ConnectionRejectionReason reason, 162 @Nullable Throwable throwable) { 163 // No-op by default 164 } 165 166 /** 167 * Called when a request is about to be accepted for application-level handling. 168 * 169 * @param serverType the server type that received the request 170 * @param remoteAddress the best-effort remote address, or {@code null} if unavailable 171 * @param requestTarget the raw request target (path + query) if known, or {@code null} if unavailable 172 */ 173 default void willAcceptRequest(@NonNull ServerType serverType, 174 @Nullable InetSocketAddress remoteAddress, 175 @Nullable String requestTarget) { 176 // No-op by default 177 } 178 179 /** 180 * Called after a request is accepted for application-level handling. 181 * 182 * @param serverType the server type that received the request 183 * @param remoteAddress the best-effort remote address, or {@code null} if unavailable 184 * @param requestTarget the raw request target (path + query) if known, or {@code null} if unavailable 185 */ 186 default void didAcceptRequest(@NonNull ServerType serverType, 187 @Nullable InetSocketAddress remoteAddress, 188 @Nullable String requestTarget) { 189 // No-op by default 190 } 191 192 /** 193 * Called when a request fails to be accepted before application-level handling begins. 194 * 195 * @param serverType the server type that received the request 196 * @param remoteAddress the best-effort remote address, or {@code null} if unavailable 197 * @param requestTarget the raw request target (path + query) if known, or {@code null} if unavailable 198 * @param reason the rejection reason 199 * @param throwable an optional underlying cause, or {@code null} if not applicable 200 */ 201 default void didFailToAcceptRequest(@NonNull ServerType serverType, 202 @Nullable InetSocketAddress remoteAddress, 203 @Nullable String requestTarget, 204 @NonNull RequestRejectionReason reason, 205 @Nullable Throwable throwable) { 206 // No-op by default 207 } 208 209 /** 210 * Called when Soklet is about to read or parse a request into a valid {@link Request}. 211 * 212 * @param serverType the server type that received the request 213 * @param remoteAddress the best-effort remote address, or {@code null} if unavailable 214 * @param requestTarget the raw request target (path + query) if known, or {@code null} if unavailable 215 */ 216 default void willReadRequest(@NonNull ServerType serverType, 217 @Nullable InetSocketAddress remoteAddress, 218 @Nullable String requestTarget) { 219 // No-op by default 220 } 221 222 /** 223 * Called when a request was successfully read or parsed into a valid {@link Request}. 224 * 225 * @param serverType the server type that received the request 226 * @param remoteAddress the best-effort remote address, or {@code null} if unavailable 227 * @param requestTarget the raw request target (path + query) if known, or {@code null} if unavailable 228 */ 229 default void didReadRequest(@NonNull ServerType serverType, 230 @Nullable InetSocketAddress remoteAddress, 231 @Nullable String requestTarget) { 232 // No-op by default 233 } 234 235 /** 236 * Called when a request could not be read or parsed into a valid {@link Request}. 237 * 238 * @param serverType the server type that received the request 239 * @param remoteAddress the best-effort remote address, or {@code null} if unavailable 240 * @param requestTarget the raw request target (path + query) if known, or {@code null} if unavailable 241 * @param reason the failure reason 242 * @param throwable an optional underlying cause, or {@code null} if not applicable 243 */ 244 default void didFailToReadRequest(@NonNull ServerType serverType, 245 @Nullable InetSocketAddress remoteAddress, 246 @Nullable String requestTarget, 247 @NonNull RequestReadFailureReason reason, 248 @Nullable Throwable throwable) { 249 // No-op by default 250 } 251 252 /** 253 * Called as soon as a request is received and a <em>Resource Method</em> has been resolved to handle it. 254 * 255 * @param serverType the server type that received the request 256 */ 257 default void didStartRequestHandling(@NonNull ServerType serverType, 258 @NonNull Request request, 259 @Nullable ResourceMethod resourceMethod) { 260 // No-op by default 261 } 262 263 /** 264 * Called after a request finishes processing. 265 */ 266 default void didFinishRequestHandling(@NonNull ServerType serverType, 267 @NonNull Request request, 268 @Nullable ResourceMethod resourceMethod, 269 @NonNull MarshaledResponse marshaledResponse, 270 @NonNull Duration duration, 271 @NonNull List<@NonNull Throwable> throwables) { 272 // No-op by default 273 } 274 275 /** 276 * Called before response data is written. 277 */ 278 default void willWriteResponse(@NonNull ServerType serverType, 279 @NonNull Request request, 280 @Nullable ResourceMethod resourceMethod, 281 @NonNull MarshaledResponse marshaledResponse) { 282 // No-op by default 283 } 284 285 /** 286 * Called after response data is written. 287 */ 288 default void didWriteResponse(@NonNull ServerType serverType, 289 @NonNull Request request, 290 @Nullable ResourceMethod resourceMethod, 291 @NonNull MarshaledResponse marshaledResponse, 292 @NonNull Duration responseWriteDuration) { 293 // No-op by default 294 } 295 296 /** 297 * Called after response data fails to write. 298 */ 299 default void didFailToWriteResponse(@NonNull ServerType serverType, 300 @NonNull Request request, 301 @Nullable ResourceMethod resourceMethod, 302 @NonNull MarshaledResponse marshaledResponse, 303 @NonNull Duration responseWriteDuration, 304 @NonNull Throwable throwable) { 305 // No-op by default 306 } 307 308 /** 309 * Called before a streaming response termination is reported as complete. 310 * <p> 311 * This is paired with {@link #didTerminateResponseStream(StreamingResponseHandle, StreamTermination)}. For standard 312 * HTTP response streams, the two callbacks are normally invoked back-to-back because there is no broadcaster or 313 * session registry cleanup phase between them. 314 * 315 * @param streamingResponse the stream that is terminating 316 * @param termination why and when the stream terminated 317 */ 318 default void willTerminateResponseStream(@NonNull StreamingResponseHandle streamingResponse, 319 @NonNull StreamTermination termination) { 320 // No-op by default 321 } 322 323 /** 324 * Called after a streaming response terminates. 325 * <p> 326 * If a stream is rejected before body bytes are written, {@link StreamingResponseHandle#getMarshaledResponse()} 327 * returns the original application-provided streaming response. For example, an HTTP/1.0 request for a streaming 328 * response is rejected on the wire with {@code 505 HTTP Version Not Supported}, while this callback still receives 329 * the original streaming response that was rejected. 330 * 331 * @param streamingResponse the stream that terminated 332 * @param termination why and when the stream terminated 333 */ 334 default void didTerminateResponseStream(@NonNull StreamingResponseHandle streamingResponse, 335 @NonNull StreamTermination termination) { 336 // No-op by default 337 } 338 339 /** 340 * Called before the MCP server starts. 341 */ 342 default void willStartMcpServer(@NonNull McpServer mcpServer) { 343 // No-op by default 344 } 345 346 /** 347 * Called after the MCP server starts. 348 */ 349 default void didStartMcpServer(@NonNull McpServer mcpServer) { 350 // No-op by default 351 } 352 353 /** 354 * Called after an {@link McpServer} instance was asked to start, but failed due to an exception. 355 */ 356 default void didFailToStartMcpServer(@NonNull McpServer mcpServer, 357 @NonNull Throwable throwable) { 358 // No-op by default 359 } 360 361 /** 362 * Called before the MCP server stops. 363 */ 364 default void willStopMcpServer(@NonNull McpServer mcpServer) { 365 // No-op by default 366 } 367 368 /** 369 * Called after the MCP server stops. 370 */ 371 default void didStopMcpServer(@NonNull McpServer mcpServer) { 372 // No-op by default 373 } 374 375 /** 376 * Called after an {@link McpServer} instance was asked to stop, but failed due to an exception. 377 */ 378 default void didFailToStopMcpServer(@NonNull McpServer mcpServer, 379 @NonNull Throwable throwable) { 380 // No-op by default 381 } 382 383 /** 384 * Called after an MCP session is durably created. 385 */ 386 default void didCreateMcpSession(@NonNull Request request, 387 @NonNull Class<? extends McpEndpoint> endpointClass, 388 @NonNull String sessionId) { 389 // No-op by default 390 } 391 392 /** 393 * Called after an MCP session is terminated. 394 */ 395 default void didTerminateMcpSession(@NonNull Class<? extends McpEndpoint> endpointClass, 396 @NonNull String sessionId, 397 @NonNull Duration sessionDuration, 398 @NonNull McpSessionTerminationReason terminationReason, 399 @Nullable Throwable throwable) { 400 // No-op by default 401 } 402 403 /** 404 * Called after a valid MCP JSON-RPC request begins handling. 405 */ 406 default void didStartMcpRequestHandling(@NonNull Request request, 407 @NonNull Class<? extends McpEndpoint> endpointClass, 408 @Nullable String sessionId, 409 @NonNull String jsonRpcMethod, 410 @Nullable McpJsonRpcRequestId jsonRpcRequestId) { 411 // No-op by default 412 } 413 414 /** 415 * Called after MCP JSON-RPC request handling finishes. 416 */ 417 default void didFinishMcpRequestHandling(@NonNull Request request, 418 @NonNull Class<? extends McpEndpoint> endpointClass, 419 @Nullable String sessionId, 420 @NonNull String jsonRpcMethod, 421 @Nullable McpJsonRpcRequestId jsonRpcRequestId, 422 @NonNull McpRequestOutcome requestOutcome, 423 @Nullable McpJsonRpcError jsonRpcError, 424 @NonNull Duration duration, 425 @NonNull List<@NonNull Throwable> throwables) { 426 // No-op by default 427 } 428 429 /** 430 * Called after an MCP GET stream is established. 431 */ 432 default void didEstablishMcpSseStream(@NonNull McpSseStream stream) { 433 // No-op by default 434 } 435 436 /** 437 * Called before an MCP GET stream is terminated. 438 */ 439 default void willTerminateMcpSseStream(@NonNull McpSseStream stream, 440 @NonNull StreamTermination termination) { 441 // No-op by default 442 } 443 444 /** 445 * Called after an MCP GET stream is terminated. 446 */ 447 default void didTerminateMcpSseStream(@NonNull McpSseStream stream, 448 @NonNull StreamTermination termination) { 449 // No-op by default 450 } 451 452 /** 453 * Called before the SSE server starts. 454 */ 455 default void willStartSseServer(@NonNull SseServer sseServer) { 456 // No-op by default 457 } 458 459 /** 460 * Called after the SSE server starts. 461 */ 462 default void didStartSseServer(@NonNull SseServer sseServer) { 463 // No-op by default 464 } 465 466 /** 467 * Called after a {@link SseServer} instance was asked to start, but failed due to an exception. 468 */ 469 default void didFailToStartSseServer(@NonNull SseServer sseServer, 470 @NonNull Throwable throwable) { 471 // No-op by default 472 } 473 474 /** 475 * Called before the SSE server stops. 476 */ 477 default void willStopSseServer(@NonNull SseServer sseServer) { 478 // No-op by default 479 } 480 481 /** 482 * Called after the SSE server stops. 483 */ 484 default void didStopSseServer(@NonNull SseServer sseServer) { 485 // No-op by default 486 } 487 488 /** 489 * Called after a {@link SseServer} instance was asked to stop, but failed due to an exception. 490 */ 491 default void didFailToStopSseServer(@NonNull SseServer sseServer, 492 @NonNull Throwable throwable) { 493 // No-op by default 494 } 495 496 /** 497 * Called before an SSE connection is established. 498 */ 499 default void willEstablishSseConnection(@NonNull Request request, 500 @Nullable ResourceMethod resourceMethod) { 501 // No-op by default 502 } 503 504 /** 505 * Called after an SSE connection is established. 506 */ 507 default void didEstablishSseConnection(@NonNull SseConnection sseConnection) { 508 // No-op by default 509 } 510 511 /** 512 * Called if an SSE connection fails to establish. 513 * 514 * @param reason the handshake failure reason 515 * @param throwable an optional underlying cause, or {@code null} if not applicable 516 */ 517 default void didFailToEstablishSseConnection(@NonNull Request request, 518 @Nullable ResourceMethod resourceMethod, 519 SseConnection.@NonNull HandshakeFailureReason reason, 520 @Nullable Throwable throwable) { 521 // No-op by default 522 } 523 524 /** 525 * Called before an SSE connection is terminated. 526 */ 527 default void willTerminateSseConnection(@NonNull SseConnection sseConnection, 528 @NonNull StreamTermination termination) { 529 // No-op by default 530 } 531 532 /** 533 * Called after an SSE connection is terminated. 534 */ 535 default void didTerminateSseConnection(@NonNull SseConnection sseConnection, 536 @NonNull StreamTermination termination) { 537 // No-op by default 538 } 539 540 /** 541 * Called before an SSE event is written. 542 */ 543 default void willWriteSseEvent(@NonNull SseConnection sseConnection, 544 @NonNull SseEvent sseEvent) { 545 // No-op by default 546 } 547 548 /** 549 * Called after an SSE event is written. 550 */ 551 default void didWriteSseEvent(@NonNull SseConnection sseConnection, 552 @NonNull SseEvent sseEvent, 553 @NonNull Duration writeDuration) { 554 // No-op by default 555 } 556 557 /** 558 * Called after an SSE event fails to write. 559 */ 560 default void didFailToWriteSseEvent(@NonNull SseConnection sseConnection, 561 @NonNull SseEvent sseEvent, 562 @NonNull Duration writeDuration, 563 @NonNull Throwable throwable) { 564 // No-op by default 565 } 566 567 /** 568 * Called before an SSE comment is written. 569 */ 570 default void willWriteSseComment(@NonNull SseConnection sseConnection, 571 @NonNull SseComment sseComment) { 572 // No-op by default 573 } 574 575 /** 576 * Called after an SSE comment is written. 577 */ 578 default void didWriteSseComment(@NonNull SseConnection sseConnection, 579 @NonNull SseComment sseComment, 580 @NonNull Duration writeDuration) { 581 // No-op by default 582 } 583 584 /** 585 * Called after an SSE comment fails to write. 586 */ 587 default void didFailToWriteSseComment(@NonNull SseConnection sseConnection, 588 @NonNull SseComment sseComment, 589 @NonNull Duration writeDuration, 590 @NonNull Throwable throwable) { 591 // No-op by default 592 } 593 594 /** 595 * Called when Soklet emits a log event. 596 */ 597 default void didReceiveLogEvent(@NonNull LogEvent logEvent) { 598 String message = logEvent.getMessage(); 599 Throwable throwable = logEvent.getThrowable().orElse(null); 600 601 if (throwable == null) { 602 System.err.printf("%s::didReceiveLogEvent [%s]: %s", LifecycleObserver.class.getSimpleName(), logEvent.getLogEventType().name(), message); 603 } else { 604 StringWriter stringWriter = new StringWriter(); 605 PrintWriter printWriter = new PrintWriter(stringWriter); 606 throwable.printStackTrace(printWriter); 607 String throwableWithStackTrace = stringWriter.toString(); 608 609 System.err.printf("%s::didReceiveLogEvent [%s]: %s\n%s\n", LifecycleObserver.class.getSimpleName(), logEvent.getLogEventType().name(), message, throwableWithStackTrace); 610 } 611 } 612 613 /** 614 * Acquires a threadsafe {@link LifecycleObserver} instance with sensible defaults. 615 * 616 * @return a {@code LifecycleObserver} with default settings 617 */ 618 @NonNull 619 static LifecycleObserver defaultInstance() { 620 return DefaultLifecycleObserver.defaultInstance(); 621 } 622}