Skip to content

Commit

Permalink
HTTPCLIENT-2277: Implement must-understand directive according to RFC…
Browse files Browse the repository at this point in the history
… 9111 (#461)

This commit introduces changes to handle the 'must-understand' directive in accordance with the updated HTTP cache-related RFC 9111. The logic ensures that the cache only stores responses with status codes it understands when the 'must-understand' directive is present.

This implementation adheres to the following RFC guidance:

- A cache that understands and conforms to the requirements of a response's status code may cache it when the 'must-understand' directive is present.
- If the 'no-store' directive is present along with the 'must-understand' directive, the cache can ignore the 'no-store' directive if it understands the status code's caching requirements.
  • Loading branch information
arturobernalg authored Jun 29, 2023
1 parent 7df675e commit a77a926
Show file tree
Hide file tree
Showing 4 changed files with 60 additions and 3 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -164,6 +164,7 @@ public class HeaderConstants {
public static final String CACHE_CONTROL_STALE_IF_ERROR = "stale-if-error";
public static final String CACHE_CONTROL_STALE_WHILE_REVALIDATE = "stale-while-revalidate";
public static final String CACHE_CONTROL_ONLY_IF_CACHED = "only-if-cached";
public static final String CACHE_CONTROL_MUST_UNDERSTAND = "must-understand";
/**
* @deprecated Use {@link #CACHE_CONTROL_STALE_IF_ERROR}
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -196,6 +196,8 @@ public final ResponseCacheControl parseResponse(final Iterator<Header> headerIte
builder.setStaleWhileRevalidate(parseSeconds(name, value));
} else if (name.equalsIgnoreCase(HeaderConstants.CACHE_CONTROL_STALE_IF_ERROR)) {
builder.setStaleIfError(parseSeconds(name, value));
} else if (name.equalsIgnoreCase(HeaderConstants.CACHE_CONTROL_MUST_UNDERSTAND)) {
builder.setMustUnderstand(true);
}
});
return builder.build();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,11 @@ final class ResponseCacheControl implements CacheControl {
* Indicates whether the Cache-Control header includes the "public" directive.
*/
private final boolean cachePublic;
/**
* Indicates whether the Cache-Control header includes the "must-understand" directive.
*/
private final boolean mustUnderstand;

/**
* The number of seconds that a stale response is considered fresh for the purpose
* of serving a response while a revalidation request is made to the origin server.
Expand Down Expand Up @@ -115,11 +120,12 @@ final class ResponseCacheControl implements CacheControl {
* @param staleWhileRevalidate The stale-while-revalidate value from the Cache-Control header.
* @param staleIfError The stale-if-error value from the Cache-Control header.
* @param noCacheFields The set of field names specified in the "no-cache" directive of the Cache-Control header.
* @param mustUnderstand The must-understand value from the Cache-Control header.
*/
ResponseCacheControl(final long maxAge, final long sharedMaxAge, final boolean mustRevalidate, final boolean noCache,
final boolean noStore, final boolean cachePrivate, final boolean proxyRevalidate,
final boolean cachePublic, final long staleWhileRevalidate, final long staleIfError,
final Set<String> noCacheFields) {
final Set<String> noCacheFields, final boolean mustUnderstand) {
this.maxAge = maxAge;
this.sharedMaxAge = sharedMaxAge;
this.noCache = noCache;
Expand All @@ -141,6 +147,7 @@ final class ResponseCacheControl implements CacheControl {
!cachePublic &&
staleWhileRevalidate == -1
&& staleIfError == -1;
this.mustUnderstand = mustUnderstand;
}

/**
Expand Down Expand Up @@ -191,6 +198,15 @@ public boolean isCachePrivate() {
return cachePrivate;
}

/**
* Returns the must-understand directive from the Cache-Control header.
*
* @return The must-understand directive.
*/
public boolean isMustUnderstand() {
return mustUnderstand;
}

/**
* Returns whether the must-revalidate directive is present in the Cache-Control header.
*
Expand Down Expand Up @@ -265,6 +281,7 @@ public String toString() {
", staleWhileRevalidate=" + staleWhileRevalidate +
", staleIfError=" + staleIfError +
", noCacheFields=" + noCacheFields +
", mustUnderstand=" + mustUnderstand +
'}';
}

Expand All @@ -285,6 +302,7 @@ static class Builder {
private long staleWhileRevalidate = -1;
private long staleIfError = -1;
private Set<String> noCacheFields;
private boolean mustUnderstand;

Builder() {
}
Expand Down Expand Up @@ -388,9 +406,18 @@ public Builder setNoCacheFields(final Set<String> noCacheFields) {
return this;
}

public boolean isMustUnderstand() {
return mustUnderstand;
}

public Builder setMustUnderstand(final boolean mustUnderstand) {
this.mustUnderstand = mustUnderstand;
return this;
}

public ResponseCacheControl build() {
return new ResponseCacheControl(maxAge, sharedMaxAge, mustRevalidate, noCache, noStore, cachePrivate, proxyRevalidate,
cachePublic, staleWhileRevalidate, staleIfError, noCacheFields);
cachePublic, staleWhileRevalidate, staleIfError, noCacheFields, mustUnderstand);
}

}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -307,11 +307,19 @@ public boolean isResponseCacheable(final ResponseCacheControl cacheControl, fina
}
return false;
}
if (cacheControl.isNoStore()) {

if (cacheControl.isMustUnderstand() && cacheControl.isNoStore() && !understoodStatusCode(response.getCode())) {
// must-understand cache directive overrides no-store
LOG.debug("Response contains a status code that the cache does not understand, so it's not cacheable");
return false;
}

if (!cacheControl.isMustUnderstand() && cacheControl.isNoStore()) {
LOG.debug("Response is explicitly non-cacheable per cache control directive");
return false;
}


if (request.getRequestUri().contains("?")) {
if (neverCache1_0ResponsesWithQueryString && from1_0Origin(response)) {
LOG.debug("Response is not cacheable as it had a query string");
Expand Down Expand Up @@ -494,4 +502,23 @@ boolean responseContainsNoCacheDirective(final ResponseCacheControl responseCach
return false;
}

/**
* This method checks if a given HTTP status code is understood according to RFC 7231.
* Understood status codes include:
* - All 2xx (Successful) status codes (200-299)
* - All 3xx (Redirection) status codes (300-399)
* - All 4xx (Client Error) status codes up to 417 and 421
* - All 5xx (Server Error) status codes up to 505
*
* @param status The HTTP status code to be checked.
* @return true if the HTTP status code is understood, false otherwise.
*/
private boolean understoodStatusCode(final int status) {
return (status >= 200 && status <= 206) ||
(status >= 300 && status <= 399) ||
(status >= 400 && status <= 417) ||
(status == 421) ||
(status >= 500 && status <= 505);
}

}

0 comments on commit a77a926

Please sign in to comment.