Blog Zenika

#CodeTheWorld

Java

Some notes about Jax-RS, HTTP statuses and Tomcat

A quick note about an issue my team and I struggled with last day. The context is the following: using Jersey client, we had to check a HTTP response status before moving on with our process. The check was done this way:
 

if (Status.OK == response.getStatusInfo()) {
    // ...
}

Jersey being a Jax-RS implementation, it is not chocking that the Jersey client also rely on Jax-RS. It is the case in this piece of code where Status is an enum defined by Jax-RS. But we had an issue with this code: on some environment it happened that our condition was false whereas the HTTP response status was actually 200 (meaning OK).
Truth is, the actual return type of getStatusInfo() is not Status but its intertace StatusType which is also defined by Jax-RS. StatusType define methods allowing to access to the content of a HTTP response status line: the status code (200, 400…) and the reason phrase which provide a short textual description of the status code (OK, BAD REQUEST…).

> GET /version HTTP/1.1
> Accept: */*
< HTTP/1.1 200 OK
< Server: Apache-Coyote/1.1
< Content-Type: text/plain
< Date: Wed, 17 May 2017 08:55:40 GMT

So, why using an interface here and not directly the enum ? First of all, because the Jax-RS Status enum is limited to standard response codes. The second reason is related to the reason phrase: all the classical reason phrases we know of (FORBIDDEN, BAD REQUEST…) are actually NOT mandatory to be used. As the RFC 2616 says:

The reason phrases listed here are only recommendations — they MAY be replaced by local equivalents without affecting the protocol

So, the following response is then perfectly valid (but not advised !):
< HTTP/1.1 200 SURE, WHY NOT!
< Server: Apache-Coyote/1.1
< Content-Type: text/plain
< Date: Wed, 17 May 2017 08:55:40 GMT
So, what is the exact behavior of the Jersey client regarding status lines ? The HttpUrlConnector give us the answer:
final Response.StatusType status =
        reasonPhrase == null ? Statuses.from(code) : Statuses.from(code, reasonPhrase);
public static StatusType from(int code) {
    StatusType result = Response.Status.fromStatusCode(code);
    return (result != null) ? result : new StatusImpl(code, "");
}
public static StatusType from(int code, String reason) {
    return new StatusImpl(code, reason);
}
We can see from this code that Jersey will use a Status enum value as a response to get getStatusInfo() call if and only if the reason phrase is not defined and the status code is standard (i.e. the status code is defined in the enum). Otherwise the response will be an instance of StatusImpl (a Jersey class of StatusType).
So, the truth is we can’t rely on a reference check using Status enum values in our conditions. The only safe solution is to use the status code integer value directly:
if (OK.getStatusCode() == response.getStatusInfo().getStatusCode()) {
    // ...
}

Another solution, less verbose but more risky, is to rely on the equals implementation of the status info that is returned:

if (response.getStatusInfo().equals(OK)) {
    // ...
}

This solution is not safe because you cannot really guarantee that the reason phrase returned by your server will match the one defined in the enum. Also, the order of the expression matters because calling equals on an enum will actually compare references. This means that the following code will not work even if the reason phrase returned by the server is the same than the one contained in the enum:

if (OK.equals(response.getStatusInfo()) {
    // ...
}

There is still a question to answer: why did this code failed on some of our environment ? Well, it happened that our local environment was using tomcat 8.5.x whereas other environments were using tomcat 7.x. And guess what: starting from 8.5, tomcat does not send reason phrases anymore by default.

Auteur/Autrice

Laisser un commentaire

Ce site utilise Akismet pour réduire les indésirables. En savoir plus sur comment les données de vos commentaires sont utilisées.