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