Interface ResponseMarshaler


public interface ResponseMarshaler
Prepares responses for each request scenario Soklet supports (Resource Method, exception, CORS preflight, etc.)

The MarshaledResponse value returned from these methods is what is ultimately sent back to clients as bytes over the wire.

A standard threadsafe implementation builder can be acquired via withDefaults() or withCharset(Charset) builder factory methods. This builder allows you to specify, for example, how to turn a Resource Method response object into a wire format (e.g. JSON) and is generally what you want.

A standard threadsafe implementation can be acquired via the defaultInstance() factory method. This is generally not needed unless your implementation requires dynamic "fall back to default" behavior that is not otherwise accessible.

Example implementation using withDefaults():

// Let's use Gson to write response body data
// See https://github.com/google/gson
final Gson GSON = new Gson();

// The request was matched to a Resource Method and executed non-exceptionally
ResourceMethodHandler resourceMethodHandler = (
  @Nonnull Request request,
  @Nonnull Response response,
  @Nonnull ResourceMethod resourceMethod
) -> {
  // Turn response body into JSON bytes with Gson
  Object bodyObject = response.getBody().orElse(null);
  byte[] body = bodyObject == null
    ? null
    : GSON.toJson(bodyObject).getBytes(StandardCharsets.UTF_8);

  // To be a good citizen, set the Content-Type header
  Map<String, Set<String>> headers = new HashMap<>(response.getHeaders());
  headers.put("Content-Type", Set.of("application/json;charset=UTF-8"));

  // Tell Soklet: "OK - here is the final response data to send"
  return MarshaledResponse.withResponse(response)
    .headers(headers)
    .body(body)
    .build();
};

// Function to create responses for exceptions that bubble out
ThrowableHandler throwableHandler = (
  @Nonnull Request request,
  @Nonnull Throwable throwable,
  @Nullable ResourceMethod resourceMethod
) -> {
  // Keep track of what to write to the response
  String message;
  int statusCode;

  // Examine the exception that bubbled out and determine what
  // the HTTP status and a user-facing message should be.
  // Note: real systems should localize these messages
  switch (throwable) {
    // Soklet throws this exception, a specific subclass of BadRequestException
    case IllegalQueryParameterException e -> {
      message = String.format("Illegal value '%s' for parameter '%s'",
        e.getQueryParameterValue().orElse("[not provided]"),
        e.getQueryParameterName());
      statusCode = 400;
    }

    // Generically handle other BadRequestExceptions
    case BadRequestException ignored -> {
      message = "Your request was improperly formatted.";
      statusCode = 400;
    }

    // Something else?  Fall back to a 500
    default -> {
      message = "An unexpected error occurred.";
      statusCode = 500;
    }
  }

  // Turn response body into JSON bytes with Gson.
  // Note: real systems should expose richer error constructs
  // than an object with a single message field
  byte[] body = GSON.toJson(Map.of("message", message))
    .getBytes(StandardCharsets.UTF_8);

  // Specify our headers
  Map<String, Set<String>> headers = new HashMap<>();
  headers.put("Content-Type", Set.of("application/json;charset=UTF-8"));

  return MarshaledResponse.withStatusCode(statusCode)
    .headers(headers)
    .body(body)
    .build();
};

// Supply our custom handlers to the standard response marshaler
SokletConfig config = SokletConfig.withServer(
  Server.withPort(8080).build()
).responseMarshaler(ResponseMarshaler.withDefaults()
  .resourceMethodHandler(resourceMethodHandler)
  .throwableHandler(throwableHandler)
  .build()
).build();

Full documentation is available at https://www.soklet.com/docs/response-writing.

Author:
Mark Allen