187

With very simple caching semantics: if the parameters are the same (and the URL is the same, of course), then it's a hit. Is that possible? Recommended?

Nayuki
  • 17,911
  • 6
  • 53
  • 80
flybywire
  • 261,858
  • 191
  • 397
  • 503

9 Answers9

108

The corresponding RFC 2616 in section 9.5 (POST) allows the caching of the response to a POST message, if you use the appropriate headers.

Responses to this method are not cacheable, unless the response includes appropriate Cache-Control or Expires header fields. However, the 303 (See Other) response can be used to direct the user agent to retrieve a cacheable resource.

Note that the same RFC states explicitly in section 13 (Caching in HTTP) that a cache must invalidate the corresponding entity after a POST request.

Some HTTP methods MUST cause a cache to invalidate an entity. This is either the entity referred to by the Request-URI, or by the Location or Content-Location headers (if present). These methods are:

  - PUT
  - DELETE
  - POST

It's not clear to me how these specifications can allow meaningful caching.

This is also reflected and further clarified in RFC 7231 (Section 4.3.3.), which obsoletes RFC 2616.

Responses to POST requests are only cacheable when they include
explicit freshness information (see Section 4.2.1 of [RFC7234]).
However, POST caching is not widely implemented. For cases where an origin server wishes the client to be able to cache the result of a POST in a way that can be reused by a later GET, the origin server MAY send a 200 (OK) response containing the result and a Content-Location header field that has the same value as the POST's effective request URI (Section 3.1.4.2).

According to this, the result of a cached POST (if this ability is indicated by the server) can be subsequently used for as the result of a GET request for the same URI.

Community
  • 1
  • 1
