In a previous post we saw how to validate the security with JAX-RS. It was mainly based on a request filter. Do you know the response has the same kind of control with JAX-RS >= 2?

To illustrate it we'll enrich our response with two headers:

  • the cluster name we belong to
  • the node name which responded

This kind of enrichment is generally avoided on exposed to the world API but is quite common for internal API to let teams interact way faster when there is an issue. This will typically allow to identify a corrupted machine or failing node really quick.

Create a response filter

To be able to enrich the response we can use a servlet filter of course, but it is interesting to replace it with a JAX-RS ContainerResponseFilter because we have access to the JAX-RS model then. Typically:

  • entity metadata (annotations etc)
  • entity stream (it is pretty hard to do it with a filter since JAX-RS runtime will flush this stream before exiting and therefore before your "post" execution in a servlet filter)
  • already parsed headers, locale, links, status...

Our implementation will just get the node value from the JVM (InetAddress) and the cluster from a system property:

public class ResponseHeaderEnricherProvider {
    private String node;
    private String cluster;

    @PostConstruct
    private void init() {
        cluster = System.getProperty("app.cluster", "default");
        try {
            node = InetAddress.getLocalHost().getHostName();
        } catch (final UnknownHostException e) {
            throw new IllegalStateException("Machine not well configured, ensure localhost name is set");
        }
    }

    // ...
}

Then to integrate it to JAX-RS runtime we create a @Provider and add these values as headers at response filtering time:

@Provider
@Dependent
public class ResponseHeaderEnricherProvider implements ContainerResponseFilter {
    // previous code to get node/cluster values

    public void filter(final ContainerRequestContext requestContext, final ContainerResponseContext responseContext) throws IOException {
        responseContext.getHeaders().put("Node", singletonList(node));
        responseContext.getHeaders().put("Cluster", singletonList(cluster));
    }
}

And here it is, if you curl a test endpoint you'll see we get our additional headers:

$ curl -v http://localhost:8080/test
*   Trying 127.0.0.1...
* Connected to localhost (127.0.0.1) port 8080 (#0)
> GET /test HTTP/1.1
> Host: localhost:8080
> User-Agent: curl/7.47.0
> Accept: */*
>
< HTTP/1.1 200
< Date: Sun, 07 May 2017 15:28:35 GMT
< Node: rmannibucau
< Cluster: default
< Content-Type: text/plain
< Content-Length: 4
<
* Connection #0 to host localhost left intact
test

Real life applications

As mentionned for internal exosure of some deployment information it is very useful but for external API it is useful too, you can expose:

  • some rate limiting
  • some account state information (like "you are locked but try again tomorrow")
  • some service state ("we are under an attack, please check status.company.com)
  • some timing information regarding the server execution
  • some transaction id
  • ...

Overall idea is to expose anything the client can be interested in, which is not dangerous in term of security, and is not directly linked to the business. Said otherwise, anything transversal is a good candidate to this kind of implementation. You will often mix it with a service injected holding some state (like a @RequestScoped contextual data holder) to complete the full code structure.

 

From the same author:

In the same category: