Skip to content

RFC: RFC web respond

Paulo Lopes edited this page Apr 2, 2021 · 4 revisions

Vert.x Web has a new experimental api RoutingContext#respond(). This Method is a helper for chaining vert.x Future or future like results to a web handler. Unlike other methods, this specific method is very opinionated as it attempts to address common use patterns and reduce boilerplate code.

Current rules (4.0.x)

  1. When the result of the Future is null the status code is 204 and no content is returned.
  2. When the result of the Future is an instanceof Buffer then the header Content-Type is set to application/octet-stream and status code is 200.
  3. When the result of the Future is an instanceof String then the header Content-Type is set to text/html and status code is 200.
  4. Otherwise, the result of the Future is passes to RoutingContext#json() which will add the content type application/json with status code 200.

RFC

201 Status code

It is a common best practice to return 201 as status code for successful POST/PUT/PATCH responses. This means that the HTTP verb should be taken into consideration when dealing with successful responses.

The question we need to still address is the case of null. Should we override the default value of 204 no content (for these HTTP verbs too?)

https://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.30

Also states: The Location response-header field is used to redirect the recipient to a location other than the Request-URI for completion of the request or identification of a new resource. For 201 (Created) responses, the Location is that of the new resource which was created by the request.

The question to address right now is, should we also add the Location header? or use the presence of Location header to assume that the user wants 201 as status code?

Codec support

Any non String/Buffer result will be assumed to be JSON, we should try to reuse the codec functionality used in vertx-web-client to perform custom content encoding.

The current implementation assumes that all non Buffer or String data is a JSON. A hypothetical API would be:

ctx.respond(Codec.XML, ctx -> db.findProductById(ctx.get("productId")))

In this case, the codec would be responsible to provide the header Content-Type value as well as perform the encoding from Object to Buffer. In this case, the previous rules would still apply however the default content type header values would always be replaced by the codec one.

Streaming support

ReadStream is not supported ATM, we could easily allow it thanks to a Pipe. The main problem to address would be how to handle content encoding.

  1. We either assume that streams are opaque and just pipe without setting any specific content-type
  2. We need to build on top of the previous item (CODECs)

Future composition

A less intrusive alternative would be to recommend user to use Future.compose() to address any deviations from the default behavior, for example, a user wishing to have the custom 201 status code would:

ctx.respond(ctx -> 
  db
    .findProductById(ctx.get("productId"))
    .compose(prod -> {
      ctx.response().setStatusCode(201);
      return Future.succeededFuture(prod);
    }));

As suggested by @tsegismont a better approach is to use the onSuccess handler which is executed before the future is returned:

ctx.respond(ctx -> 
  db
    .findProductById(ctx.get("productId"))
    .onSuccess(prod -> ctx.response().setStatusCode(201)));

This approach also ensures that the end user doesn't need to remember to return the future again.

The same can be said about codec support:

ctx.respond(ctx -> 
  db
    .findProductById(ctx.get("productId"))
    .map(this::convertToXML));

// ...

public <T> Future<Buffer> convertToXML(T source) {
  // ... conversion
  return Future.succeededFuture(buffer);
}
Clone this wiki locally