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 the MCP server starts. 310 */ 311 default void willStartMcpServer(@NonNull McpServer mcpServer) { 312 // No-op by default 313 } 314 315 /** 316 * Called after the MCP server starts. 317 */ 318 default void didStartMcpServer(@NonNull McpServer mcpServer) { 319 // No-op by default 320 } 321 322 /** 323 * Called after an {@link McpServer} instance was asked to start, but failed due to an exception. 324 */ 325 default void didFailToStartMcpServer(@NonNull McpServer mcpServer, 326 @NonNull Throwable throwable) { 327 // No-op by default 328 } 329 330 /** 331 * Called before the MCP server stops. 332 */ 333 default void willStopMcpServer(@NonNull McpServer mcpServer) { 334 // No-op by default 335 } 336 337 /** 338 * Called after the MCP server stops. 339 */ 340 default void didStopMcpServer(@NonNull McpServer mcpServer) { 341 // No-op by default 342 } 343 344 /** 345 * Called after an {@link McpServer} instance was asked to stop, but failed due to an exception. 346 */ 347 default void didFailToStopMcpServer(@NonNull McpServer mcpServer, 348 @NonNull Throwable throwable) { 349 // No-op by default 350 } 351 352 /** 353 * Called after an MCP session is durably created. 354 */ 355 default void didCreateMcpSession(@NonNull Request request, 356 @NonNull Class<? extends McpEndpoint> endpointClass, 357 @NonNull String sessionId) { 358 // No-op by default 359 } 360 361 /** 362 * Called after an MCP session is terminated. 363 */ 364 default void didTerminateMcpSession(@NonNull Class<? extends McpEndpoint> endpointClass, 365 @NonNull String sessionId, 366 @NonNull Duration sessionDuration, 367 @NonNull McpSessionTerminationReason terminationReason, 368 @Nullable Throwable throwable) { 369 // No-op by default 370 } 371 372 /** 373 * Called after a valid MCP JSON-RPC request begins handling. 374 */ 375 default void didStartMcpRequestHandling(@NonNull Request request, 376 @NonNull Class<? extends McpEndpoint> endpointClass, 377 @Nullable String sessionId, 378 @NonNull String jsonRpcMethod, 379 @Nullable McpJsonRpcRequestId jsonRpcRequestId) { 380 // No-op by default 381 } 382 383 /** 384 * Called after MCP JSON-RPC request handling finishes. 385 */ 386 default void didFinishMcpRequestHandling(@NonNull Request request, 387 @NonNull Class<? extends McpEndpoint> endpointClass, 388 @Nullable String sessionId, 389 @NonNull String jsonRpcMethod, 390 @Nullable McpJsonRpcRequestId jsonRpcRequestId, 391 @NonNull McpRequestOutcome requestOutcome, 392 @Nullable McpJsonRpcError jsonRpcError, 393 @NonNull Duration duration, 394 @NonNull List<@NonNull Throwable> throwables) { 395 // No-op by default 396 } 397 398 /** 399 * Called after an MCP GET stream is established. 400 */ 401 default void didEstablishMcpSseStream(@NonNull Request request, 402 @NonNull Class<? extends McpEndpoint> endpointClass, 403 @NonNull String sessionId) { 404 // No-op by default 405 } 406 407 /** 408 * Called before an MCP GET stream is terminated. 409 */ 410 default void willTerminateMcpSseStream(@NonNull Request request, 411 @NonNull Class<? extends McpEndpoint> endpointClass, 412 @NonNull String sessionId, 413 @NonNull McpStreamTerminationReason terminationReason, 414 @Nullable Throwable throwable) { 415 // No-op by default 416 } 417 418 /** 419 * Called after an MCP GET stream is terminated. 420 */ 421 default void didTerminateMcpSseStream(@NonNull Request request, 422 @NonNull Class<? extends McpEndpoint> endpointClass, 423 @NonNull String sessionId, 424 @NonNull Duration connectionDuration, 425 @NonNull McpStreamTerminationReason terminationReason, 426 @Nullable Throwable throwable) { 427 // No-op by default 428 } 429 430 /** 431 * Called before the SSE server starts. 432 */ 433 default void willStartSseServer(@NonNull SseServer sseServer) { 434 // No-op by default 435 } 436 437 /** 438 * Called after the SSE server starts. 439 */ 440 default void didStartSseServer(@NonNull SseServer sseServer) { 441 // No-op by default 442 } 443 444 /** 445 * Called after a {@link SseServer} instance was asked to start, but failed due to an exception. 446 */ 447 default void didFailToStartSseServer(@NonNull SseServer sseServer, 448 @NonNull Throwable throwable) { 449 // No-op by default 450 } 451 452 /** 453 * Called before the SSE server stops. 454 */ 455 default void willStopSseServer(@NonNull SseServer sseServer) { 456 // No-op by default 457 } 458 459 /** 460 * Called after the SSE server stops. 461 */ 462 default void didStopSseServer(@NonNull SseServer sseServer) { 463 // No-op by default 464 } 465 466 /** 467 * Called after a {@link SseServer} instance was asked to stop, but failed due to an exception. 468 */ 469 default void didFailToStopSseServer(@NonNull SseServer sseServer, 470 @NonNull Throwable throwable) { 471 // No-op by default 472 } 473 474 /** 475 * Called before an SSE connection is established. 476 */ 477 default void willEstablishSseConnection(@NonNull Request request, 478 @Nullable ResourceMethod resourceMethod) { 479 // No-op by default 480 } 481 482 /** 483 * Called after an SSE connection is established. 484 */ 485 default void didEstablishSseConnection(@NonNull SseConnection sseConnection) { 486 // No-op by default 487 } 488 489 /** 490 * Called if an SSE connection fails to establish. 491 * 492 * @param reason the handshake failure reason 493 * @param throwable an optional underlying cause, or {@code null} if not applicable 494 */ 495 default void didFailToEstablishSseConnection(@NonNull Request request, 496 @Nullable ResourceMethod resourceMethod, 497 SseConnection.@NonNull HandshakeFailureReason reason, 498 @Nullable Throwable throwable) { 499 // No-op by default 500 } 501 502 /** 503 * Called before an SSE connection is terminated. 504 */ 505 default void willTerminateSseConnection(@NonNull SseConnection sseConnection, 506 SseConnection.@NonNull TerminationReason terminationReason, 507 @Nullable Throwable throwable) { 508 // No-op by default 509 } 510 511 /** 512 * Called after an SSE connection is terminated. 513 */ 514 default void didTerminateSseConnection(@NonNull SseConnection sseConnection, 515 @NonNull Duration connectionDuration, 516 SseConnection.@NonNull TerminationReason terminationReason, 517 @Nullable Throwable throwable) { 518 // No-op by default 519 } 520 521 /** 522 * Called before an SSE event is written. 523 */ 524 default void willWriteSseEvent(@NonNull SseConnection sseConnection, 525 @NonNull SseEvent sseEvent) { 526 // No-op by default 527 } 528 529 /** 530 * Called after an SSE event is written. 531 */ 532 default void didWriteSseEvent(@NonNull SseConnection sseConnection, 533 @NonNull SseEvent sseEvent, 534 @NonNull Duration writeDuration) { 535 // No-op by default 536 } 537 538 /** 539 * Called after an SSE event fails to write. 540 */ 541 default void didFailToWriteSseEvent(@NonNull SseConnection sseConnection, 542 @NonNull SseEvent sseEvent, 543 @NonNull Duration writeDuration, 544 @NonNull Throwable throwable) { 545 // No-op by default 546 } 547 548 /** 549 * Called before an SSE comment is written. 550 */ 551 default void willWriteSseComment(@NonNull SseConnection sseConnection, 552 @NonNull SseComment sseComment) { 553 // No-op by default 554 } 555 556 /** 557 * Called after an SSE comment is written. 558 */ 559 default void didWriteSseComment(@NonNull SseConnection sseConnection, 560 @NonNull SseComment sseComment, 561 @NonNull Duration writeDuration) { 562 // No-op by default 563 } 564 565 /** 566 * Called after an SSE comment fails to write. 567 */ 568 default void didFailToWriteSseComment(@NonNull SseConnection sseConnection, 569 @NonNull SseComment sseComment, 570 @NonNull Duration writeDuration, 571 @NonNull Throwable throwable) { 572 // No-op by default 573 } 574 575 /** 576 * Called when Soklet emits a log event. 577 */ 578 default void didReceiveLogEvent(@NonNull LogEvent logEvent) { 579 String message = logEvent.getMessage(); 580 Throwable throwable = logEvent.getThrowable().orElse(null); 581 582 if (throwable == null) { 583 System.err.printf("%s::didReceiveLogEvent [%s]: %s", LifecycleObserver.class.getSimpleName(), logEvent.getLogEventType().name(), message); 584 } else { 585 StringWriter stringWriter = new StringWriter(); 586 PrintWriter printWriter = new PrintWriter(stringWriter); 587 throwable.printStackTrace(printWriter); 588 String throwableWithStackTrace = stringWriter.toString(); 589 590 System.err.printf("%s::didReceiveLogEvent [%s]: %s\n%s\n", LifecycleObserver.class.getSimpleName(), logEvent.getLogEventType().name(), message, throwableWithStackTrace); 591 } 592 } 593 594 /** 595 * Acquires a threadsafe {@link LifecycleObserver} instance with sensible defaults. 596 * 597 * @return a {@code LifecycleObserver} with default settings 598 */ 599 @NonNull 600 static LifecycleObserver defaultInstance() { 601 return DefaultLifecycleObserver.defaultInstance(); 602 } 603}