169

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

4

9 回答 9

105

如果您使用适当的标头,则第 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 缓存并未得到广泛实施。对于源服务器希望客户端能够缓存 POST 的结果以供以后的 GET 重用的情况,源服务器可以发送包含结果和 Content-Location 的 200(OK)响应与 POST 的有效请求 URI 具有相同值的标头字段(第 3.1.4.2 节)。

据此,缓存 POST 的结果(如果服务器指示此能力)随后可以用作对同一 URI 的 GET 请求的结果。

于 2009-03-09T12:50:42.277 回答
73

根据 RFC 2616 第 9.5 节:

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

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

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

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

于 2009-05-06T04:57:04.443 回答
34

总体:

基本上POST 不是幂等操作。所以你不能用它来缓存。GET 应该是幂等操作,所以常用于缓存。

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

除了 GET 方法的语义:

POST 方法本身在语义上意味着将某些内容发布到资源。POST 不能被缓存,因为如果你做某事一次 vs 两次 vs 三次,那么你每次都在改变服务器的资源。每个请求都很重要,应该传递给服务器。

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

DELETE 方法本身在语义上意味着删除资源。这是一个幂等操作,但不会用于缓存,因为同时可能发生了 PUT。

关于客户端缓存:

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

关于代理缓存:

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

关于服务器缓存:

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

使资源无效:

检查HTTP 1.1 RFC 2616 S. 13.10表明 POST 方法应该使缓存资源无效。

于 2009-03-09T13:04:28.703 回答
20

如果您想知道是否可以缓存发布请求,并尝试研究该问题的答案,您可能不会成功。搜索“缓存发布请求”时,第一个结果是这个 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 响应中设置Locationor标头以传达正文代表的资源。Content-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。

于 2020-01-02T20:13:21.223 回答
6

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

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

在此假设下,可以从缓存中提供以下 GET 服务。

未能附加必要且正确的标头以区分可缓存和不可缓存 POST 响应的应用程序对于任何无效缓存结果都是错误的。

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

于 2015-10-16T20:49:32.060 回答
5

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

于 2014-07-07T06:47:30.503 回答
3

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

于 2009-03-09T12:55:37.897 回答
-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中的。逆天!

于 2014-05-19T18:45:29.257 回答
-3

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

一个简单的例子是一条消息,作为副作用,当周支付你的工资 10,000 美元。你不想得到“好的,它通过了!” 上周缓存的页面返回。其他更复杂的真实案例也会引起类似的欢闹。

于 2013-06-11T20:57:09.957 回答