可以在 HTTP 中缓存 POST 方法吗?

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

119270 次浏览

第9.5节(POST)中相应的 RFC 2616允许将 回应缓存到一个 POST 消息,如果您使用了适当的头的话。

对此方法的响应不可缓存,除非响应 包括适当的缓存控件或过期头字段, 303(请参阅其他)响应可用于指示用户代理 检索可缓存的资源。

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

一些 HTTP 方法必须导致 缓存使实体无效 所提述的实体 请求-URI,或由位置或 内容-位置标头(如果存在)。 这些方法是:

  - PUT
- DELETE
- POST

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

这一点在取消 RFC2616的 RFC 7231(第4.3.3节)中也得到了反映和进一步澄清。

对 POST 请求的响应只有在包含
明确的新鲜度资料(见[ RFC7234]第4.2.1节)。
然而,POST 缓存并没有得到广泛的实现。 对于原始服务器希望客户端能够 缓存 POST 的结果,以便以后可以重用 GET,原始服务器可能会发送一个包含 Result 和具有相同值的 Content-Location 标头字段 作为 POST 的有效请求 URI (3.1.4.2节)。

根据这一点,缓存 POST 的结果(如果服务器指示了这种能力)随后可以用作相同 URI 的 GET 请求的结果。

如果它实际上不会改变站点上的数据,那么它应该是一个 GET 请求。即使它是一个表单,您仍然可以将其设置为一个 get 请求。虽然像其他人指出的那样,您可以缓存 POST 的结果,但是这不会产生语义上的意义,因为根据定义,POST 正在更改数据。

总体情况:

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

根据 RFC 2616第9.5节:

”对 POST 方法的响应不是 可缓存的,除非响应 包括适当的缓存控件或 过期头字段”

因此,YES,您可以缓存 POST 请求响应,但只有当它带有适当的头时才能缓存。在大多数情况下,您不希望缓存响应。但是在某些情况下——例如,如果您不在服务器上保存任何数据——这是完全合适的。

注意,很多浏览器,包括当前的 Firefox3.0.10,都不会缓存 POST 响应,而不管头是什么。IE 在这方面表现得更聪明。

现在,我想澄清一些关于 RFC 2616 S. 13.10的混淆。URI 上的 POST 方法不会像这里所说的那样“使缓存的资源失效”。即使它的缓存控制头指示持续时间更长的新鲜度,它也会使该 URI 过时的以前缓存版本。

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

一个微不足道的例子是一条消息,作为一个副作用,支付你的薪水 $10,000当前一周。你不会想听到“好的,它通过了!”上周缓存的页面返回。其他更为复杂的现实世界案例也会导致类似的喜剧效果。

通过 Firefox 27.0和 httpfox,在2014年5月19日,我看到了这样一行: 00:03:58.7770.488657(393) POST (Cache) text/html < a href = “ https://users.jackiszhp.info/S4UP”rel = “ nofollow”> https://users.jackiszhp.info/s4up

显然,post 方法的响应是缓存的,而且它也在 https 中。 难以置信!

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

POST 不处理已识别状态的表示,100次中有99次。 但是,有一种情况是这样的: 当服务器从 它表示这个 POST 响应是它的 URI 的表示, 通过设置与请求相同的 Content-Location 标头 当这种情况发生时,POST 响应就像 GET 响应一样 它可以被缓存和重用——但只能用于将来 获取请求。

Https://www.mnot.net/blog/2012/09/24/caching_post.

如果您确实缓存了 POST 响应,那么它必须位于 Web 应用程序的方向。这就是所谓的“对此方法的响应不可缓存,除非响应包含适当的 Cache-Control 或 Exires 头字段。”

可以安全地假设应用程序知道 POST 的结果是否等幂,它决定是否附加必要的和适当的缓存控制标头。如果存在建议允许缓存的头,那么应用程序就会告诉你,POST 实际上是一个超级 GET; 使用 POST 只是因为执行幂等运算所需的大量不必要和不相关的数据(使用 URI 作为缓存键)。

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

如果应用程序未能附加必要的正确标头以区分可缓存和非可缓存的 POST 响应,那么任何无效的缓存结果都是错误的。

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

如果您想知道是否可以缓存一个 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 允许您将后续 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 应用程序。在浏览器中打开网页。尝试几种不同的方案来测试浏览器的行为:

  • 单击“ TriggerGET 请求”每次显示相同的“计数”(HTTP 缓存工作)。
  • 单击“ TriggerPOST 请求”每次都会触发不同的计数(针对 POST 的 HTTP 缓存不起作用)。
  • 单击“ TriggerGET 请求”、“ TriggerPOST 请求”和“ TriggerGET 请求”将显示 POST 请求使 GET 请求的缓存失效。
  • 单击“ Trigger POST request”然后单击“ Trigger GET request”显示浏览器不会缓存后续 GET 请求的 POST 请求,即使 RFC 允许这样做。

这表明,即使您可以设置 Cache-ControlContent-Location响应头,也没有办法使浏览器缓存成为 HTTPPOST 请求。

我必须遵循 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。例如,如果您使用“快速”作为 CDN,“快速”允许您将 自定义 VCL逻辑写入 缓存 POST 请求

我应该缓存 POST 请求吗?

是否应该缓存 POST 请求取决于上下文。

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

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

GraphQL

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