Skip to content

Latest commit

 

History

History
150 lines (118 loc) · 4.67 KB

responses.md

File metadata and controls

150 lines (118 loc) · 4.67 KB

Responses

Return values

What a handler returns becomes the body of the response:

Return type Response
String the string, encoded in UTF-8
byte[] the bytes
InputStream the contents of the stream, which is closed afterwards
void, or null an empty body
Response nothing more: the handler has already set the status and written the body
any other type converted by an output formatter, e.g. to JSON

The status is 200 unless the handler set another one.

Flak does not guess the content type of a String, byte[] or InputStream. Set it when the client needs it:

@Route("/report.csv")
public String report(Response r) {
  r.addHeader("Content-Type", "text/csv");
  return buildCsv();
}

An InputStream suits large content: it is copied to the client as it is read, and never held in memory as a whole. If reading it fails midway, the connection is aborted, so the client sees an error rather than a truncated document that looks complete.

Output formatters

A handler can return any object, provided a formatter converts it. JSON is the common case, and flak-jackson needs no more than @JSON. Other formats are registered under a name, which handlers refer to with @OutputFormat:

app.addOutputFormatter("CSV", (data, resp) -> {
  resp.addHeader("Content-Type", "text/csv");
  Csv.write((List<?>) data, resp.getOutputStream());
});

@Route("/api/items.csv")
@OutputFormat("CSV")
public List<Item> items() { ... }

Register formatters before scanning the handlers that use them. A handler returning an unknown type without a formatter makes scan() fail with "No @OutputFormat or @JSON around method …".

Status and headers

Take a Response parameter, or call app.getResponse():

@Route("/items")
@Post
public String create(Form form, Response r) {
  Item item = save(form);
  r.setStatus(201);
  r.addHeader("Location", app.absolutePath("/items/" + item.getId()));
  return item.getId();
}
  • addHeader() adds a header, and can be called several times for the same name (e.g. Set-Cookie). hasResponseHeader(name) tells whether one is set.
  • setStatus() sets the status, and isStatusSet() tells whether one has been set.

The status and the headers go out with the first bytes of the body. Set them before writing a large body or flushing the output stream. After that point they can no longer change.

To reply with an error status and a message, throw an HttpException (see Errors and hooks).

Redirects

@Route("/old")
public void old(Response r) {
  r.redirect("/new");
}

This answers 302 with a Location header. A path is relative to the app: in an app at /shop, redirect("/new") sends the client to /shop/new. A URL, such as https://example.com/ or //cdn.example.com/, is used as is.

Writing the body yourself

A handler can write into response.getOutputStream() instead of returning a value. Return void, or return the Response if you prefer to make it explicit. There is no need to close the stream: Flak finishes the response when the handler returns.

Calling flush() sends what has been written so far, as a chunk. This keeps the connection open for as long as the handler runs, so the handler can stream progress, logs or events:

@Route("/events")
public void events(Response r) throws Exception {
  r.addHeader("Content-Type", "text/event-stream");
  r.addHeader("Cache-Control", "no-cache");
  OutputStream out = r.getOutputStream();
  while (running) {
    out.write(("data: " + nextEvent() + "\n\n").getBytes(StandardCharsets.UTF_8));
    out.flush();
  }
}

Each open stream holds a thread for as long as it lasts, so size the executor accordingly. Once the client is gone, write() or flush() throws an IOException, though not necessarily the first one after it left.

For Server-Sent Events, flak-util encodes the events, sends them to every connected client and drops the clients that went away: see Server-Sent Events.

HEAD

A GET handler does not answer HEAD requests. A path that should answer them needs a handler with @Head, which sets the status and headers. The body is not sent anyway:

@Route("/files/*path")
@Head
public Response exists(String path, Response r) {
  r.setStatus(Files.exists(root.resolve(path)) ? 200 : 404);
  return r;
}

Replying later

Neither backend supports asynchronous responses. The response is complete as soon as the handler returns, so a handler cannot hand it over to another thread and reply from there. A handler that needs to wait for something must block until it is available.