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}