HTTP中是否可能缓存POST方法?

191

使用非常简单的缓存机制:如果参数相同(当然,URL也相同),则命中。这是否可行?建议采用?

9个回答

110

在第9.5节(POST)中,对应的RFC 2616允许缓存POST消息的响应,如果您使用适当的标头。

除非响应包含适当的Cache-Control或Expires标头字段,否则不得缓存此方法的响应。但是,可以使用303(See Other)响应将用户代理重定向到可缓存的资源。

请注意,同一RFC在第13节(HTTP缓存)中明确指出,在POST请求之后,缓存必须使相应的实体失效。

某些HTTP方法必须导致缓存使实体失效。这既可以是由Request-URI引用的实体,也可以是由Location或Content-Location标头引用的实体(如果存在)。这些方法是:

  - PUT
  - DELETE
  - POST

我不清楚这些规范如何允许有意义的缓存。

这也在RFC 7231(第4.3.3节)中有所体现和进一步澄清,该标准废除了RFC 2616。

仅当POST响应包括明确的新鲜度信息时(请参阅[RFC7234]的第4.2.1节),才可以缓存响应。然而,POST缓存并不常用。 对于源服务器希望客户端能够缓存POST结果以便稍后重用的情况,源服务器可以发送一个包含结果和Content-Location头字段的200(OK)响应,该头字段具有与POST的有效请求URI(第3.1.4.2节)相同的值。

根据此规范,如果服务器指示了此功能,缓存的POST结果可以随后用作同一URI的GET请求的结果。


2
源服务器是HTTP和处理POST请求的应用程序之间的中介。 应用程序超出了HTTP边界,可以随心所欲地执行操作。 如果缓存对于特定的POST请求有意义,它可以自由地缓存,就像操作系统可以缓存磁盘请求一样。 - Diomidis Spinellis
1
这就是我的意思,即运行在源服务器上的应用程序不受HTTP缓存限制的约束。 - David Z
2
Diomidis,你的说法缓存POST请求不会是HTTP是错误的。请参阅reBoot的答案以了解详情。在错误的答案出现在顶部并不是很有帮助,但这就是民主如何运作。如果你同意reBoot的看法,那么更正你的回答会很好。 - Evgeniy Berezovsky
2
尤金,我们能否达成以下协议:a)根据第13.10节,POST应使缓存的实体失效,以便例如后续的GET必须获取新的副本;b)根据第9.5节,POST的响应可以被缓存,以便例如后续的POST可以接收相同的响应? - Diomidis Spinellis
4
这一点在HTTPbis中得到了澄清;请参阅http://www.mnot.net/blog/2012/09/24/caching_POST以获得摘要。 - Mark Nottingham
显示剩余6条评论

75
根据RFC 2616第9.5节的规定:

“POST方法的响应是不可缓存的,除非响应包括适当的Cache-Control或Expires头字段。”

所以,是的,您可以缓存POST请求的响应,但仅当它带有适当的头信息时才能这样做。在大多数情况下,您不想缓存响应。但在某些情况下 - 比如如果您没有在服务器上保存任何数据 - 这是完全合适的。
然而,请注意,许多浏览器(包括当前的Firefox 3.0.10)将不会缓存POST响应,无论头信息如何。IE在这方面表现得更加聪明。
现在,我想澄清一些关于RFC 2616 S. 13.10的混淆。URI上的POST方法并不会“使缓存的资源无效”,正如一些人在这里所说的那样。即使其缓存控制头指示了更长时间的新鲜度,它也会使该URI的先前缓存版本过期。

4
“使缓存的资源失效”和“使URI的缓存版本过期”的区别是什么?您是否意味着服务器可以缓存POST响应,但客户端则不允许? - Gili
7
如果您在GETPOST请求中使用相同的URI,那么"使缓存版本过期"就适用。如果您是客户端和服务器之间的缓存,则会看到GET /foo并将其缓存下来。接下来,您会看到POST /foo,即使POST响应不包括任何缓存控制头,因为它们是相同的URI,因此您必须使从GET /foo获取的缓存响应失效,这样下一个GET /foo请求就必须重新验证,即使原始头部指示缓存仍然有效(如果您没有看到POST /foo请求)。 - Stephen Connolly
2
但在某些情况下 - 比如如果您没有在服务器上保存任何数据 - 这是完全适当的。那么这样的POST API一开始的目的是什么呢? - Siddhartha

43
如果你想知道是否可以缓存一个POST请求,并尝试寻找答案,你可能不会成功。当搜索“cache post request”时,第一个结果是这个StackOverflow问题。
答案是混乱的,涉及到缓存应该如何工作,根据RFC缓存如何工作,根据RFC缓存应该如何工作,以及缓存在实践中如何工作。让我们从RFC开始,了解浏览器实际上是如何工作的,然后谈论CDN、GraphQL和其他相关领域。
RFC 2616
根据RFC,POST请求必须使缓存失效。
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

