是否可以在HTTP中缓存POST方法?


152

具有非常简单的缓存语义:如果参数相同(当然,URL也相同),那么这很成功。那可能吗?推荐的?

Answers:


93

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

除非响应包括适当的Cache-Control或Expires标头字段,否则对此方法的响应不可缓存。但是,303(请参阅其他)响应可用于指导用户代理检索可缓存资源。

请注意,相同的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缓存并未广泛实施。对于原始服务器希望客户端能够以一种可以被以后的GET重用的方式缓存POST结果的情况,原始服务器可以发送一个200(OK)响应,其中包含结果和一个Content-Location。标头字段,其值与POST的有效请求URI相同(第3.1.4.2节)。

据此,可以将缓存的POST的结果(如果此功能由服务器指示)随后用于对相同URI的GET请求。


1
本部分适用于中间高速缓存(例如高速缓存代理服务器),而不适用于原始服务器。
David Z

2
原始服务器是HTTP和处理POST请求的应用程序之间的代理。该应用程序超出了HTTP的范围,它可以做任何它想做的事情。如果缓存对于特定的POST请求有意义,则可以自由缓存,这与操作系统可以缓存磁盘请求的次数一样多。
Diomidis Spinellis 2009年

2
Diomidis,您认为缓存POST请求不是HTTP的说法是错误的。有关详细信息,请参见reBoot的答案。在顶部显示错误的答案不是很有帮助,但这就是民主的运作方式。如果您同意reBoot,则更正您的答案会很好。
尤金·别列索夫斯基

2
尤金(Eugene),我们可以同意:a)POST应该使缓存的实体无效(根据第13.10节),以便例如后续的GET必须获取fersh副本,以及b)可以缓存POST的响应(根据9.5节),以便例如随后的POST可以收到相同的响应吗?
Diomidis Spinellis 2011年

3
HTTPbis正在对此进行澄清;有关摘要,请参见mnot.net/blog/2012/09/24/caching_POST
马克·诺丁汉

68

根据RFC 2616第9.5节:

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

因此,是的,您可以缓存POST请求响应,但前提是它带有适当的标头。在大多数情况下,您不想缓存响应。但是在某些情况下(例如,如果您没有在服务器上保存任何数据),这是完全适当的。

请注意,无论标题如何,许多浏览器(包括当前的Firefox 3.0.10)都不会缓存POST响应。IE在这方面表现得更聪明。

现在,我想在这里消除对RFC 2616 S. 13.10的困惑。URI上的POST方法不会“使用于缓存的资源无效”,正如某些人在此处所述。即使它的缓存控制标头指示了较长的持续时间,它也会使该URI过时的先前缓存的版本。


2
+1 reBoot,感谢您解释标题问题并更正了有关13.10的错误声明。令人惊讶的是,这些错误的答案获得了很多好评。
尤金·别列索夫斯基

3
“使缓存资源无效”与“使URI过时的缓存版本”有什么区别?您是说允许服务器缓存POST响应,但客户端可能不缓存吗?
吉利2012年

1
当您对GETPOST请求使用相同的URI时,“制作URI过期的缓存版本”适用。如果您是位于客户端和服务器之间的缓存,则会看到GET /foo并缓存响应。接下来您会看到,即使响应不包含任何缓存控制标头,因为它们是相同的URIPOST /foo需要使来自的缓存响应无效,因此即使原始标头指示缓存仍为URI,下一个也必须重新验证。直播(如果您没有看到请求)GET /fooPOSTGET /fooPOST /foo
Stephen Connolly

But in some cases - such as if you are not saving any data on the server - it's entirely appropriate.。那么,这样的POST API有什么意义呢?
悉达多

33

总体:

基本上,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方法应使用于缓存的资源无效。


9
“基本上,POST不是幂等操作。因此,不能将其用于缓存。” 这是错误的,并且没有任何意义,有关详细信息,请参见reBoot的答案。不幸的是,我不能投票,否则我会投票。
尤金·别列索夫斯基

1
尤金:我将“不是”改为“可能不是”。
Brian R. Bondy

1
谢谢Brian,听起来更好。我的问题是您的“ POST不是幂等的->无法缓存”,尽管我-并没有足够清楚-一个操作不是幂等的,这并不意味着它是不可缓存的。我想问题是从服务器的角度看它,谁提供数据并知道它的语义,还是从接收方(例如缓存代理等或客户端)看它? 。如果是客户/代理人,我完全同意您的帖子。如果是服务器pov,则服务器说:“客户端可以缓存”,则客户端可以缓存。
尤金·别列索夫斯基

