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.withServer( 122 * Server.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 * 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>. 180 * 181 * @param request the HTTP request 182 * @param resourceMethod the <em>Resource Method</em> that would have handled the request, if available 183 * @return the response to be sent over the wire 184 */ 185 @NonNull 186 MarshaledResponse forContentTooLarge(@NonNull Request request, 187 @Nullable ResourceMethod resourceMethod); 188 189 /** 190 * 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>. 191 * <p> 192 * 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>. 193 * 194 * @param request the HTTP request 195 * @param resourceMethod the <em>Resource Method</em> that would have handled the request, if available 196 * @return the response to be sent over the wire 197 */ 198 @NonNull 199 MarshaledResponse forServiceUnavailable(@NonNull Request request, 200 @Nullable ResourceMethod resourceMethod); 201 202 /** 203 * Prepares a response for an HTTP {@code OPTIONS} request. 204 * <p> 205 * Note that CORS preflight responses are handled specially by {@link #forCorsPreflightAllowed(Request, CorsPreflight, CorsPreflightResponse)} 206 * and {@link #forCorsPreflightRejected(Request, CorsPreflight)} - not this method. 207 * <p> 208 * 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>. 209 * 210 * @param request the HTTP request 211 * @param allowedHttpMethods appropriate HTTP methods to write to the {@code Allow} response header 212 * @return the response to be sent over the wire 213 */ 214 @NonNull 215 MarshaledResponse forOptions(@NonNull Request request, 216 @NonNull Set<@NonNull HttpMethod> allowedHttpMethods); 217 218 /** 219 * Prepares a response for an HTTP {@code OPTIONS *} (colloquially, "{@code OPTIONS} Splat") request. 220 * <p> 221 * 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. 222 * <p> 223 * 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>. 224 * 225 * @param request the HTTP request 226 * @return the response to be sent over the wire 227 */ 228 @NonNull 229 MarshaledResponse forOptionsSplat(@NonNull Request request); 230 231 /** 232 * Prepares a response for scenarios in which an uncaught exception is encountered. 233 * <p> 234 * 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>. 235 * 236 * @param request the HTTP request 237 * @param throwable the exception that was thrown 238 * @param resourceMethod the <em>Resource Method</em> that would have handled the request, if available 239 * @return the response to be sent over the wire 240 */ 241 @NonNull 242 MarshaledResponse forThrowable(@NonNull Request request, 243 @NonNull Throwable throwable, 244 @Nullable ResourceMethod resourceMethod); 245 246 /** 247 * Prepares a response for an HTTP {@code HEAD} request. 248 * <p> 249 * 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>. 250 * 251 * @param request the HTTP request 252 * @param getMethodMarshaledResponse the binary data 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) 253 * @return the response to be sent over the wire 254 */ 255 @NonNull 256 MarshaledResponse forHead(@NonNull Request request, 257 @NonNull MarshaledResponse getMethodMarshaledResponse); 258 259 /** 260 * Prepares a response for "CORS preflight allowed" scenario when your {@link CorsAuthorizer} approves a preflight request. 261 * <p> 262 * 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>. 263 * 264 * @param request the HTTP request 265 * @param corsPreflight the CORS preflight request data 266 * @param corsPreflightResponse the data that should be included in this CORS preflight response 267 * @return the response to be sent over the wire 268 */ 269 @NonNull 270 MarshaledResponse forCorsPreflightAllowed(@NonNull Request request, 271 @NonNull CorsPreflight corsPreflight, 272 @NonNull CorsPreflightResponse corsPreflightResponse); 273 274 /** 275 * Prepares a response for "CORS preflight rejected" scenario when your {@link CorsAuthorizer} denies a preflight request. 276 * <p> 277 * 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>. 278 * 279 * @param request the HTTP request 280 * @param corsPreflight the CORS preflight request data 281 * @return the response to be sent over the wire 282 */ 283 @NonNull 284 MarshaledResponse forCorsPreflightRejected(@NonNull Request request, 285 @NonNull CorsPreflight corsPreflight); 286 287 /** 288 * Applies "CORS is permitted for this request" data to a response. 289 * <p> 290 * Invoked for any non-preflight CORS request that your {@link CorsAuthorizer} approves. 291 * <p> 292 * This method will normally return a copy of the {@code marshaledResponse} with these headers applied 293 * based on the values of {@code corsResponse}: 294 * <ul> 295 * <li>{@code Access-Control-Allow-Origin} (required)</li> 296 * <li>{@code Access-Control-Allow-Credentials} (optional)</li> 297 * <li>{@code Access-Control-Expose-Headers} (optional)</li> 298 * </ul> 299 * 300 * @param request the HTTP request 301 * @param cors the CORS request data 302 * @param corsResponse CORS response data to write as specified by {@link CorsAuthorizer} 303 * @param marshaledResponse the existing response to which we should apply relevant CORS headers 304 * @return the response to be sent over the wire 305 */ 306 @NonNull 307 MarshaledResponse forCorsAllowed(@NonNull Request request, 308 @NonNull Cors cors, 309 @NonNull CorsResponse corsResponse, 310 @NonNull MarshaledResponse marshaledResponse); 311 312 /** 313 * Acquires a threadsafe {@link ResponseMarshaler} with a reasonable "out of the box" configuration that uses UTF-8 to write character data. 314 * <p> 315 * The returned instance is guaranteed to be a JVM-wide singleton. 316 * 317 * @return a UTF-8 {@code ResponseMarshaler} with default settings 318 */ 319 @NonNull 320 static ResponseMarshaler defaultInstance() { 321 return DefaultResponseMarshaler.defaultInstance(); 322 } 323 324 /** 325 * Acquires a builder for a default {@link ResponseMarshaler} implementation that uses {@link java.nio.charset.StandardCharsets#UTF_8} encoding. 326 * 327 * @return a {@code ResponseMarshaler} builder 328 */ 329 @NonNull 330 static Builder builder() { 331 return new Builder(StandardCharsets.UTF_8); 332 } 333 334 /** 335 * Acquires a builder for a default {@link ResponseMarshaler} implementation. 336 * 337 * @param charset the default charset to use when writing response data 338 * @return a {@code ResponseMarshaler} builder 339 */ 340 @NonNull 341 static Builder withCharset(@NonNull Charset charset) { 342 requireNonNull(charset); 343 return new Builder(charset); 344 } 345 346 /** 347 * Builder used to construct a standard implementation of {@link ResponseMarshaler}. 348 * <p> 349 * This class is intended for use by a single thread. 350 * 351 * @author <a href="https://www.revetkn.com">Mark Allen</a> 352 */ 353 @NotThreadSafe 354 final class Builder { 355 /** 356 * Function used to support pluggable implementations of {@link ResponseMarshaler#forResourceMethod(Request, Response, ResourceMethod)}. 357 */ 358 @FunctionalInterface 359 public interface ResourceMethodHandler { 360 /** 361 * Prepares a response for the scenario in which the request was matched to a <em>Resource Method</em> and executed non-exceptionally. 362 * <p> 363 * 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>. 364 * 365 * @param request the HTTP request 366 * @param response the response provided by the <em>Resource Method</em> that handled the request 367 * @param resourceMethod the <em>Resource Method</em> that handled the request 368 * @return the response to be sent over the wire 369 */ 370 @NonNull 371 MarshaledResponse handle(@NonNull Request request, 372 @NonNull Response response, 373 @NonNull ResourceMethod resourceMethod); 374 } 375 376 /** 377 * Function used to support pluggable implementations of {@link ResponseMarshaler#forNotFound(Request)}. 378 */ 379 @FunctionalInterface 380 public interface NotFoundHandler { 381 /** 382 * Prepares a response for a request that triggers an 383 * <a href="https://httpwg.org/specs/rfc9110.html#status.404">HTTP 404 Not Found</a>. 384 * <p> 385 * 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>. 386 * 387 * @param request the HTTP request 388 * @return the response to be sent over the wire 389 */ 390 @NonNull 391 MarshaledResponse handle(@NonNull Request request); 392 } 393 394 /** 395 * Function used to support pluggable implementations of {@link ResponseMarshaler#forMethodNotAllowed(Request, Set)}. 396 */ 397 @FunctionalInterface 398 public interface MethodNotAllowedHandler { 399 /** 400 * Prepares a response for a request that triggers an 401 * <a href="https://httpwg.org/specs/rfc9110.html#status.405">HTTP 405 Method Not Allowed</a>. 402 * <p> 403 * 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>. 404 * 405 * @param request the HTTP request 406 * @param allowedHttpMethods appropriate HTTP methods to write to the {@code Allow} response header 407 * @return the response to be sent over the wire 408 */ 409 @NonNull 410 MarshaledResponse handle(@NonNull Request request, 411 @NonNull Set<@NonNull HttpMethod> allowedHttpMethods); 412 } 413 414 /** 415 * Function used to support pluggable implementations of {@link ResponseMarshaler#forContentTooLarge(Request, ResourceMethod)}. 416 */ 417 @FunctionalInterface 418 public interface ContentTooLargeHandler { 419 /** 420 * 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>. 421 * <p> 422 * 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>. 423 * 424 * @param request the HTTP request 425 * @param resourceMethod the <em>Resource Method</em> that would have handled the request, if available 426 * @return the response to be sent over the wire 427 */ 428 @NonNull 429 MarshaledResponse handle(@NonNull Request request, 430 @Nullable ResourceMethod resourceMethod); 431 } 432 433 /** 434 * Function used to support pluggable implementations of {@link ResponseMarshaler#forServiceUnavailable(Request, ResourceMethod)}. 435 */ 436 @FunctionalInterface 437 public interface ServiceUnavailableHandler { 438 /** 439 * Prepares a response for a request that triggers an <a href="https://httpwg.org/specs/rfc9110.html#status.503">HTTP 503 Service Unavailable</a>. 440 * <p> 441 * 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>. 442 * 443 * @param request the HTTP request 444 * @param resourceMethod the <em>Resource Method</em> that would have handled the request, if available 445 * @return the response to be sent over the wire 446 */ 447 @NonNull 448 MarshaledResponse handle(@NonNull Request request, 449 @Nullable ResourceMethod resourceMethod); 450 } 451 452 /** 453 * Function used to support pluggable implementations of {@link ResponseMarshaler#forOptions(Request, Set)}. 454 */ 455 @FunctionalInterface 456 public interface OptionsHandler { 457 /** 458 * Prepares a response for an HTTP {@code OPTIONS} request. 459 * <p> 460 * Note that CORS preflight responses are handled specially by {@link #forCorsPreflightAllowed(Request, CorsPreflight, CorsPreflightResponse)} 461 * and {@link #forCorsPreflightRejected(Request, CorsPreflight)} - not this method. 462 * <p> 463 * 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>. 464 * 465 * @param request the HTTP request 466 * @param allowedHttpMethods appropriate HTTP methods to write to the {@code Allow} response header 467 * @return the response to be sent over the wire 468 */ 469 @NonNull 470 MarshaledResponse handle(@NonNull Request request, 471 @NonNull Set<@NonNull HttpMethod> allowedHttpMethods); 472 } 473 474 /** 475 * Function used to support pluggable implementations of {@link ResponseMarshaler#forOptionsSplat(Request)}. 476 */ 477 @FunctionalInterface 478 public interface OptionsSplatHandler { 479 /** 480 * Prepares a response for an HTTP {@code OPTIONS *} (colloquially, "{@code OPTIONS} Splat") request. 481 * <p> 482 * 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. 483 * <p> 484 * 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>. 485 * 486 * @param request the HTTP request 487 * @return the response to be sent over the wire 488 */ 489 @NonNull 490 MarshaledResponse handle(@NonNull Request request); 491 } 492 493 /** 494 * Function used to support pluggable implementations of {@link ResponseMarshaler#forThrowable(Request, Throwable, ResourceMethod)}. 495 */ 496 @FunctionalInterface 497 public interface ThrowableHandler { 498 /** 499 * Prepares a response for scenarios in which an uncaught exception is encountered. 500 * <p> 501 * 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>. 502 * 503 * @param request the HTTP request 504 * @param throwable the exception that was thrown 505 * @param resourceMethod the <em>Resource Method</em> that would have handled the request, if available 506 * @return the response to be sent over the wire 507 */ 508 @NonNull 509 MarshaledResponse handle(@NonNull Request request, 510 @NonNull Throwable throwable, 511 @Nullable ResourceMethod resourceMethod); 512 } 513 514 /** 515 * Function used to support pluggable implementations of {@link ResponseMarshaler#forHead(Request, MarshaledResponse)}. 516 */ 517 @FunctionalInterface 518 public interface HeadHandler { 519 /** 520 * Prepares a response for an HTTP {@code HEAD} request. 521 * <p> 522 * 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>. 523 * 524 * @param request the HTTP request 525 * @param getMethodMarshaledResponse the binary data 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) 526 * @return the response to be sent over the wire 527 */ 528 @NonNull 529 MarshaledResponse handle(@NonNull Request request, 530 @NonNull MarshaledResponse getMethodMarshaledResponse); 531 } 532 533 /** 534 * Function used to support pluggable implementations of {@link ResponseMarshaler#forCorsPreflightAllowed(Request, CorsPreflight, CorsPreflightResponse)}. 535 */ 536 @FunctionalInterface 537 public interface CorsPreflightAllowedHandler { 538 /** 539 * Prepares a response for "CORS preflight allowed" scenario when your {@link CorsAuthorizer} approves a preflight request. 540 * <p> 541 * 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>. 542 * 543 * @param request the HTTP request 544 * @param corsPreflight the CORS preflight request data 545 * @param corsPreflightResponse the data that should be included in this CORS preflight response 546 * @return the response to be sent over the wire 547 */ 548 @NonNull 549 MarshaledResponse handle(@NonNull Request request, 550 @NonNull CorsPreflight corsPreflight, 551 @NonNull CorsPreflightResponse corsPreflightResponse); 552 } 553 554 /** 555 * Function used to support pluggable implementations of {@link ResponseMarshaler#forCorsPreflightRejected(Request, CorsPreflight)}. 556 */ 557 @FunctionalInterface 558 public interface CorsPreflightRejectedHandler { 559 /** 560 * Prepares a response for "CORS preflight rejected" scenario when your {@link CorsAuthorizer} denies a preflight request. 561 * <p> 562 * 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>. 563 * 564 * @param request the HTTP request 565 * @param corsPreflight the CORS preflight request data 566 * @return the response to be sent over the wire 567 */ 568 @NonNull 569 MarshaledResponse handle(@NonNull Request request, 570 @NonNull CorsPreflight corsPreflight); 571 } 572 573 /** 574 * Function used to support pluggable implementations of {@link ResponseMarshaler#forCorsAllowed(Request, Cors, CorsResponse, MarshaledResponse)}. 575 */ 576 @FunctionalInterface 577 public interface CorsAllowedHandler { 578 /** 579 * Applies "CORS is permitted for this request" data to a response. 580 * <p> 581 * Invoked for any non-preflight CORS request that your {@link CorsAuthorizer} approves. 582 * <p> 583 * This method will normally return a copy of the {@code marshaledResponse} with these headers applied 584 * based on the values of {@code corsResponse}: 585 * <ul> 586 * <li>{@code Access-Control-Allow-Origin} (required)</li> 587 * <li>{@code Access-Control-Allow-Credentials} (optional)</li> 588 * <li>{@code Access-Control-Expose-Headers} (optional)</li> 589 * </ul> 590 * 591 * @param request the HTTP request 592 * @param cors the CORS request data 593 * @param corsResponse CORS response data to write as specified by {@link CorsAuthorizer} 594 * @param marshaledResponse the existing response to which we should apply relevant CORS headers 595 * @return the response to be sent over the wire 596 */ 597 @NonNull 598 MarshaledResponse handle(@NonNull Request request, 599 @NonNull Cors cors, 600 @NonNull CorsResponse corsResponse, 601 @NonNull MarshaledResponse marshaledResponse); 602 } 603 604 /** 605 * Function used to support a pluggable "post-process" hook for any final customization or processing before data goes over the wire. 606 */ 607 @FunctionalInterface 608 public interface PostProcessor { 609 /** 610 * Applies an optional "post-process" hook for any final customization or processing before data goes over the wire. 611 * 612 * @param marshaledResponse the response data generated by the appropriate handler function, but not yet sent over the wire 613 * @return the response data (possibly customized) to be sent over the wire 614 */ 615 @NonNull 616 MarshaledResponse postProcess(@NonNull MarshaledResponse marshaledResponse); 617 } 618 619 @NonNull 620 Charset charset; 621 @Nullable 622 ResourceMethodHandler resourceMethodHandler; 623 @Nullable 624 NotFoundHandler notFoundHandler; 625 @Nullable 626 MethodNotAllowedHandler methodNotAllowedHandler; 627 @Nullable 628 ContentTooLargeHandler contentTooLargeHandler; 629 @Nullable 630 ServiceUnavailableHandler serviceUnavailableHandler; 631 @Nullable 632 OptionsHandler optionsHandler; 633 @Nullable 634 OptionsSplatHandler optionsSplatHandler; 635 @Nullable 636 ThrowableHandler throwableHandler; 637 @Nullable 638 HeadHandler headHandler; 639 @Nullable 640 CorsPreflightAllowedHandler corsPreflightAllowedHandler; 641 @Nullable 642 CorsPreflightRejectedHandler corsPreflightRejectedHandler; 643 @Nullable 644 CorsAllowedHandler corsAllowedHandler; 645 @Nullable 646 PostProcessor postProcessor; 647 648 private Builder(@NonNull Charset charset) { 649 requireNonNull(charset); 650 this.charset = charset; 651 } 652 653 @NonNull 654 public Builder charset(@NonNull Charset charset) { 655 requireNonNull(charset); 656 this.charset = charset; 657 return this; 658 } 659 660 @NonNull 661 public Builder resourceMethodHandler(@Nullable ResourceMethodHandler resourceMethodHandler) { 662 this.resourceMethodHandler = resourceMethodHandler; 663 return this; 664 } 665 666 @NonNull 667 public Builder notFoundHandler(@Nullable NotFoundHandler notFoundHandler) { 668 this.notFoundHandler = notFoundHandler; 669 return this; 670 } 671 672 @NonNull 673 public Builder methodNotAllowedHandler(@Nullable MethodNotAllowedHandler methodNotAllowedHandler) { 674 this.methodNotAllowedHandler = methodNotAllowedHandler; 675 return this; 676 } 677 678 @NonNull 679 public Builder contentTooLargeHandler(@Nullable ContentTooLargeHandler contentTooLargeHandler) { 680 this.contentTooLargeHandler = contentTooLargeHandler; 681 return this; 682 } 683 684 @NonNull 685 public Builder serviceUnavailableHandler(@Nullable ServiceUnavailableHandler serviceUnavailableHandler) { 686 this.serviceUnavailableHandler = serviceUnavailableHandler; 687 return this; 688 } 689 690 @NonNull 691 public Builder optionsHandler(@Nullable OptionsHandler optionsHandler) { 692 this.optionsHandler = optionsHandler; 693 return this; 694 } 695 696 @NonNull 697 public Builder optionsSplatHandler(@Nullable OptionsSplatHandler optionsSplatHandler) { 698 this.optionsSplatHandler = optionsSplatHandler; 699 return this; 700 } 701 702 @NonNull 703 public Builder throwableHandler(@Nullable ThrowableHandler throwableHandler) { 704 this.throwableHandler = throwableHandler; 705 return this; 706 } 707 708 @NonNull 709 public Builder headHandler(@Nullable HeadHandler headHandler) { 710 this.headHandler = headHandler; 711 return this; 712 } 713 714 @NonNull 715 public Builder corsPreflightAllowedHandler(@Nullable CorsPreflightAllowedHandler corsPreflightAllowedHandler) { 716 this.corsPreflightAllowedHandler = corsPreflightAllowedHandler; 717 return this; 718 } 719 720 @NonNull 721 public Builder corsPreflightRejectedHandler(@Nullable CorsPreflightRejectedHandler corsPreflightRejectedHandler) { 722 this.corsPreflightRejectedHandler = corsPreflightRejectedHandler; 723 return this; 724 } 725 726 @NonNull 727 public Builder corsAllowedHandler(@Nullable CorsAllowedHandler corsAllowedHandler) { 728 this.corsAllowedHandler = corsAllowedHandler; 729 return this; 730 } 731 732 @NonNull 733 public Builder postProcessor(@Nullable PostProcessor postProcessor) { 734 this.postProcessor = postProcessor; 735 return this; 736 } 737 738 @NonNull 739 public ResponseMarshaler build() { 740 return new DefaultResponseMarshaler(this); 741 } 742 } 743}