Diomidis Spinellis
  • 18,734
  • 5
  • 61
  • 83
  • 1
    This section applies to an intermediate cache (like a caching proxy server), not the origin server. – David Z Mar 09 '09 at 12:55
  • True, but they can be interpreted in this context too (and they do make sense). – Grey Panther Mar 09 '09 at 13:05
  • 2
    The origin server is a broker between HTTP and the application that handles the POST requests. The application is beyond the HTTP boundary and it can do whatever it pleases. If caching makes sense for a specific POST request it's free to cache, as much as the OS can cache disk requests. – Diomidis Spinellis Mar 09 '09 at 13:10
  • 1
    That is what I meant, that the application running on the origin server is not bound by the HTTP restrictions on caching. – David Z Mar 09 '09 at 13:26
  • 2
    Diomidis, your statement that caching POST requests would not be HTTP, is wrong. Please see reBoot's answer for details. It's not very helpful to have the wrong answer show up at the top, but that's how democracy works. If you agree with reBoot, it would be nice if you corrected your answer. – Evgeniy Berezovsky Aug 11 '11 at 02:24
  • 2
    Eugene, can we agree that a) POST should invalidate the cached entity (per section 13.10), so that e.g. a subsequent GET must fetch a fersh copy and b) that the POST's response can be cached (per section 9.5), so that e.g. a subsequent POST can receive the same response? – Diomidis Spinellis Aug 14 '11 at 21:12
  • Diomidis, we can probably agree on many things, but I explicitly criticized the 'if you cached, it would not be HTTP' statement, as the HTTP spec says (albeit formulated in inversely) 'caching is OK, if appropriate headers are used'. – Evgeniy Berezovsky Aug 19 '11 at 04:02
  • Eugene, I modified the entry as I suggested. I can't however see how this would work in practice. – Diomidis Spinellis Aug 27 '11 at 15:23
  • 4
    This is being clarified by HTTPbis; see http://www.mnot.net/blog/2012/09/24/caching_POST for a summary. – Mark Nottingham Sep 23 '12 at 16:05
  • 1
    Clarification by Mark Nottingham, with slightly changed wording, is now standardized as [RFC 7231](https://tools.ietf.org/html/rfc7231#section-4.3.3) which obsoletes RFC 2616. – gmk57 May 27 '20 at 11:14
  • Yes, under certain conditions, as indicated in the answer. – Diomidis Spinellis Mar 10 '21 at 09:09
75

According to RFC 2616 Section 9.5:

"Responses to POST method are not cacheable, UNLESS the response includes appropriate Cache-Control or Expires header fields."

So, YES, you can cache POST request response but only if it arrives with appropriate headers. In most cases you don't want to cache the response. But in some cases - such as if you are not saving any data on the server - it's entirely appropriate.

Note, however many browsers, including current Firefox 3.0.10, will not cache POST response regardless of the headers. IE behaves more smartly in this respect.

Now, i want to clear up some confusion here regarding RFC 2616 S. 13.10. POST method on a URI doesn't "invalidate the resource for caching" as some have stated here. It makes a previously cached version of that URI stale, even if its cache control headers indicated freshness of longer duration.

  • 4
    What's the difference between "invalidate the resource for caching" and "making a cached version of the URI stale"? Are you saying that the server is allowed to cache a POST response but clients may not? – Gili Jan 12 '12 at 18:37
  • 5
    "making a cached version of the URI stale" applies where you use the same URI for `GET` and `POST` requests. If you are a cache sitting between the client and the server, you see `GET /foo` and you cache the response. Next you see `POST /foo` then you are *required* to invalidate the cached response from `GET /foo` even if the `POST` response doesn't include any cache control headers *because they are the same URI*, thus the next `GET /foo` will have to revalidate even if the original headers indicated the cache would still be live (if you had not seen the `POST /foo` request) – Stephen Connolly Sep 28 '18 at 10:01
  • 2
    ```But in some cases - such as if you are not saving any data on the server - it's entirely appropriate.```. What's the point of such a POST API in the first place then? – Siddhartha Jun 07 '20 at 18:02
39

If you're wondering whether you can cache a post request, and try researching an answer to that question, you likely won't succeed. When searching "cache post request" the first result is this StackOverflow question.

The answers are a confused mixture of how caching should work, how caching works according to the RFC, how caching should work according to the RFC, and how caching works in practice. Let's start with the RFC, walk through how browser's actually work, then talk about CDNs, GraphQL, and other areas of concern.

RFC 2616

Per the RFC, POST requests must invalidate the cache:

13.10 Invalidation After Updates or Deletions

..

Some HTTP methods MUST cause a cache to invalidate an entity. This is
either the entity referred to by the Request-URI, or by the Location
or Content-Location headers (if present). These methods are:
  - PUT
  - DELETE
  - POST

This language suggests POST requests are not cacheable, but that is not true (in this case). The cache is only invalidated for previously stored data. The RFC (appears to) explicitly clarify that yes, you can cache POST requests:

9.5 POST

..

Responses to this method are not cacheable, unless the response
includes appropriate Cache-Control or Expires header fields. However,
the 303 (See Other) response can be used to direct the user agent to
retrieve a cacheable resource.

Despite this language, setting the Cache-Control must not cache subsequent POST requests to the same resource. POST requests must be sent to the server:

13.11 Write-Through Mandatory

..

All methods that might be expected to cause modifications to the
origin server's resources MUST be written through to the origin
server. This currently includes all methods except for GET and HEAD.
A cache MUST NOT reply to such a request from a client before having
transmitted the request to the inbound server, and having received a
corresponding response from the inbound server. This does not prevent
a proxy cache from sending a 100 (Continue) response before the
inbound server has sent its final reply.

How does that make sense? Well, you're not caching the POST request, you're caching the resource.

The POST response body can only be cached for subsequent GET requests to the same resource. Set the Location or Content-Location header in the POST response to communicate which resource the body represents. So the only technically valid way to cache a POST request, is for subsequent GETs to the same resource.

The correct answer is both:

  • "yes, the RFC allows you to cache POST requests for subsequent GETs to the same resource"
  • "no, the RFC does not allow you to cache POST requests for subsequent POSTs because POST is not idempotent and must be written through to the server"

Although the RFC allows for caching requests to the same resource, in practice, browsers and CDNs do not implement this behavior, and do not allow you to cache POST requests.

Sources:

Demonstration of Browser Behavior

Given the following example JavaScript application (index.js):

const express = require('express')
const app = express()

let count = 0

app
    .get('/asdf', (req, res) => {
        count++
        const msg = `count is ${count}`
        console.log(msg)
        res
            .set('Access-Control-Allow-Origin', '*')
            .set('Cache-Control', 'public, max-age=30')
            .send(msg)
    })
    .post('/asdf', (req, res) => {
        count++
        const msg = `count is ${count}`
        console.log(msg)
        res
            .set('Access-Control-Allow-Origin', '*')
            .set('Cache-Control', 'public, max-age=30')
            .set('Content-Location', 'http://localhost:3000/asdf')
            .set('Location', 'http://localhost:3000/asdf')
            .status(201)
            .send(msg)
    })
    .set('etag', false)
    .disable('x-powered-by')
    .listen(3000, () => {
        console.log('Example app listening on port 3000!')
    })

And given the following example web page (index.html):

<!DOCTYPE html>
<html>

<head>
    <script>
        async function getRequest() {
            const response = await fetch('http://localhost:3000/asdf')
            const text = await response.text()
            alert(text)
        }
        async function postRequest(message) {
            const response = await fetch(
                'http://localhost:3000/asdf',
                {
                    method: 'post',
                    body: { message },
                }
            )
            const text = await response.text()
            alert(text)
        }
    </script>
</head>

<body>
    <button onclick="getRequest()">Trigger GET request</button>
    <br />
    <button onclick="postRequest('trigger1')">Trigger POST request (body 1)</button>
    <br />
    <button onclick="postRequest('trigger2')">Trigger POST request (body 2)</button>
</body>

</html>

Install NodeJS, Express, and start the JavaScript application. Open the web page in your browser. Try a few different scenarios to test browser behavior:

  • Clicking "Trigger GET request" displays the same "count" every time (HTTP caching works).
  • Clicking "Trigger POST request" triggers a different count every time (HTTP caching for POST does not work).
  • Clicking "Trigger GET request", "Trigger POST request", and "Trigger GET request" shows the POST request invalidates the GET request's cache.
  • Clicking "Trigger POST request" then "Trigger GET request" shows that browsers will not cache POST requests for subsequent GET requests even though it's allowed by the RFC.

This shows that, even though you can set the Cache-Control and Content-Location response headers, there is no way to make a browser cache an HTTP POST request.

Do I have to follow the RFC?

Browser behavior is not configurable, but if you're not a browser, you aren't necessarily bound by the rules of the RFC.

If you're writing application code, there's nothing stopping you from explicitly caching POST requests (pseudocode):

if (cache.get('hello')) {
  return cache.get('hello')
} else {
  response = post(url = 'http://somewebsite/hello', request_body = 'world')
  cache.put('hello', response.body)
  return response.body
}

CDNs, proxies, and gateways do not necessarily have to follow the RFC either. For example, if you use Fastly as your CDN, Fastly allows you to write custom VCL logic to cache POST requests.

Should I cache POST requests?

Whether your POST request should be cached or not depends on the context.

For example, you might query Elasticsearch or GraphQL using POST where your underlying query is idempotent. In those cases, it may or may not make sense to cache the response depending on the use case.

In a RESTful API, POST requests usually create a resource and should not be cached. This is also the RFC's understanding of POST that it is not an idempotent operation.

GraphQL

If you're using GraphQL and require HTTP caching across CDNs and browsers, consider whether sending queries using the GET method meets your requirements instead of POST. As a caveat, different browsers and CDNs may have different URI length limits, but operation safelisting (query whitelist), as a best practice for external-facing production GraphQL apps, can shorten URIs.

timrs2998
  • 1,612
  • 18
  • 14
34

Overall:

Basically POST is not an idempotent operation. So you cannot use it for caching. GET should be an idempotent operation, so it is commonly used for caching.

Please see section 9.1 of the HTTP 1.1 RFC 2616 S. 9.1.

Other than GET method's semantics:

The POST method itself is semantically meant to post something to a resource. POST cannot be cached because if you do something once vs twice vs three times, then you are altering the server's resource each time. Each request matters and should be delivered to the server.

The PUT method itself is semantically meant to put or create a resource. It is an idempotent operation, but it won't be used for caching because a DELETE could have occurred in the meantime.

The DELETE method itself is semantically meant to delete a resource. It is an idempotent operation, but it won't be used for caching because a PUT could have occurred in the meantime.

Regarding client side caching:

A web browser will always forward your request even if it has a response from a previous POST operation. For example you may send emails with gmail a couple days apart. They may be the same subject and body, but both emails should be sent.

Regarding proxy caching:

A proxy HTTP server that forwards your message to the server would never cache anything but a GET or a HEAD request.

Regarding server caching:

A server by default wouldn't automatically handle a POST request via checking its cache. But of course a POST request can be sent to your application or add-in and you can have your own cache that you read from when the parameters are the same.

Invalidating a resource:

Checking the HTTP 1.1 RFC 2616 S. 13.10 shows that the POST method should invalidate the resource for caching.

Brian R. Bondy
  • 339,232
  • 124
  • 596
  • 636
  • 14
    "Basically POST is not an idempotent operation. So you cannot use it for caching." That's just wrong, and it does not really make sense, see reBoot's answer for details. Unfortunately, I can't downvote yet, otherwise I would have. – Evgeniy Berezovsky Aug 11 '11 at 02:21
  • 1
    Eugene: I changed "is not" to "may not". – Brian R. Bondy Aug 11 '11 at 14:16
  • 1
    Thanks Brian, that sounds better. My problem with your "POST not idemp. -> can't be cached" though was - and I did not make that clear enough - even though an operation is not idempotent that does not mean it is not cacheable. I guess the question is are you looking at it from the point of view of the server, who offers the data and knows its semantics, or are you looking at it from the receiving side (be it a caching proxy etc. or a client). If it's the client/proxy pov, I totally agree with your post. If it's the server pov, if the server says: "client can cache", than client can cache. – Evgeniy Berezovsky Aug 13 '11 at 01:05
  • 1
    Eugene: If it makes a difference whether it is called once or 5 times, such as if you are posting a message to a list, then you want that call to hit the server 5 times right? And you don't want to cache it so it doesn't hit the server right? Because there are side effects that are important. – Brian R. Bondy Aug 13 '11 at 01:10
  • [cont'd] I have however not made my mind up whether the server indeed should send cache-allowing expires header ONLY if it the operation is idempotent. It kind of makes sense, though, I guess. [just saw your response]: Agreed, so I gues I made my mind up: Server should only signal cacheability in case of idempotency - and that could be a POST as well, especially considering the need for X-HTTP-Method-Override in some cases. – Evgeniy Berezovsky Aug 13 '11 at 01:14
  • The OP asked if it was possible, not if it should be done, and the HTTP specification (as referenced in other answers) does indicate that a server may return cache headers to indicate that the response can be cached. The rest of the detail in your answer is good. – perfectionist Nov 10 '16 at 08:37
  • `REST` - "POST not idemp. -> can't be cached", `HTTP` - "POST can be cached". Choose depending on whether you're sticking to REST or simply use HTTP to as a data exchange protocol. Both are right in their own domains. – skryvets Jul 02 '20 at 22:32
9

If you do cache a POST response, it must be at the direction of the web application. This is what is meant by "Responses to this method are not cachable, unless the response includes appropriate Cache-Control or Expires header fields."

One can safely assume that the application, which knows whether or not the results of a POST are idempotent, decides whether or not to attach the necessary and proper cache control headers. If headers that suggest caching is allowed are present, the application is telling you that the POST is, in actuality, a super-GET; that the use of POST was only required due to the amount of unnecessary and irrelevant (to the use of the URI as a cache key) data necessary to perform the idempotent operation.

Following GET's can be served from cache under this assumption.

An application that fails to attach the necessary and correct headers to differentiate between cachable and non-cachable POST responses is at fault for any invalid caching results.

That said, each POST that hits the cache requires validation using conditional headers. This is required in order to refresh the cache content to avoid having the results of a POST not be reflected in the responses to requests until after the lifetime of the object expires.

JohnS
  • 99
  • 1
  • 1
7

Mark Nottingham has analyzed when it is feasible to cache the response of a POST. Note that the subsequent requests that want to take advantage of caching must be GET or HEAD requests. See also http semantics

POSTs don't deal in representations of identified state, 99 times out of 100. However, there is one case where it does; when the server goes out of its way to say that this POST response is a representation of its URI, by setting a Content-Location header that's the same as the request URI. When that happens, the POST response is just like a GET response to the same URI; it can be cached and reused -- but only for future GET requests.

https://www.mnot.net/blog/2012/09/24/caching_POST.

Community
  • 1
  • 1
dschulten
  • 2,994
  • 1
  • 27
  • 44
4

If it's something that doesn't actually change data on your site, it should be a GET request. Even if it's a form, you can still set it as a get request. While, like others point out, you could cache the results of a POST, it wouldn't make semantic sense because a POST by definition is changing data.

Kibbee
  • 65,369
  • 27
  • 142
  • 182
  • The POST request might not be changing any data that is used to generate the response page, in which case it could make sense to cache the response. – David Z Mar 09 '09 at 13:27
  • David Z: Surely if a POST is changing data then the response should give some indication of success / failure. Not required exactly, but I can't think of a situation where a POST would change data and the response be static. – Morvael Oct 25 '13 at 11:02
  • 9
    If the parameter data is too long, a GET request won't work with all servers, thus POST is needed, especially if the source should run on servers that the code author doesn't configure. – Gogowitsch Apr 06 '15 at 16:31
  • @Gogowitsch very true, you'll run into a 414 error code - https://stackoverflow.com/a/2891598/792238 – Siddhartha Jun 07 '20 at 17:57
-2

With firefox 27.0 & with httpfox, on May 19, 2014, I saw one line of this: 00:03:58.777 0.488 657 (393) POST (Cache) text/html https://users.jackiszhp.info/S4UP

Clearly, the response of a post method is cached, and it is also in https. Unbelievable!

jackiszhp
  • 767
  • 1
  • 5
  • 11
-5

POST is used in stateful Ajax. Returning a cached response for a POST defeats the communication channel and the side effects of receiving a message. This is Very Very Bad. It's also a real pain to track down. Highly recommended against.

A trivial example would be a message that, as a side effect, pays your salary $10,000 the current week. You DON'T want to get the "OK, it went through!" page back that was cached last week. Other, more complex real-world cases result in similar hilarity.

DragonLord
  • 6,395
  • 4
  • 36
  • 38