这种语言表明POST请求不可缓存,但事实并非如此(在这种情况下)。缓存仅对先前存储的数据无效。RFC (似乎)明确澄清,是的,您可以缓存POST 请求:

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.

尽管如此,设置Cache-Control不能缓存后续针对同一资源的POST请求。必须将POST请求发送到服务器:
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.

这怎么说呢?你没有缓存POST请求,而是缓存了资源。 POST响应体只能被缓存用于后续对同一资源的GET请求。在POST响应中设置LocationContent-Location头来指示响应体表示的资源。所以,唯一有效的缓存POST请求的方式是用于后续对同一资源的GET请求。
正确答案是两个:
  • "是的,RFC允许您缓存POST请求,以便用于后续对同一资源的GET请求"
  • "不,RFC不允许您缓存POST请求,以便用于后续的POST请求,因为POST不具有幂等性并且必须写入服务器"
虽然RFC允许缓存对同一资源的请求,但在实践中,浏览器和CDN不实现此行为,并且不允许您缓存POST请求。
来源:

浏览器行为演示

给定以下示例JavaScript应用程序(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!')
    })

假设有以下示例网页(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>

安装NodeJS、Express并启动JavaScript应用程序。在浏览器中打开网页,尝试几种不同的场景来测试浏览器行为:
- 点击“触发GET请求”每次显示相同的“计数”(HTTP缓存有效)。 - 点击“触发POST请求”每次触发不同的计数(HTTP缓存对于POST无效)。 - 点击“触发GET请求”、“触发POST请求”和“触发GET请求”显示POST请求使得GET请求的缓存无效。 - 点击“触发POST请求”然后“触发GET请求”显示浏览器不会为后续的GET请求缓存POST请求,即使RFC允许这样做。
这表明,即使您可以设置Cache-ControlContent-Location响应头,也没有办法使浏览器缓存HTTP POST请求。

我必须遵循RFC吗?

浏览器行为无法配置,但如果你不是浏览器,你不一定受到RFC规则的约束。
如果你正在编写应用程序代码,没有什么阻止你明确地缓存POST请求(伪代码):
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
}

CDN、代理和网关不一定需要遵循 RFC。例如,如果您使用 Fastly 作为 CDN,则 Fastly 允许您编写自定义 VCL逻辑以缓存 POST 请求
我是否应该缓存 POST 请求取决于上下文。例如,您可能会使用 POST 查询 Elasticsearch 或 GraphQL,在这种情况下,根据用例,缓存响应可能有意义也可能没有意义。
在 RESTful API 中,POST 请求通常创建资源,不应该被缓存。这也是 RFC 对 POST 的理解,即它不是幂等操作。
如果您正在使用 GraphQL 并且需要在 CDN 和浏览器之间进行 HTTP 缓存,请考虑是否使用GET 方法发送查询来满足您的要求,而不是POST。需要注意的是,不同的浏览器和 CDN 可能具有不同的 URI 长度限制,但是操作安全列表(查询白名单)作为外部面向生产 GraphQL 应用程序的最佳实践可以缩短 URI。

1
我见过的最好的答案之一 - mehmet6parmak

34

总体而言:

基本上,POST不是幂等操作,因此不能用于缓存。GET应该是一个幂等操作,所以通常用于缓存。

请参阅HTTP 1.1 RFC 2616 S.9.1第9.1节。

除GET方法的语义外:

POST方法本身的语义是将某些内容发布到资源中。POST不能被缓存,因为如果您做一次、两次或三次操作,则每次都会改变服务器的资源。每个请求都很重要,都应该发送到服务器。

PUT方法本身的语义是放置或创建资源。它是一个幂等操作,但不会用于缓存,因为在此期间可能发生了DELETE操作。

DELETE方法本身的语义是删除资源。它是一个幂等操作,但不会用于缓存,因为在此期间可能发生了PUT操作。

关于客户端缓存:

Web浏览器将始终转发您的请求,即使它具有来自先前POST操作的响应。例如,您可以隔几天使用gmail发送相同主题和正文的电子邮件。每封电子邮件都应该发出。

关于代理缓存:

将您的消息转发到服务器的代理HTTP服务器永远不会缓存除GET或HEAD请求以外的任何内容。

关于服务器缓存:

默认情况下,服务器不会通过检查其缓存来自动处理POST请求。但是,当参数相同时,可以将POST请求发送到您的应用程序或插件,并从缓存中读取。

使资源无效:

