Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Spring MVC response Headers: ETag has double quotes for GET request but not for PUT request

We upgraded our Spring MVC from 4.0 to 4.3 in our service. It caused ETag format change in the response headers for "GET" method. Clients making "GET" calls will get ETags with double quotes in the response headers. Previously ETag in the response headers had no double quotes for "GET" method.

For example:

Now: etag →"TVMzTWFpbmxpbmVEZXZvLTI5ODIxMQ" 
Previously: etag →TVMzTWFpbmxpbmVEZXZvLTI5ODIxMQ

The response of "PUT" request does not have double quotes around ETag in Headers, just like before.

Anyone has any ideas why?

like image 930
Tony Avatar asked Oct 17 '22 03:10

Tony


1 Answers

Prior to Spring 4.2.x, there was no management for the ETag header. It has been introduced since then in the HttpEntityMethodProcessor. The class has evolved during time and the management of the ETag header respect the RFC (or is close enough).

As you can see in this commit, Spring team fixed an issue with its management:

Fix missing ETag/LastModified headers in responses

Prior to this commit, the HttpEntityMethodProcessor would avoid writing ETag/Last-Modified response headers before calling ServletWebRequest to process conditional requests. This was done to avoid duplicate response header values due to headers being already written to the underlying servlet response.

This is still necessary for GET/HEAD requests, since this is properly handled by ServletWebRequest for those cases. But HttpEntityMethodProcessor should not make that decision for PUT/PATCH/POST responses since developers are adding response headers on purpose and should be in control of the situation — whereas ServletWebRequest does not write those headers in those cases.

The relevant part of modified code is here.

So basically, when you are manually adding an ETag header, in case of a 200 status of a GET or HEAD method, the framework removes it, then recreates it. That is why with a PUT, there are no double quotes.

In HttpEntityMethodProcessor:

    if (inputMessage.getMethod() == HttpMethod.GET || inputMessage.getMethod() == HttpMethod.HEAD) {
        responseHeaders.remove(HttpHeaders.ETAG);
        responseHeaders.remove(HttpHeaders.LAST_MODIFIED);
    }

Then in ServletWebRequest:

private String padEtagIfNecessary(String etag) {
    if (!StringUtils.hasLength(etag)) {
        return etag;
    }
    if ((etag.startsWith("\"") || etag.startsWith("W/\"")) && etag.endsWith("\"")) {
        return etag;
    }
    return "\"" + etag + "\"";
}

As you can see, this respects the chapter 2.4 of the RFC:

2.4. When to Use Entity-Tags and Last-Modified Dates

In 200 (OK) responses to GET or HEAD, an origin server:

o SHOULD send an entity-tag validator unless it is not feasible to generate one.

o MAY send a weak entity-tag instead of a strong entity-tag, if performance considerations support the use of weak entity-tags, or if it is unfeasible to send a strong entity-tag.

o SHOULD send a Last-Modified value if it is feasible to send one.

In other words, the preferred behavior for an origin server is to send both a strong entity-tag and a Last-Modified value in successful responses to a retrieval request.

But I found that it is not backward compatible and breaks what developer could have used prior to these versions, without the possibility to skip/override what they did.

Here is the description of the ETag from the MDN (more clearer).

Hope it helped in comprehension.

like image 167
Naoj Avatar answered Oct 20 '22 23:10

Naoj