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 javax.annotation.concurrent.NotThreadSafe; 023import java.nio.charset.Charset; 024import java.nio.charset.StandardCharsets; 025import java.util.Set; 026 027import static java.util.Objects.requireNonNull; 028 029/** 030 * Prepares responses for each request scenario Soklet supports (<em>Resource Method</em>, exception, CORS preflight, etc.) 031 * <p> 032 * The {@link MarshaledResponse} value returned from these methods is what is ultimately sent back to 033 * clients as bytes over the wire. 034 * <p> 035 * A standard threadsafe implementation builder can be acquired via {@link #builder()} or {@link #withCharset(Charset)}. 036 * This builder allows you to specify, for example, how to turn a <em>Resource Method</em> response object into a wire format (e.g. JSON) and is generally what you want. 037 * <p> 038 * A standard threadsafe implementation can be acquired via the {@link #defaultInstance()} factory method. 039 * This is generally not needed unless your implementation requires dynamic "fall back to default" behavior that is not otherwise accessible. 040 * <p> 041 * Example implementation using {@link #builder()}: 042 * <pre>{@code // Let's use Gson to write response body data 043 * // See https://github.com/google/gson 044 * final Gson GSON = new Gson(); 045 * 046 * // The request was matched to a Resource Method and executed non-exceptionally 047 * ResourceMethodHandler resourceMethodHandler = ( 048 * @NonNull Request request, 049 * @NonNull Response response, 050 * @NonNull ResourceMethod resourceMethod 051 * ) -> { 052 * // Turn response body into JSON bytes with Gson 053 * Object bodyObject = response.getBody().orElse(null); 054 * byte[] body = bodyObject == null 055 * ? null 056 * : GSON.toJson(bodyObject).getBytes(StandardCharsets.UTF_8); 057 * 058 * // To be a good citizen, set the Content-Type header 059 * Map<String, Set<String>> headers = new HashMap<>(response.getHeaders()); 060 * headers.put("Content-Type", Set.of("application/json;charset=UTF-8")); 061 * 062 * // Tell Soklet: "OK - here is the final response data to send" 063 * return MarshaledResponse.withResponse(response) 064 * .headers(headers) 065 * .body(body) 066 * .build(); 067 * }; 068 * 069 * // Function to create responses for exceptions that bubble out 070 * ThrowableHandler throwableHandler = ( 071 * @NonNull Request request, 072 * @NonNull Throwable throwable, 073 * @Nullable ResourceMethod resourceMethod 074 * ) -> { 075 * // Keep track of what to write to the response 076 * String message; 077 * int statusCode; 078 * 079 * // Examine the exception that bubbled out and determine what 080 * // the HTTP status and a user-facing message should be. 081 * // Note: real systems should localize these messages 082 * switch (throwable) { 083 * // Soklet throws this exception, a specific subclass of BadRequestException 084 * case IllegalQueryParameterException e -> { 085 * message = String.format("Illegal value '%s' for parameter '%s'", 086 * e.getQueryParameterValue().orElse("[not provided]"), 087 * e.getQueryParameterName()); 088 * statusCode = 400; 089 * } 090 * 091 * // Generically handle other BadRequestExceptions 092 * case BadRequestException ignored -> { 093 * message = "Your request was improperly formatted."; 094 * statusCode = 400; 095 * } 096 * 097 * // Something else? Fall back to a 500 098 * default -> { 099 * message = "An unexpected error occurred."; 100 * statusCode = 500; 101 * } 102 * } 103 * 104 * // Turn response body into JSON bytes with Gson. 105 * // Note: real systems should expose richer error constructs 106 * // than an object with a single message field 107 * byte[] body = GSON.toJson(Map.of("message", message)) 108 * .getBytes(StandardCharsets.UTF_8); 109 * 110 * // Specify our headers 111 * Map<String, Set<String>> headers = new HashMap<>(); 112 * headers.put("Content-Type", Set.of("application/json;charset=UTF-8")); 113 * 114 * return MarshaledResponse.withStatusCode(statusCode) 115 * .headers(headers) 116 * .body(body) 117 * .build(); 118 * }; 119 * 120 * // Supply our custom handlers to the standard response marshaler 121 * SokletConfig config = SokletConfig.withHttpServer( 122 * HttpServer.fromPort(8080) 123 * ).responseMarshaler(ResponseMarshaler.builder() 124 * .resourceMethodHandler(resourceMethodHandler) 125 * .throwableHandler(throwableHandler) 126 * .build() 127 * ).build();}</pre> 128 * <p> 129 * Full documentation is available at <a href="https://www.soklet.com/docs/response-writing">https://www.soklet.com/docs/response-writing</a>. 130 * 131 * @author <a href="https://www.revetkn.com">Mark Allen</a> 132 */ 133public interface ResponseMarshaler { 134 /** 135 * Prepares a response for a request that was matched to a <em>Resource Method</em> and returned normally (i.e., without throwing an exception). 136 * <p> 137 * <strong>Note that the returned {@link Response} may represent any HTTP status (e.g., 200, 403, 404), and is not restricted to "successful" outcomes.</strong> 138 * <p> 139 * Detailed documentation is available at <a href="https://www.soklet.com/docs/response-writing#resource-method">https://www.soklet.com/docs/response-writing#resource-method</a>. 140 * 141 * @param request the HTTP request 142 * @param response the response provided by the <em>Resource Method</em> that handled the request 143 * @param resourceMethod the <em>Resource Method</em> that handled the request 144 * @return the response to be sent over the wire 145 */ 146 @NonNull 147 MarshaledResponse forResourceMethod(@NonNull Request request, 148 @NonNull Response response, 149 @NonNull ResourceMethod resourceMethod); 150 151 /** 152 * Prepares a response for a request that does not have a matching <em>Resource Method</em>, which triggers an <a href="https://httpwg.org/specs/rfc9110.html#status.404">HTTP 404 Not Found</a>. 153 * <p> 154 * Detailed documentation is available at <a href="https://www.soklet.com/docs/response-writing#404-not-found">https://www.soklet.com/docs/response-writing#404-not-found</a>. 155 * 156 * @param request the HTTP request 157 * @return the response to be sent over the wire 158 */ 159 @NonNull 160 MarshaledResponse forNotFound(@NonNull Request request); 161 162 /** 163 * Prepares a response for a request that triggers an 164 * <a href="https://httpwg.org/specs/rfc9110.html#status.405">HTTP 405 Method Not Allowed</a>. 165 * <p> 166 * Detailed documentation is available at <a href="https://www.soklet.com/docs/response-writing#405-method-not-allowed">https://www.soklet.com/docs/response-writing#405-method-not-allowed</a>. 167 * 168 * @param request the HTTP request 169 * @param allowedHttpMethods appropriate HTTP methods to write to the {@code Allow} response header 170 * @return the response to be sent over the wire 171 */ 172 @NonNull 173 MarshaledResponse forMethodNotAllowed(@NonNull Request request, 174 @NonNull Set<@NonNull HttpMethod> allowedHttpMethods); 175 176 /** 177 * Prepares a response for a request that triggers an <a href="https://httpwg.org/specs/rfc9110.html#status.413">HTTP 413 Content Too Large</a>. 178 * <p> 179 * This method is only invoked when the transport can parse enough request data to construct a {@link Request}. 180 * If the size limit is exceeded before a request target can be identified, the transport may close the connection instead. 181 * <p> 182 * Detailed documentation is available at <a href="https://www.soklet.com/docs/response-writing#413-content-too-large">https://www.soklet.com/docs/response-writing#413-content-too-large</a>. 183 * 184 * @param request the HTTP request 185 * @param resourceMethod the <em>Resource Method</em> that would have handled the request, if available 186 * @return the response to be sent over the wire 187 */ 188 @NonNull 189 MarshaledResponse forContentTooLarge(@NonNull Request request, 190 @Nullable ResourceMethod resourceMethod); 191 192 /** 193 * Prepares a response for a request that is rejected because the server is overloaded (e.g. connection limit reached), triggering an <a href="https://httpwg.org/specs/rfc9110.html#status.503">HTTP 503 Service Unavailable</a>. 194 * <p> 195 * Detailed documentation is available at <a href="https://www.soklet.com/docs/response-writing#503-service-unavailable">https://www.soklet.com/docs/response-writing#503-service-unavailable</a>. 196 * 197 * @param request the HTTP request 198 * @param resourceMethod the <em>Resource Method</em> that would have handled the request, if available 199 * @return the response to be sent over the wire 200 */ 201 @NonNull 202 MarshaledResponse forServiceUnavailable(@NonNull Request request, 203 @Nullable ResourceMethod resourceMethod); 204 205 /** 206 * Prepares a response for an HTTP {@code OPTIONS} request. 207 * <p> 208 * Note that CORS preflight responses are handled specially by {@link #forCorsPreflightAllowed(Request, CorsPreflight, CorsPreflightResponse)} 209 * and {@link #forCorsPreflightRejected(Request, CorsPreflight)} - not this method. 210 * <p> 211 * Detailed documentation is available at <a href="https://www.soklet.com/docs/response-writing#http-options">https://www.soklet.com/docs/response-writing#http-options</a>. 212 * 213 * @param request the HTTP request 214 * @param allowedHttpMethods appropriate HTTP methods to write to the {@code Allow} response header 215 * @return the response to be sent over the wire 216 */ 217 @NonNull 218 MarshaledResponse forOptions(@NonNull Request request, 219 @NonNull Set<@NonNull HttpMethod> allowedHttpMethods); 220 221 /** 222 * Prepares a response for an HTTP {@code OPTIONS *} (colloquially, "{@code OPTIONS} Splat") request. 223 * <p> 224 * This is a special HTTP/1.1 request defined in RFC 7231 §4.3.7 which permits querying of server-wide capabilities, not the capabilities of a particular resource - e.g. a load balancer asking "is the system up?" without an explicit health-check URL. 225 * <p> 226 * Detailed documentation is available at <a href="https://www.soklet.com/docs/response-writing#http-options">https://www.soklet.com/docs/response-writing#http-options</a>. 227 * 228 * @param request the HTTP request 229 * @return the response to be sent over the wire 230 */ 231 @NonNull 232 MarshaledResponse forOptionsSplat(@NonNull Request request); 233 234 /** 235 * Prepares a response for scenarios in which an uncaught exception is encountered. 236 * <p> 237 * Detailed documentation is available at <a href="https://www.soklet.com/docs/response-writing#uncaught-exceptions">https://www.soklet.com/docs/response-writing#uncaught-exceptions</a>. 238 * 239 * @param request the HTTP request 240 * @param throwable the exception that was thrown 241 * @param resourceMethod the <em>Resource Method</em> that would have handled the request, if available 242 * @return the response to be sent over the wire 243 */ 244 @NonNull 245 MarshaledResponse forThrowable(@NonNull Request request, 246 @NonNull Throwable throwable, 247 @Nullable ResourceMethod resourceMethod); 248 249 /** 250 * Prepares a response for an HTTP {@code HEAD} request. 251 * <p> 252 * Detailed documentation is available at <a href="https://www.soklet.com/docs/response-writing#http-head">https://www.soklet.com/docs/response-writing#http-head</a>. 253 * 254 * @param request the HTTP request 255 * @param getMethodMarshaledResponse the marshaled response that would have been sent over the wire for an equivalent {@code GET} request (necessary in order to write the {@code Content-Length} header for a {@code HEAD} response) 256 * @return the response to be sent over the wire 257 */ 258 @NonNull 259 MarshaledResponse forHead(@NonNull Request request, 260 @NonNull MarshaledResponse getMethodMarshaledResponse); 261 262 /** 263 * Prepares a response for "CORS preflight allowed" scenario when your {@link CorsAuthorizer} approves a preflight request. 264 * <p> 265 * Detailed documentation is available at <a href="https://www.soklet.com/docs/cors#writing-cors-responses">https://www.soklet.com/docs/cors#writing-cors-responses</a>. 266 * 267 * @param request the HTTP request 268 * @param corsPreflight the CORS preflight request data 269 * @param corsPreflightResponse the data that should be included in this CORS preflight response 270 * @return the response to be sent over the wire 271 */ 272 @NonNull 273 MarshaledResponse forCorsPreflightAllowed(@NonNull Request request, 274 @NonNull CorsPreflight corsPreflight, 275 @NonNull CorsPreflightResponse corsPreflightResponse); 276 277 /** 278 * Prepares a response for "CORS preflight rejected" scenario when your {@link CorsAuthorizer} denies a preflight request. 279 * <p> 280 * Detailed documentation is available at <a href="https://www.soklet.com/docs/cors#writing-cors-responses">https://www.soklet.com/docs/cors#writing-cors-responses</a>. 281 * 282 * @param request the HTTP request 283 * @param corsPreflight the CORS preflight request data 284 * @return the response to be sent over the wire 285 */ 286 @NonNull 287 MarshaledResponse forCorsPreflightRejected(@NonNull Request request, 288 @NonNull CorsPreflight corsPreflight); 289 290 /** 291 * Applies "CORS is permitted for this request" data to a response. 292 * <p> 293 * Invoked for any non-preflight CORS request that your {@link CorsAuthorizer} approves. 294 * <p> 295 * This method will normally return a copy of the {@code marshaledResponse} with these headers applied 296 * based on the values of {@code corsResponse}: 297 * <ul> 298 * <li>{@code Access-Control-Allow-Origin} (required)</li> 299 * <li>{@code Access-Control-Allow-Credentials} (optional)</li> 300 * <li>{@code Access-Control-Expose-Headers} (optional)</li> 301 * </ul> 302 * 303 * @param request the HTTP request 304 * @param cors the CORS request data 305 * @param corsResponse CORS response data to write as specified by {@link CorsAuthorizer} 306 * @param marshaledResponse the existing response to which we should apply relevant CORS headers 307 * @return the response to be sent over the wire 308 */ 309 @NonNull 310 MarshaledResponse forCorsAllowed(@NonNull Request request, 311 @NonNull Cors cors, 312 @NonNull CorsResponse corsResponse, 313 @NonNull MarshaledResponse marshaledResponse); 314 315 /** 316 * Acquires a threadsafe {@link ResponseMarshaler} with a reasonable "out of the box" configuration that uses UTF-8 to write character data. 317 * <p> 318 * The returned instance is guaranteed to be a JVM-wide singleton. 319 * 320 * @return a UTF-8 {@code ResponseMarshaler} with default settings 321 */ 322 @NonNull 323 static ResponseMarshaler defaultInstance() { 324 return DefaultResponseMarshaler.defaultInstance(); 325 } 326 327 /** 328 * Acquires a builder for a default {@link ResponseMarshaler} implementation that uses {@link java.nio.charset.StandardCharsets#UTF_8} encoding. 329 * 330 * @return a {@code ResponseMarshaler} builder 331 */ 332 @NonNull 333 static Builder builder() { 334 return new Builder(StandardCharsets.UTF_8); 335 } 336 337 /** 338 * Acquires a builder for a default {@link ResponseMarshaler} implementation. 339 * 340 * @param charset the default charset to use when writing response data 341 * @return a {@code ResponseMarshaler} builder 342 */ 343 @NonNull 344 static Builder withCharset(@NonNull Charset charset) { 345 requireNonNull(charset); 346 return new Builder(charset); 347 } 348 349 /** 350 * Builder used to construct a standard implementation of {@link ResponseMarshaler}. 351 * <p> 352 * This class is intended for use by a single thread. 353 * 354 * @author <a href="https://www.revetkn.com">Mark Allen</a> 355 */ 356 @NotThreadSafe 357 final class Builder { 358 /** 359 * Function used to support pluggable implementations of {@link ResponseMarshaler#forResourceMethod(Request, Response, ResourceMethod)}. 360 */ 361 @FunctionalInterface 362 public interface ResourceMethodHandler { 363 /** 364 * Prepares a response for the scenario in which the request was matched to a <em>Resource Method</em> and executed non-exceptionally. 365 * <p> 366 * Detailed documentation is available at <a href="https://www.soklet.com/docs/response-writing#resource-method">https://www.soklet.com/docs/response-writing#resource-method</a>. 367 * 368 * @param request the HTTP request 369 * @param response the response provided by the <em>Resource Method</em> that handled the request 370 * @param resourceMethod the <em>Resource Method</em> that handled the request 371 * @return the response to be sent over the wire 372 */ 373 @NonNull 374 MarshaledResponse handle(@NonNull Request request, 375 @NonNull Response response, 376 @NonNull ResourceMethod resourceMethod); 377 } 378 379 /** 380 * Function used to support pluggable implementations of {@link ResponseMarshaler#forNotFound(Request)}. 381 */ 382 @FunctionalInterface 383 public interface NotFoundHandler { 384 /** 385 * Prepares a response for a request that triggers an 386 * <a href="https://httpwg.org/specs/rfc9110.html#status.404">HTTP 404 Not Found</a>. 387 * <p> 388 * Detailed documentation is available at <a href="https://www.soklet.com/docs/response-writing#404-not-found">https://www.soklet.com/docs/response-writing#404-not-found</a>. 389 * 390 * @param request the HTTP request 391 * @return the response to be sent over the wire 392 */ 393 @NonNull 394 MarshaledResponse handle(@NonNull Request request); 395 } 396 397 /** 398 * Function used to support pluggable implementations of {@link ResponseMarshaler#forMethodNotAllowed(Request, Set)}. 399 */ 400 @FunctionalInterface 401 public interface MethodNotAllowedHandler { 402 /** 403 * Prepares a response for a request that triggers an 404 * <a href="https://httpwg.org/specs/rfc9110.html#status.405">HTTP 405 Method Not Allowed</a>. 405 * <p> 406 * Detailed documentation is available at <a href="https://www.soklet.com/docs/response-writing#405-method-not-allowed">https://www.soklet.com/docs/response-writing#405-method-not-allowed</a>. 407 * 408 * @param request the HTTP request 409 * @param allowedHttpMethods appropriate HTTP methods to write to the {@code Allow} response header 410 * @return the response to be sent over the wire 411 */ 412 @NonNull 413 MarshaledResponse handle(@NonNull Request request, 414 @NonNull Set<@NonNull HttpMethod> allowedHttpMethods); 415 } 416 417 /** 418 * Function used to support pluggable implementations of {@link ResponseMarshaler#forContentTooLarge(Request, ResourceMethod)}. 419 */ 420 @FunctionalInterface 421 public interface ContentTooLargeHandler { 422 /** 423 * Prepares a response for a request that triggers an <a href="https://httpwg.org/specs/rfc9110.html#status.413">HTTP 413 Content Too Large</a>. 424 * <p> 425 * Detailed documentation is available at <a href="https://www.soklet.com/docs/response-writing#413-content-too-large">https://www.soklet.com/docs/response-writing#413-content-too-large</a>. 426 * 427 * @param request the HTTP request 428 * @param resourceMethod the <em>Resource Method</em> that would have handled the request, if available 429 * @return the response to be sent over the wire 430 */ 431 @NonNull 432 MarshaledResponse handle(@NonNull Request request, 433 @Nullable ResourceMethod resourceMethod); 434 } 435 436 /** 437 * Function used to support pluggable implementations of {@link ResponseMarshaler#forServiceUnavailable(Request, ResourceMethod)}. 438 */ 439 @FunctionalInterface 440 public interface ServiceUnavailableHandler { 441 /** 442 * Prepares a response for a request that triggers an <a href="https://httpwg.org/specs/rfc9110.html#status.503">HTTP 503 Service Unavailable</a>. 443 * <p> 444 * Detailed documentation is available at <a href="https://www.soklet.com/docs/response-writing#503-service-unavailable">https://www.soklet.com/docs/response-writing#503-service-unavailable</a>. 445 * 446 * @param request the HTTP request 447 * @param resourceMethod the <em>Resource Method</em> that would have handled the request, if available 448 * @return the response to be sent over the wire 449 */ 450 @NonNull 451 MarshaledResponse handle(@NonNull Request request, 452 @Nullable ResourceMethod resourceMethod); 453 } 454 455 /** 456 * Function used to support pluggable implementations of {@link ResponseMarshaler#forOptions(Request, Set)}. 457 */ 458 @FunctionalInterface 459 public interface OptionsHandler { 460 /** 461 * Prepares a response for an HTTP {@code OPTIONS} request. 462 * <p> 463 * Note that CORS preflight responses are handled specially by {@link #forCorsPreflightAllowed(Request, CorsPreflight, CorsPreflightResponse)} 464 * and {@link #forCorsPreflightRejected(Request, CorsPreflight)} - not this method. 465 * <p> 466 * Detailed documentation is available at <a href="https://www.soklet.com/docs/response-writing#http-options">https://www.soklet.com/docs/response-writing#http-options</a>. 467 * 468 * @param request the HTTP request 469 * @param allowedHttpMethods appropriate HTTP methods to write to the {@code Allow} response header 470 * @return the response to be sent over the wire 471 */ 472 @NonNull 473 MarshaledResponse handle(@NonNull Request request, 474 @NonNull Set<@NonNull HttpMethod> allowedHttpMethods); 475 } 476 477 /** 478 * Function used to support pluggable implementations of {@link ResponseMarshaler#forOptionsSplat(Request)}. 479 */ 480 @FunctionalInterface 481 public interface OptionsSplatHandler { 482 /** 483 * Prepares a response for an HTTP {@code OPTIONS *} (colloquially, "{@code OPTIONS} Splat") request. 484 * <p> 485 * This is a special HTTP/1.1 request defined in RFC 7231 §4.3.7 which permits querying of server-wide capabilities, not the capabilities of a particular resource - e.g. a load balancer asking "is the system up?" without an explicit health-check URL. 486 * <p> 487 * Detailed documentation is available at <a href="https://www.soklet.com/docs/response-writing#http-options">https://www.soklet.com/docs/response-writing#http-options</a>. 488 * 489 * @param request the HTTP request 490 * @return the response to be sent over the wire 491 */ 492 @NonNull 493 MarshaledResponse handle(@NonNull Request request); 494 } 495 496 /** 497 * Function used to support pluggable implementations of {@link ResponseMarshaler#forThrowable(Request, Throwable, ResourceMethod)}. 498 */ 499 @FunctionalInterface 500 public interface ThrowableHandler { 501 /** 502 * Prepares a response for scenarios in which an uncaught exception is encountered. 503 * <p> 504 * Detailed documentation is available at <a href="https://www.soklet.com/docs/response-writing#uncaught-exceptions">https://www.soklet.com/docs/response-writing#uncaught-exceptions</a>. 505 * 506 * @param request the HTTP request 507 * @param throwable the exception that was thrown 508 * @param resourceMethod the <em>Resource Method</em> that would have handled the request, if available 509 * @return the response to be sent over the wire 510 */ 511 @NonNull 512 MarshaledResponse handle(@NonNull Request request, 513 @NonNull Throwable throwable, 514 @Nullable ResourceMethod resourceMethod); 515 } 516 517 /** 518 * Function used to support pluggable implementations of {@link ResponseMarshaler#forHead(Request, MarshaledResponse)}. 519 */ 520 @FunctionalInterface 521 public interface HeadHandler { 522 /** 523 * Prepares a response for an HTTP {@code HEAD} request. 524 * <p> 525 * Detailed documentation is available at <a href="https://www.soklet.com/docs/response-writing#http-head">https://www.soklet.com/docs/response-writing#http-head</a>. 526 * 527 * @param request the HTTP request 528 * @param getMethodMarshaledResponse the marshaled response that would have been sent over the wire for an equivalent {@code GET} request (necessary in order to write the {@code Content-Length} header for a {@code HEAD} response) 529 * @return the response to be sent over the wire 530 */ 531 @NonNull 532 MarshaledResponse handle(@NonNull Request request, 533 @NonNull MarshaledResponse getMethodMarshaledResponse); 534 } 535 536 /** 537 * Function used to support pluggable implementations of {@link ResponseMarshaler#forCorsPreflightAllowed(Request, CorsPreflight, CorsPreflightResponse)}. 538 */ 539 @FunctionalInterface 540 public interface CorsPreflightAllowedHandler { 541 /** 542 * Prepares a response for "CORS preflight allowed" scenario when your {@link CorsAuthorizer} approves a preflight request. 543 * <p> 544 * Detailed documentation is available at <a href="https://www.soklet.com/docs/cors#writing-cors-responses">https://www.soklet.com/docs/cors#writing-cors-responses</a>. 545 * 546 * @param request the HTTP request 547 * @param corsPreflight the CORS preflight request data 548 * @param corsPreflightResponse the data that should be included in this CORS preflight response 549 * @return the response to be sent over the wire 550 */ 551 @NonNull 552 MarshaledResponse handle(@NonNull Request request, 553 @NonNull CorsPreflight corsPreflight, 554 @NonNull CorsPreflightResponse corsPreflightResponse); 555 } 556 557 /** 558 * Function used to support pluggable implementations of {@link ResponseMarshaler#forCorsPreflightRejected(Request, CorsPreflight)}. 559 */ 560 @FunctionalInterface 561 public interface CorsPreflightRejectedHandler { 562 /** 563 * Prepares a response for "CORS preflight rejected" scenario when your {@link CorsAuthorizer} denies a preflight request. 564 * <p> 565 * Detailed documentation is available at <a href="https://www.soklet.com/docs/cors#writing-cors-responses">https://www.soklet.com/docs/cors#writing-cors-responses</a>. 566 * 567 * @param request the HTTP request 568 * @param corsPreflight the CORS preflight request data 569 * @return the response to be sent over the wire 570 */ 571 @NonNull 572 MarshaledResponse handle(@NonNull Request request, 573 @NonNull CorsPreflight corsPreflight); 574 } 575 576 /** 577 * Function used to support pluggable implementations of {@link ResponseMarshaler#forCorsAllowed(Request, Cors, CorsResponse, MarshaledResponse)}. 578 */ 579 @FunctionalInterface 580 public interface CorsAllowedHandler { 581 /** 582 * Applies "CORS is permitted for this request" data to a response. 583 * <p> 584 * Invoked for any non-preflight CORS request that your {@link CorsAuthorizer} approves. 585 * <p> 586 * This method will normally return a copy of the {@code marshaledResponse} with these headers applied 587 * based on the values of {@code corsResponse}: 588 * <ul> 589 * <li>{@code Access-Control-Allow-Origin} (required)</li> 590 * <li>{@code Access-Control-Allow-Credentials} (optional)</li> 591 * <li>{@code Access-Control-Expose-Headers} (optional)</li> 592 * </ul> 593 * 594 * @param request the HTTP request 595 * @param cors the CORS request data 596 * @param corsResponse CORS response data to write as specified by {@link CorsAuthorizer} 597 * @param marshaledResponse the existing response to which we should apply relevant CORS headers 598 * @return the response to be sent over the wire 599 */ 600 @NonNull 601 MarshaledResponse handle(@NonNull Request request, 602 @NonNull Cors cors, 603 @NonNull CorsResponse corsResponse, 604 @NonNull MarshaledResponse marshaledResponse); 605 } 606 607 /** 608 * Function used to support a pluggable "post-process" hook for any final customization or processing before data goes over the wire. 609 */ 610 @FunctionalInterface 611 public interface PostProcessor { 612 /** 613 * Applies an optional "post-process" hook for any final customization or processing before data goes over the wire. 614 * 615 * @param marshaledResponse the response data generated by the appropriate handler function, but not yet sent over the wire 616 * @return the response data (possibly customized) to be sent over the wire 617 */ 618 @NonNull 619 MarshaledResponse postProcess(@NonNull MarshaledResponse marshaledResponse); 620 } 621 622 @NonNull 623 Charset charset; 624 @Nullable 625 ResourceMethodHandler resourceMethodHandler; 626 @Nullable 627 NotFoundHandler notFoundHandler; 628 @Nullable 629 MethodNotAllowedHandler methodNotAllowedHandler; 630 @Nullable 631 ContentTooLargeHandler contentTooLargeHandler; 632 @Nullable 633 ServiceUnavailableHandler serviceUnavailableHandler; 634 @Nullable 635 OptionsHandler optionsHandler; 636 @Nullable 637 OptionsSplatHandler optionsSplatHandler; 638 @Nullable 639 ThrowableHandler throwableHandler; 640 @Nullable 641 HeadHandler headHandler; 642 @Nullable 643 CorsPreflightAllowedHandler corsPreflightAllowedHandler; 644 @Nullable 645 CorsPreflightRejectedHandler corsPreflightRejectedHandler; 646 @Nullable 647 CorsAllowedHandler corsAllowedHandler; 648 @Nullable 649 PostProcessor postProcessor; 650 651 private Builder(@NonNull Charset charset) { 652 requireNonNull(charset); 653 this.charset = charset; 654 } 655 656 @NonNull 657 public Builder charset(@NonNull Charset charset) { 658 requireNonNull(charset); 659 this.charset = charset; 660 return this; 661 } 662 663 @NonNull 664 public Builder resourceMethodHandler(@Nullable ResourceMethodHandler resourceMethodHandler) { 665 this.resourceMethodHandler = resourceMethodHandler; 666 return this; 667 } 668 669 @NonNull 670 public Builder notFoundHandler(@Nullable NotFoundHandler notFoundHandler) { 671 this.notFoundHandler = notFoundHandler; 672 return this; 673 } 674 675 @NonNull 676 public Builder methodNotAllowedHandler(@Nullable MethodNotAllowedHandler methodNotAllowedHandler) { 677 this.methodNotAllowedHandler = methodNotAllowedHandler; 678 return this; 679 } 680 681 @NonNull 682 public Builder contentTooLargeHandler(@Nullable ContentTooLargeHandler contentTooLargeHandler) { 683 this.contentTooLargeHandler = contentTooLargeHandler; 684 return this; 685 } 686 687 @NonNull 688 public Builder serviceUnavailableHandler(@Nullable ServiceUnavailableHandler serviceUnavailableHandler) { 689 this.serviceUnavailableHandler = serviceUnavailableHandler; 690 return this; 691 } 692 693 @NonNull 694 public Builder optionsHandler(@Nullable OptionsHandler optionsHandler) { 695 this.optionsHandler = optionsHandler; 696 return this; 697 } 698 699 @NonNull 700 public Builder optionsSplatHandler(@Nullable OptionsSplatHandler optionsSplatHandler) { 701 this.optionsSplatHandler = optionsSplatHandler; 702 return this; 703 } 704 705 @NonNull 706 public Builder throwableHandler(@Nullable ThrowableHandler throwableHandler) { 707 this.throwableHandler = throwableHandler; 708 return this; 709 } 710 711 @NonNull 712 public Builder headHandler(@Nullable HeadHandler headHandler) { 713 this.headHandler = headHandler; 714 return this; 715 } 716 717 @NonNull 718 public Builder corsPreflightAllowedHandler(@Nullable CorsPreflightAllowedHandler corsPreflightAllowedHandler) { 719 this.corsPreflightAllowedHandler = corsPreflightAllowedHandler; 720 return this; 721 } 722 723 @NonNull 724 public Builder corsPreflightRejectedHandler(@Nullable CorsPreflightRejectedHandler corsPreflightRejectedHandler) { 725 this.corsPreflightRejectedHandler = corsPreflightRejectedHandler; 726 return this; 727 } 728 729 @NonNull 730 public Builder corsAllowedHandler(@Nullable CorsAllowedHandler corsAllowedHandler) { 731 this.corsAllowedHandler = corsAllowedHandler; 732 return this; 733 } 734 735 @NonNull 736 public Builder postProcessor(@Nullable PostProcessor postProcessor) { 737 this.postProcessor = postProcessor; 738 return this; 739 } 740 741 @NonNull 742 public ResponseMarshaler build() { 743 return new DefaultResponseMarshaler(this); 744 } 745 } 746}