查看HTTP 1.1 RFC 2616 S. 13.10可知,POST方法应使缓存失效。


14
基本上,POST操作不是幂等的。因此,您不能将其用于缓存。"这种说法是错误的,而且并没有什么意义,详细信息请参见reBoot的答案。不幸的是,我还不能进行投票,否则我会这样做。 - Evgeniy Berezovsky
1
尤金:我把“不是”改成了“可能不是”。 - Brian R. Bondy
1
谢谢Brian,这听起来更好。然而,我对你的“POST不是幂等的->不能被缓存”有问题,尽管我没有表达清楚-即使操作不是幂等的,也并不意味着它不能被缓存。我想问题在于你是从服务器的角度来看待它的,服务器提供数据并知道其语义,还是从接收方的角度来看待它(无论是缓存代理等还是客户端)。如果是客户端/代理的观点,我完全同意你的帖子。如果是服务器的观点,如果服务器说:“客户端可以缓存”,那么客户端就可以缓存。 - Evgeniy Berezovsky
1
如果调用一次和五次有区别,比如你要向列表发布一条消息,那么你希望该调用命中服务器5次,对吧?而且你不想将其缓存以避免命中服务器,对吧?因为有些副作用是很重要的。 - Brian R. Bondy
OP问的是是否可能,而不是应该这样做,HTTP规范(如其他答案中所引用)确实表明服务器可以返回缓存头以指示响应可以被缓存。你回答中的其余细节很好。 - perfectionist
显示剩余2条评论

9
如果您缓存了POST响应,则必须得到Web应用程序的指示。这就是“除非响应包括适当的Cache-Control或Expires标头字段,否则不可缓存该方法的响应”的含义。
可以安全地假设,应用程序知道POST的结果是否具有幂等性,决定是否附加必要和适当的缓存控制标头。如果存在表明允许缓存的标头,则应用程序告诉您POST实际上是超级GET;只有由于执行幂等操作所需的大量不必要和无关紧要的数据才需要使用POST。
在此假设下,可以从缓存中提供以下GET。
未能附加区分可缓存和不可缓存POST响应所需的必要和正确标头的应用程序会导致任何无效的缓存结果。
话虽如此,每个命中缓存的POST都需要使用条件标头进行验证。为了刷新缓存内容以避免POST的结果在请求的响应中反映之前不被反映直到对象的生存期过期。

7
马克·诺丁汉分析了何时可以缓存POST的响应。请注意,想要利用缓存的后续请求必须是GET或HEAD请求。请参见http语义

99次中,POST不涉及已识别状态的表示。 然而,有一种情况例外;当服务器特别声明此POST响应是其URI的表示形式时, 通过设置Content-Location标题与请求URI相同。当发生这种情况时, POST响应就像对同一URI的GET响应一样;它可以被缓存和重复使用--但仅限于将来的GET请求。

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


4
如果涉及到的内容并没有实际改变网站上的数据,那么它应该是一个GET请求。即使它是一个表单,你仍然可以将其设置为GET请求。虽然像其他人指出的那样,你可以缓存POST的结果,但这在语义上并不合理,因为根据定义,POST是在改变数据。

POST请求可能不会更改用于生成响应页面的任何数据,因此缓存响应可能是有意义的。 - David Z
David Z:如果POST正在更改数据,那么响应应该给出一些成功/失败的指示。不一定需要完全一样,但我想不出一个POST会更改数据而响应保持静态的情况。 - Morvael
9
如果参数数据过长,GET 请求将无法与所有服务器正常工作,因此需要使用 POST 请求,特别是当源代码的作者未对服务器进行配置时。 - Gogowitsch
@Gogowitsch非常正确,您将遇到414错误代码-https://dev59.com/iXE85IYBdhLWcg3wNgrk#2891598 - Siddhartha

-2

使用Firefox 27.0和HttpFox,在2014年5月19日,我看到了这样一行代码:

00:03:58.777 0.488 657 (393) POST (Cache) text/html https://users.jackiszhp.info/S4UP

显然,POST方法的响应被缓存,并且是在HTTPS中。真不可思议!


-5

POST 在有状态的 Ajax 中使用。返回 POST 的缓存响应会破坏通信渠道和接收消息的副作用。这是非常非常糟糕的。而且,要追踪它也是真正的痛苦。强烈建议不要这样做。

一个微不足道的例子是一条消息,其副作用是支付你本周的 10,000 美元工资。你不想得到上周缓存的“好的,它通过了!”页面。其他更复杂的实际情况会导致类似的滑稽结果。


4
并不是一个明确的答案 - POST 方法被用于各种事情,有时希望缓存响应结果是合理的。 - Alexei Levenkov

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接