1
尤金(Eugene):如果一次调用或5次调用都会有所不同(例如,将消息发布到列表中),那么您希望该调用正确打5次服务器吗?而且您不想缓存它,这样它就不会打在服务器上了吗?因为有些副作用很重要。
Brian R. Bondy

[续]但是,我还没有决定服务器是否确实应该仅在操作是幂等的情况下才发送缓存允许过期标头。我想这是有道理的。[只是看到了您的回应]:同意,所以我想下定决心:服务器仅应在等幂情况下发出可缓存性信号-这也可能是POST,特别是考虑到在X-HTTP-Method-Override中的需求某些情况下。
尤金·别列索夫斯基

6

如果您确实缓存了POST响应,则该响应必须在Web应用程序的指导下。这就是“对此方法的响应是不可缓存的,除非响应包括适当的Cache-Control或Expires标头字段”。

可以放心地假设,知道POST结果是否幂等的应用程序决定是否附加必要和适当的缓存控制标头。如果存在建议缓存的标头,则该应用程序会告诉您POST实际上是一个超级GET。仅仅由于执行幂等操作所需的不必要和无关的数据量(使用URI作为缓存键)才需要使用POST。

在这种假设下,可以从缓存中提供以下GET。

无法附加必要且正确的标头以区分可缓存和不可缓存POST响应的应用程序因任何无效的缓存结果而出错。

也就是说,每个到达缓存的POST都需要使用条件标头进行验证。为了刷新缓存内容,这是必需的,以避免直到对象的生存期到期后,POST的结果才不会反映在对请求的响应中。


4

Mark Nottingham分析了何时可以缓存POST响应。请注意,要利用缓存的后续请求必须是GET或HEAD请求。另请参见http语义

POST不处理已标识状态的表示形式(占100个状态的99倍)。当服务器通过设置与请求URI相同的Content-Location标头来表示此POST响应是其URI的表示形式时。发生这种情况时,POST响应就像是对相同URI的GET响应;可以缓存和重用它-但仅用于将来的GET请求。

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


3

如果它实际上并没有改变您网站上的数据,则应该是GET请求。即使是表单,您仍然可以将其设置为get请求。就像其他人指出的那样,虽然您可以缓存POST的结果,但是这在语义上是没有意义的,因为POST从定义上说就是在更改数据。


POST请求可能不会更改用于生成响应页面的任何数据,在这种情况下,缓存响应可能是有意义的。
David Z

David Z:当然,如果POST正在更改数据,则响应应该给出成功/失败的指示。并非完全需要,但我无法想到POST会更改数据且响应为静态的情况。
Morvael 2013年

6
如果参数数据太长,则GET请求将不适用于所有服务器,因此需要POST,尤其是如果源应在代码作者未配置的服务器上运行时。
Gogowitsch 2015年

@Gogowitsch非常真实,你会碰到一个414错误代码- stackoverflow.com/a/2891598/792238
悉达多

3

如果您想知道是否可以缓存发帖请求,并尝试研究该问题的答案,那么您可能不会成功。当搜索“缓存发布请求”时,第一个结果是这个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允许您将后续GET的POST请求缓存到同一资源”
  • “不,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请求”都会触发不同的计数(POST的HTTP缓存不起作用)。
  • 单击“触发器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请求是否应该缓存取决于上下文。

例如,您可以在基础查询为幂等的情况下使用POST查询Elasticsearch或GraphQL。在那些情况下,根据用例来缓存响应可能有意义,也可能没有意义。

在RESTful API中,POST请求通常会创建资源,因此不应进行缓存。这也是RFC对POST的理解,即它不是幂等操作。

GraphQL

如果您使用的是GraphQL,并且需要跨CDN和浏览器进行HTTP缓存,请考虑使用GET方法而不是POST来满足您的要求。请注意,不同的浏览器和CDN可能具有不同的URI长度限制,但作为面向外部生产GraphQL应用程序的最佳实践,操作安全列表(查询白名单)可以缩短URI。


-2

使用firefox 27.0和httpfox,在2014年5月19日,我看到了这一行:00:03:58.777 0.488 657(393)POST(缓存)文本/ html https://users.jackiszhp.info/S4UP

显然,post方法的响应已缓存,并且也位于https中。难以置信的!


-3

POST用于有状态的Ajax。返回POST的缓存响应会破坏通信通道和接收消息的副作用。这是非常非常糟糕。追踪也是一个真正的痛苦。强烈建议反对。

一个简单的例子是一条消息,作为副作用,它在当前周支付您的薪水10,000美元。您不想得到“确定,它成功了!” 上周缓存的页面返回。在现实世界中,其他更复杂的案例也会导致类似的欢闹。


3
这并不是一个真正的答案-POST用于各种各样的事情,有时有充分的理由希望缓存响应。
阿列克谢·莱文科夫'17
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.