HTTP Caching and Conditional Requests
The performance of web sites and applications can be significantly improved by reusing previously fetched resources. Web caches reduce latency and network traffic and thus lessen the time needed to display a representation of a resource. By making use of HTTP caching, Web sites become more responsive.
1. Different kinds of caches
Caching is a technique that stores a copy of a given resource and serves it back when requested. When a web cache has a requested resource in its store, it intercepts the request and returns its copy instead of re-downloading from the originating server. This achieves several goals: it eases the load of the server that doesn’t need to serve all clients itself, and it improves performance by being closer to the client, i.e., it takes less time to transmit the resource back. For a web site, it is a major component in achieving high performance. On the other side, it has to be configured properly as not all resources stay identical forever: it is important to cache a resource only until it changes, not longer.
There are several kinds of caches: these can be grouped into two main categories: private or shared caches. A shared cache is a cache that stores responses for reuse by more than one user. A private cache is dedicated to a single user. This page will mostly talk about browser and proxy caches, but there are also gateway caches, CDN, reverse proxy caches and load balancers that are deployed on web servers for better reliability, performance and scaling of web sites and web applications.
1.1. Private browser caches
A private cache is dedicated to a single user. You might have seen "caching" in your browser’s settings already. A browser cache holds all documents downloaded via HTTP by the user. This cache is used to make visited documents available for back/forward navigation, saving, viewing-as-source, etc. without requiring an additional trip to the server. It likewise improves offline browsing of cached content.
1.2. Shared proxy caches
A shared cache is a cache that stores responses to be reused by more than one user. For example, an ISP or your company might have set up a web proxy as part of its local network infrastructure to serve many users so that popular resources are reused a number of times, reducing network traffic and latency.
2. Targets of caching operations
HTTP caching is optional but usually desirable. HTTP caches are typically limited to caching responses to GET; they may decline other methods. The primary cache key consists of the request method and target URI (often only the URI is used — this is because only GET requests are caching targets).
Common forms of caching entries are:
-
Successful results of a retrieval request: a 200 (OK) response to a GET request containing a resource like HTML documents, images or files.
-
Permanent redirects: a 301 (Moved Permanently) response.
-
Error responses: a 404 (Not Found) result page.
-
Incomplete results: a 206 (Partial Content) response.
-
Responses other than GET if something suitable for use as a cache key is defined.
A cache entry might also consist of multiple stored responses differentiated by a secondary key, if the request is target of content negotiation (Vary).
3. Controlling caching
3.1. The Cache-Control header
The Cache-Control HTTP/1.1 general-header field is used to specify directives for caching mechanisms in both requests and responses. Use this header to define your caching policies with the variety of directives it provides.
No caching
The cache should not store anything about the client request or server response. A request is sent to the server and a full response is downloaded each and every time.
Cache-Control: no-store
Cache but revalidate
A cache will send the request to the origin server for validation before releasing a cached copy.
Cache-Control: no-cache
Private and public caches
The "public" directive indicates that the response may be cached by any cache. This can be useful if pages with HTTP authentication, or response status codes that aren’t normally cacheable, should now be cached.
On the other hand, "private" indicates that the response is intended for a single user only and must not be stored by a shared cache. A private browser cache may store the response in this case.
Cache-Control: private
Cache-Control: public
Expiration
The most important directive here is max-age=<seconds>
, which is the maximum amount of time in which a resource will be considered fresh. This directive is relative to the time of the request, and overrides the Expires
header (if set). For the files in the application that will not change, you can normally use aggressive caching. This includes static files such as images, CSS files, and JavaScript files, for example.
Cache-Control: max-age=31536000
Validation
When using the "must-revalidate" directive, the cache must verify the status of the stale resources before using it and expired ones should not be used.
Cache-Control: must-revalidate
3.2. The Pragma header
Pragma is an HTTP/1.0 header. Pragma: no-cache
is like Cache-Control: no-cache
in that it forces caches to submit the request to the origin server for validation, before releasing a cached copy. However, Pragma
is not specified for HTTP responses and is therefore not a reliable replacement for the general HTTP/1.1 Cache-Control
header.
Pragma
should only be used for backwards compatibility with HTTP/1.0 caches where the Cache-Control
HTTP/1.1 header is not yet present.
4. Freshness
Once a resource is stored in a cache, it could theoretically be served by the cache forever. Caches have finite storage so items are periodically removed from storage. This process is called cache eviction. On the other side, some resources may change on the server so the cache should be updated. As HTTP is a client-server protocol, servers can’t contact caches and clients when a resource changes; they have to communicate an expiration time for the resource. Before this expiration time, the resource is fresh; after the expiration time, the resource is stale. Eviction algorithms often privilege fresh resources over stale resources. Note that a stale resource is not evicted or ignored; when the cache receives a request for a stale resource, it forwards this request with a If-None-Match to check if it is in fact still fresh. If so, the server returns a 304 (Not Modified) header without sending the body of the requested resource, saving some bandwidth.
Here is an example of this process with a shared cache proxy:
The freshness lifetime is calculated based on several headers. If a “Cache-Control: max-age=N” header is specified, then the freshness lifetime is equal to N
. If this header is not present, which is very often the case, it is checked if an Expires header is present. If an Expires header exists, then its value minus the value of the Date header determines the freshness lifetime.
4.1. Heuristic freshness checking
If an origin server does not explicitly specify freshness (e.g. using Cache-Control or Expires header) then a heuristic approach may be used.
HTTP/1.1 200 OK
Content-Type: text/html
Content-Length: 1024
Date: Tue, 22 Feb 2022 22:22:22 GMT
Last-Modified: Tue, 22 Feb 2021 22:22:22 GMT
In this case look for a Last-Modified header. If this header is present, then the cache’s freshness lifetime is equal to the value of the Date header minus the value of the Last-modified header divided by 10
. The expiration time is computed as follows:
expirationTime = responseTime + freshnessLifetime - currentAge
where responseTime
is the time at which the response was received according to the browser. For more information see RFC 7234: Hypertext Transfer Protocol (HTTP/1.1): 4.2.2. Calculating Heuristic Freshness.
4.2. Revved resources
The more we use cached resources, the better the responsiveness and the performance of a Web site will be. To optimize this, good practices recommend to set expiration times as far in the future as possible. This is possible on resources that are regularly updated, or often, but is problematic for resources that are rarely and infrequently updated. They are the resources that would benefit the most from caching resources, yet this makes them very difficult to update. This is typical of the technical resources included and linked from each Web pages: JavaScript and CSS files change infrequently, but when they change you want them to be updated quickly.
Web developers invented a technique that Steve Souders called revving. Infrequently updated files are named in a specific way: in their URL, usually in the filename, a revision (or version) number is added. That way each new revision of this resource is considered as a resource on its own that never changes and that can have an expiration time very far in the future, usually one year or even more. In order to have the new versions, all the links to them must be changed, that is the drawback of this method: additional complexity that is usually taken care of by the tool chain used by Web developers. When the infrequently variable resources change they induce an additional change to often variable resources. When these are read, the new versions of the others are also read.
This technique has an additional benefit: updating two cached resources at the same time will not lead to the situation where the out-dated version of one resource is used in combination with the new version of the other one. This is very important when web sites have CSS stylesheets or JS scripts that have mutual dependencies, i.e., they depend on each other because they refer to the same HTML elements.
4.3. Cache validation
When a cached document’s expiration time has been reached, it is either validated or fetched again. Validation can only occur if the server provided either a strong validator or a weak validator.
Revalidation is triggered when the user presses the reload button. It is also triggered under normal browsing if the cached response includes the “Cache-Control: must-revalidate” header.
ETags
The ETag response header is an opaque-to-the-user-agent value that can be used as a strong validator. That means that a HTTP user-agent, such as the browser, does not know what this string represents and can’t predict what its value would be. If the ETag header was part of the response for a resource, the client can issue an If-None-Match in the header of future requests in order to validate the cached resource.
HTTP/1.1 200 OK
Content-Type: text/html
Content-Length: 1024
Date: Tue, 22 Feb 2022 22:22:22 GMT
ETag: "33a64df5"
Cache-Control: max-age=3600
GET /index.html HTTP/1.1
Host: example.com
Accept: text/html
If-None-Match: "33a64df5"
Last-Modified
The Last-Modified response header can be used as a weak validator. It is considered weak because it only has 1-second resolution. If the Last-Modified header is present in a response, then the client can issue an If-Modified-Since request header to validate the cached document.
HTTP/1.1 200 OK
Content-Type: text/html
Content-Length: 1024
Date: Tue, 22 Feb 2022 22:22:22 GMT
Last-Modified: Tue, 22 Feb 2022 22:00:00 GMT
Cache-Control: max-age=3600
When a validation request is made, the server can either ignore the validation request and respond with a normal 200 OK, or it can return 304 Not Modified (with an empty body) to instruct the browser to use its cached copy. The latter response can also include headers that update the expiration time of the cached document.
GET /index.html HTTP/1.1
Host: example.com
Accept: text/html
If-Modified-Since: Tue, 22 Feb 2022 22:00:00 GMT
HTTP/1.1 304 Not Modified
Content-Type: text/html
Date: Tue, 22 Feb 2022 23:22:22 GMT
Last-Modified: Tue, 22 Feb 2022 22:00:00 GMT
Cache-Control: max-age=3600
5. Varying responses
The Vary HTTP response header determines how to match future request headers to decide whether a cached response can be used, or if a fresh one must be requested from the origin server.
When a cache receives a request that has a Vary header field, it must not use a cached response by default unless all header fields specified in the Vary header match in both the original (cached) request and the new request.
This feature is commonly used to allow a resource to be cached in uncompressed and (various) compressed forms, and served appropriately to user agents based on the encodings that they support. For example, a server can set Vary: Accept-Encoding
to ensure that a separate version of a resource is cached for all requests that specify support for a particular set of encodings, e.g. Accept-Encoding: gzip,deflate,sdch
.
Vary: Accept-Encoding
6. HTTP caching control headers
6.1. Cache-Control
The Cache-Control HTTP header holds directives (instructions) for caching in both requests and responses. A given directive in a request does not mean the same directive should be in the response.
6.1.1. Syntax
Caching directives have the following rules to be valid:
-
Case-insensitive, but lowercase is recommended.
-
Multiple directives are comma-separated.
-
Some directives have an optional argument, which can be either a token or a quoted-string. (See spec for definitions)
Cache request directives
Standard Cache-Control
directives that can be used by the client in an HTTP request.
Cache-Control: max-age=<seconds>
Cache-Control: max-stale[=<seconds>]
Cache-Control: min-fresh=<seconds>
Cache-Control: no-cache
Cache-Control: no-store
Cache-Control: no-transform
Cache-Control: only-if-cached
Cache response directives
Standard Cache-Control
directives that can be used by the server in an HTTP response.
Cache-Control: must-revalidate
Cache-Control: no-cache
Cache-Control: no-store
Cache-Control: no-transform
Cache-Control: public
Cache-Control: private
Cache-Control: proxy-revalidate
Cache-Control: max-age=<seconds>
Cache-Control: s-maxage=<seconds>
Extension Cache-Control directives
Extension Cache-Control
directives are not part of the core HTTP caching standards document. Check the compatibility table for their support; user-agents that don’t recognize them should ignore them.
Cache-Control: immutable
Cache-Control: stale-while-revalidate=<seconds>
Cache-Control: stale-if-error=<seconds>
6.1.2. Directives
Cacheability
Directives that define whether a response/request can be cached, where it may be cached, and whether it must be validated with the origin server before caching.
public
The response may be stored by any cache, even if the response is normally non-cacheable.
private
The response may be stored only by a browser’s cache, even if the response is normally non-cacheable. If you mean to not store the response in any cache, use no-store instead. This directive is not effective in preventing caches from storing your response.
no-cache
The response may be stored by any cache, even if the response is normally non-cacheable. However, the stored response MUST always go through validation with the origin server first before using it, therefore, you cannot use no-cache in-conjunction with immutable. If you mean to not store the response in any cache, use no-store
instead. This directive is not effective in preventing caches from storing your response.
no-store
The response may not be stored in any cache. Note that this will not prevent a valid pre-existing cached response being returned. Clients can set max-age=0
to also clear existing cache responses, as this forces the cache to revalidate with the server (no other directives have an effect when used with no-store
).
Expiration
max-age=<seconds>
The maximum amount of time a resource is considered fresh. Unlike Expires
, this directive is relative to the time of the request.
s-maxage=<seconds>
Overrides max-age
or the Expires
header, but only for shared caches (e.g., proxies). Ignored by private caches.
max-stale[=<seconds>]
Indicates the client will accept a stale response. An optional value in seconds indicates the upper limit of staleness the client will accept.
min-fresh=<seconds>
Indicates the client wants a response that will still be fresh for at least the specified number of seconds.
stale-while-revalidate=<seconds>
Indicates the client will accept a stale response, while asynchronously checking in the background for a fresh one. The seconds value indicates how long the client will accept a stale response. Note that the time does not start at the time of the request itself, but, for example, after max-age
has elapsed.
stale-if-error=<seconds>
Indicates the client will accept a stale response if the check for a fresh one fails. The seconds value indicates how long the client will accept the stale response after the initial expiration.
Revalidation and reloading
must-revalidate
Indicates that once a resource becomes stale, caches must not use their stale copy without successful validation on the origin server.
proxy-revalidate
Like must-revalidate
, but only for shared caches (e.g., proxies). Ignored by private caches.
immutable
Indicates that the response body will not change over time. The resource, if unexpired, is unchanged on the server and therefore the client should not send a conditional revalidation for it (e.g. If-None-Match
or If-Modified-Since
) to check for updates, even when the user explicitly refreshes the page. Clients that aren’t aware of this extension must ignore them as per the HTTP specification.
6.1.3. Examples
Preventing caching
A good way to disable caching of a resource, is to send the following response header:
Cache-Control: no-store
Note: The no-store directive will prevent a new resource being cached, but it will not prevent the cache from responding with a non-stale resource that was cached as the result of an earlier request. Setting
On the opposite, this is a bad way to achieve this:
|
Caching static assets
For the files in the application that will not change, you can usually add aggressive caching by sending the response header below. This includes static files that are served by the application such as images, CSS files and JavaScript files, for example. In addition, see also the Expires header.
Cache-Control: public, max-age=604800, immutable
Requiring revalidation
no-cache
and max-age=0
, must-revalidate
have the same meaning. Clients can cache a resource but must revalidate each time before using it. This means HTTP request occurs each time though, it can skip downloading HTTP body if the content is valid.
Cache-Control: no-cache
Cache-Control: max-age=0, must-revalidate
Note: The following header may serve a stale resource, if server is down or loses connectivity.
|
6.2. Expires
The Expires
header contains the date/time
after which the response is considered stale.
Invalid dates, like the value 0, represent a date in the past and mean that the resource is already expired.
If there is a Cache-Control header with the max-age or s-maxage directive in the response, the Expires header is ignored.
|
6.2.1. Syntax
Expires: <http-date>
6.2.2. Directives
<http-date>
An HTTP-date timestamp.
6.2.3. Examples
Expires: Wed, 21 Oct 2015 07:28:00 GMT
6.3. Age
The Age
header contains the time in seconds the object has been in a proxy cache.
The Age
header is usually close to zero. If it is Age: 0
, it was probably just fetched from the origin server; otherwise It is usually calculated as a difference between the proxy’s current date and the Date general header included in the HTTP response.
6.4. Date
The Date
general HTTP header contains the date and time at which the message was originated.
Date: Wed, 21 Oct 2015 07:28:00 GMT
new Date().toUTCString()
// "Mon, 09 Mar 2020 08:13:24 GMT"
6.5. Vary
The Vary
HTTP response header determines how to match future request headers to decide whether a cached response can be used rather than requesting a fresh one from the origin server. It is used by the server to indicate which headers it used when selecting a representation of a resource in a content negotiation algorithm.
The Vary
header should be set on a 304
Not Modified response exactly like it would have been set on an equivalent 200
OK response.
6.5.1. Syntax
Vary: *
Vary: <header-name>, <header-name>, ...
6.5.2. Directives
*
Each request for a URL is supposed to be treated as a unique and uncacheable request. A better way to indicate this is to use Cache-Control: no-store
, which is clearer to read and also signals that the object shouldn’t be stored ever.
<header-name>
A comma-separated list of header names to take into account when deciding whether or not a cached response can be used.
6.5.3. Examples
Dynamic serving
When using the Vary: User-Agent
header, caching servers should consider the user agent when deciding whether to serve the page from cache. For example, if you are serving different content to mobile users, it can help you to avoid that a cache may mistakenly serve a desktop version of your site to your mobile users. It can help Google and other search engines to discover the mobile version of a page, and might also tell them that no Cloaking is intended.
Vary: User-Agent
6.6. Pragma
The Pragma
HTTP/1.0 general header is an implementation-specific header that may have various effects along the request-response chain. It is used for backwards compatibility with HTTP/1.0 caches where the Cache-Control
HTTP/1.1 header is not yet present.
Pragma is not specified for HTTP responses and is therefore not a reliable replacement for the general HTTP/1.1 Cache-Control header, although it does behave the same as Cache-Control: no-cache , if the Cache-Control header field is omitted in a request. Use Pragma only for backwards compatibility with HTTP/1.0 clients.
|
7. HTTP conditional requests (HTTP/1.1)
7.1. Last-Modified
The Last-Modified
response HTTP header contains the date and time at which the origin server believes the resource was last modified. It is used as a validator to determine if a resource received or stored is the same. Less accurate than an ETag header, it is a fallback mechanism. Conditional requests containing If-Modified-Since
or If-Unmodified-Since
headers make use of this field.
$ curl -iI https://blog.codefarm.me/
HTTP/2 200
server: GitHub.com
content-type: text/html; charset=utf-8
last-modified: Wed, 22 Sep 2021 06:10:18 GMT
access-control-allow-origin: *
etag: "614ac8ca-1c64"
expires: Wed, 22 Sep 2021 06:23:12 GMT
cache-control: max-age=600
x-proxy-cache: MISS
x-github-request-id: BBC8:13FF:75A61:CAF87:614AC978
accept-ranges: bytes
date: Wed, 22 Sep 2021 06:15:58 GMT
via: 1.1 varnish
age: 166
x-served-by: cache-hkg17930-HKG
x-cache: HIT
x-cache-hits: 1
x-timer: S1632291359.923473,VS0,VE1
vary: Accept-Encoding
x-fastly-request-id: 342a98d6c40c636779bf85f616d08e9b18b312c6
content-length: 7268
$ curl -iI https://blog.codefarm.me/ \
> -H'If-Modified-Since: Wed, 22 Sep 2021 06:10:18 GMT'
HTTP/2 304
date: Wed, 22 Sep 2021 06:16:21 GMT
via: 1.1 varnish
cache-control: max-age=600
etag: "614ac8ca-1c64"
expires: Wed, 22 Sep 2021 06:23:12 GMT
age: 188
x-served-by: cache-hkg17930-HKG
x-cache: HIT
x-cache-hits: 2
x-timer: S1632291381.213552,VS0,VE0
vary: Accept-Encoding
x-fastly-request-id: 6604678c8e31a6c9ba1330f38f79998a469ad75b
$ curl -iI https://blog.codefarm.me/ \
> -H'If-Unmodified-Since: Wed, 22 Sep 2021 06:10:18 GMT'
HTTP/2 200
server: GitHub.com
content-type: text/html; charset=utf-8
last-modified: Wed, 22 Sep 2021 06:10:18 GMT
access-control-allow-origin: *
etag: "614ac8ca-1c64"
expires: Wed, 22 Sep 2021 06:23:12 GMT
cache-control: max-age=600
x-proxy-cache: MISS
x-github-request-id: BBC8:13FF:75A61:CAF87:614AC978
accept-ranges: bytes
date: Wed, 22 Sep 2021 06:16:48 GMT
via: 1.1 varnish
age: 216
x-served-by: cache-hkg17928-HKG
x-cache: HIT
x-cache-hits: 3
x-timer: S1632291409.582379,VS0,VE0
vary: Accept-Encoding
x-fastly-request-id: e4ab3af31cbfa36dc9d8d15e66d14b0eccf44059
content-length: 7268
$ curl -iI https://blog.codefarm.me/ \
> -H'If-Unmodified-Since: Wed, 20 Sep 2021 06:10:18 GMT'
HTTP/2 412
server: Varnish
retry-after: 0
content-type: text/html; charset=utf-8
accept-ranges: bytes
date: Wed, 22 Sep 2021 06:16:56 GMT
via: 1.1 varnish
x-served-by: cache-hkg17926-HKG
x-cache: MISS
x-cache-hits: 0
x-timer: S1632291416.406798,VS0,VE0
x-fastly-request-id: ba3bbf134d35ff9f4242e2cc6f5892bc9243732f
content-length: 452
7.2. ETag
The ETag
HTTP response header is an identifier for a specific version of a resource. It lets caches be more efficient and save bandwidth, as a web server does not need to resend a full response if the content has not changed. Additionally, etags help prevent simultaneous updates of a resource from overwriting each other ("mid-air collisions").
If the resource at a given URL changes, a new Etag
value must be generated. A comparison of them can determine whether two representations of a resource are the same. Etags are therefore similar to fingerprints, and might also be used for tracking purposes by some servers. They might also be set to persist indefinitely by a tracking server.
7.2.1. Syntax
ETag: W/"<etag_value>"
ETag: "<etag_value>"
7.2.2. Directives
W/ Optional
'W/' (case-sensitive) indicates that a weak validator is used. Weak etags are easy to generate, but are far less useful for comparisons. Strong validators are ideal for comparisons but can be very difficult to generate efficiently. Weak ETag values of two representations of the same resources might be semantically equivalent, but not byte-for-byte identical. This means weak etags prevent caching when byte range requests are used, but strong etags mean range requests can still be cached.
"<etag_value>"
Entity tag uniquely representing the requested resource. They are a string of ASCII characters placed between double quotes, like "675af34563dc-tr34". The method by which ETag
values are generated is not specified. Often, a hash of the content, a hash of the last modification timestamp, or just a revision number is used. For example, MDN uses a hexadecimal hash of the wiki article content.
7.2.3. Examples
ETag: "33a64df551425fcc55e4d42a148795d9f25f89d4"
ETag: W/"0815"
Avoiding mid-air collisions
With the help of the ETag
and the If-Match headers, you can detect mid-air edit collisions.
For example, when editing a wiki, the current wiki content may be hashed and put into an Etag
header in the response:
ETag: "33a64df551425fcc55e4d42a148795d9f25f89d4"
When saving changes to a wiki page (posting data), the POST request will contain the If-Match
header containing the ETag
values to check freshness against.
If-Match: "33a64df551425fcc55e4d42a148795d9f25f89d4"
If the hashes don’t match, it means that the document has been edited in-between and a 412 Precondition Failed error is thrown.
Caching of unchanged resources
Another typical use of the ETag
header is to cache resources that are unchanged. If a user visits a given URL again (that has an ETag
set), and it is stale (too old to be considered usable), the client will send the value of its ETag
along in an If-None-Match header field:
If-None-Match: "33a64df551425fcc55e4d42a148795d9f25f89d4"
The server compares the client’s ETag
(sent with If-None-Match
) with the ETag
for its current version of the resource, and if both values match (that is, the resource has not changed), the server sends back a 304 Not Modified status, without a body, which tells the client that the cached version of the response is still good to use (fresh).
$ curl -iI https://blog.codefarm.me/
HTTP/2 200
server: GitHub.com
content-type: text/html; charset=utf-8
last-modified: Mon, 20 Sep 2021 14:00:00 GMT
access-control-allow-origin: *
etag: "614893e0-1c64"
expires: Wed, 22 Sep 2021 05:07:57 GMT
cache-control: max-age=600
x-proxy-cache: MISS
x-github-request-id: DE68:7B7F:D07D7:17DBD4:614AB7D5
accept-ranges: bytes
date: Wed, 22 Sep 2021 05:08:12 GMT
via: 1.1 varnish
age: 3
x-served-by: cache-hkg17926-HKG
x-cache: HIT
x-cache-hits: 3
x-timer: S1632287292.223946,VS0,VE0
vary: Accept-Encoding
x-fastly-request-id: 127919c74eb061331922c451e3c21500a47355f8
content-length: 7268
$ curl -iI https://blog.codefarm.me/ \
> -H 'Cache-Control: no-cache, max-age=0' \
> -H 'If-None-Match: "614893e0-1c64"'
HTTP/2 304
date: Wed, 22 Sep 2021 05:08:26 GMT
via: 1.1 varnish
cache-control: max-age=600
etag: "614893e0-1c64"
expires: Wed, 22 Sep 2021 05:07:57 GMT
age: 17
x-served-by: cache-hkg17921-HKG
x-cache: HIT
x-cache-hits: 1
x-timer: S1632287307.670572,VS0,VE1
vary: Accept-Encoding
x-fastly-request-id: 580f53afd3df9b3f06c362965321914c72eb4e60
7.3. If-Match
The If-Match
HTTP request header makes the request conditional. For GET and HEAD methods, the server will send back the requested resource only if it matches one of the listed ETags. For PUT and other non-safe methods, it will only upload the resource in this case.
The comparison with the stored ETag
uses the strong comparison algorithm, meaning two files are considered identical byte to byte only. If a listed ETag
has the W/
prefix indicating a weak entity tag, it will never match under this comparison algorithm.
There are two common use cases:
-
For
GET
andHEAD
methods, used in combination with a Range header, it can guarantee that the new ranges requested comes from the same resource than the previous one. If it doesn’t match, then a 416 (Range Not Satisfiable) response is returned. -
For other methods, and in particular for
PUT
,If-Match
can be used to prevent the lost update problem. It can check if the modification of a resource that the user wants to upload will not override another change that has been done since the original resource was fetched. If the request cannot be fulfilled, the 412 (Precondition Failed) response is returned.
7.3.1. Syntax
If-Match: <etag_value>
If-Match: <etag_value>, <etag_value>, …
7.3.2. Directives
<etag_value>
Entity tags uniquely representing the requested resources. They are a string of ASCII characters placed between double quotes (like "675af34563dc-tr34"). They may be prefixed by W/
to indicate that they are "weak", i.e. that they represent the resource semantically, but not byte-for-byte. However, in an If-Match
header, weak entity tags will never match.
*
The asterisk is a special value representing any resource.
$ curl -iI https://blog.codefarm.me/ \
> -H'If-Match: "614ac8ca-1c64"'
HTTP/2 200
server: GitHub.com
content-type: text/html; charset=utf-8
last-modified: Wed, 22 Sep 2021 06:10:18 GMT
access-control-allow-origin: *
etag: "614ac8ca-1c64"
expires: Wed, 22 Sep 2021 06:23:12 GMT
cache-control: max-age=600
x-proxy-cache: MISS
x-github-request-id: BBC8:13FF:75A61:CAF87:614AC978
accept-ranges: bytes
date: Wed, 22 Sep 2021 06:28:38 GMT
via: 1.1 varnish
age: 105
x-served-by: cache-hkg17924-HKG
x-cache: HIT
x-cache-hits: 1
x-timer: S1632292118.311608,VS0,VE1
vary: Accept-Encoding
x-fastly-request-id: 6d9bb0336b206c24729f619a0d52ac3932df452e
content-length: 7268
$ curl -iI https://blog.codefarm.me/ \
> -H'If-Match: "614ac8ca-123"'
HTTP/2 412
server: Varnish
retry-after: 0
content-type: text/html; charset=utf-8
accept-ranges: bytes
date: Wed, 22 Sep 2021 06:30:10 GMT
via: 1.1 varnish
x-served-by: cache-hkg17923-HKG
x-cache: MISS
x-cache-hits: 0
x-timer: S1632292210.132090,VS0,VE1
x-fastly-request-id: b15ce88b2dae2c305777ffa75dca42c68bc3b6f1
content-length: 452
7.4. If-None-Match
The If-None-Match
HTTP request header makes the request conditional. For GET
and HEAD
methods, the server will send back the requested resource, with a 200
status, only if it doesn’t have an ETag
matching the given ones. For other methods, the request will be processed only if the eventually existing resource’s ETag
doesn’t match any of the values listed.
When the condition fails for GET
and HEAD
methods, then the server must return HTTP status code 304
(Not Modified). For methods that apply server-side changes, the status code 412 (Precondition Failed) is used. Note that the server generating a 304
response MUST generate any of the following header fields that would have been sent in a 200
(OK) response to the same request: Cache-Control
, Content-Location
, Date
, ETag
, Expires
, and Vary
.
The comparison with the stored ETag
uses the weak comparison algorithm, meaning two files are considered identical if the content is equivalent — they don’t have to be identical byte for byte. For example, two pages that differ by the date of generation in the footer would still be considered as identical.
When used in combination with If-Modified-Since
, If-None-Match
has precedence (if the server supports it).
There are two common use cases:
-
For
GET
andHEAD
methods, to update a cached entity that has an associatedETag
. -
For other methods, and in particular for
PUT
,If-None-Match
used with the*
value can be used to save a file not known to exist, guaranteeing that another upload didn’t happen before, losing the data of the previous put; this problem is a variation of the lost update problem.
7.5. If-Modified-Since
The If-Modified-Since
request HTTP header makes the request conditional: the server will send back the requested resource, with a 200
status, only if it has been last modified after the given date. If the resource has not been modified since, the response will be a 304
without any body; the Last-Modified
response header of a previous request will contain the date of last modification. Unlike If-Unmodified-Since
, If-Modified-Since
can only be used with a GET
or HEAD
.
When used in combination with If-None-Match
, it is ignored, unless the server doesn’t support If-None-Match
.
The most common use case is to update a cached entity that has no associated ETag
.
7.6. If-Unmodified-Since
The If-Unmodified-Since
request HTTP header makes the request conditional: the server will send back the requested resource, or accept it in the case of a POST
or another non-safe method, only if it has not been last modified after the given date. If the resource has been modified after the given date, the response will be a 412
(Precondition Failed) error.
There are two common use cases:
-
In conjunction with non-safe methods, like
POST
, it can be used to implement an optimistic concurrency control, like done by some wikis: editions are rejected if the stored document has been modified since the original has been retrieved. -
In conjunction with a range request with a If-Range header, it can be used to ensure that the new fragment requested comes from an unmodified document.
7.6.1. If-Range
The If-Range
HTTP request header makes a range request conditional: if the condition is fulfilled, the range request will be issued and the server sends back a 206 Partial Content answer with the appropriate body. If the condition is not fulfilled, the full resource is sent back, with a 200 OK status.
This header can be used either with a Last-Modified
validator, or with an ETag
, but not with both.
The most common use case is to resume a download, to guarantee that the stored resource has not been modified since the last fragment has been received.
7.6.2. Examples
If-Match: "bfc13a64729c4290ef5b2c2730249c88ca92d82d"
If-Match: "67ab43", "54ed21", "7892dd"
If-Match: *
8. References
-
https://developer.mozilla.org/en-US/docs/Web/HTTP/Caching, HTTP caching - HTTP | MDN
-
https://datatracker.ietf.org/doc/html/rfc7234, Hypertext Transfer Protocol (HTTP/1.1): Caching
-
https://datatracker.ietf.org/doc/html/rfc7232, Hypertext Transfer Protocol (HTTP/1.1): Conditional Requests