É possível armazenar em cache métodos POST em HTTP?

152

Com semântica de cache muito simples: se os parâmetros forem os mesmos (e a URL for a mesma, é claro), será um sucesso. Isso é possível? Recomendado?

flybywire
fonte

Respostas:

93

O RFC 2616 correspondente na seção 9.5 (POST) permite o armazenamento em cache da resposta a uma mensagem POST, se você usar os cabeçalhos apropriados.

As respostas a esse método não podem ser armazenadas em cache, a menos que a resposta inclua os campos de cabeçalho Cache-Control ou Expir apropriados. No entanto, a resposta 303 (Consulte Outro) pode ser usada para direcionar o agente do usuário para recuperar um recurso armazenável em cache.

Observe que a mesma RFC afirma explicitamente na seção 13 (Armazenamento em cache em HTTP) que um cache deve invalidar a entidade correspondente após uma solicitação POST .

Alguns métodos HTTP DEVEM causar um cache para invalidar uma entidade. Essa é a entidade mencionada pelo Request-URI ou pelos cabeçalhos Location ou Content-Location (se presentes). Esses métodos são:

  - PUT
  - DELETE
  - POST

Não está claro para mim como essas especificações podem permitir um cache significativo.

Isso também é refletido e esclarecido na RFC 7231 (Seção 4.3.3.), Que obsoleta a RFC 2616.

As respostas às solicitações POST só podem ser armazenadas em cache quando incluem
informações explícitas de atualização (consulte a Seção 4.2.1 de [RFC7234]).
No entanto, o armazenamento em cache do POST não é amplamente implementado. Para casos em que um servidor de origem deseja que o cliente possa armazenar em cache o resultado de um POST de uma maneira que possa ser reutilizada por um GET posterior, o servidor de origem PODE enviar uma resposta 200 (OK) contendo o resultado e um Local de Conteúdo campo de cabeçalho que possui o mesmo valor que o URI de solicitação efetiva do POST (Seção 3.1.4.2).

De acordo com isso, o resultado de um POST em cache (se essa capacidade é indicada pelo servidor) pode ser usado posteriormente como resultado de uma solicitação GET para o mesmo URI.

Diomidis Spinellis
fonte
1
Esta seção se aplica a um cache intermediário (como um servidor proxy de armazenamento em cache), não ao servidor de origem.
David Z
2
O servidor de origem é um intermediário entre HTTP e o aplicativo que manipula as solicitações POST. O aplicativo está além do limite HTTP e pode fazer o que bem entender. Se o cache faz sentido para uma solicitação POST específica, é livre para armazenar em cache, tanto quanto o sistema operacional pode armazenar em cache as solicitações de disco.
Diomidis Spinellis 09/03/09
2
Diomidis, sua afirmação de que o cache de solicitações POST não seria HTTP, está errado. Consulte a resposta do reBoot para obter detalhes. Não é muito útil ter a resposta errada no topo, mas é assim que a democracia funciona. Se você concorda com o reBoot, seria bom se você corrigisse sua resposta.
Eugene Beresovsky
2
Eugene, podemos concordar que a) o POST deve invalidar a entidade armazenada em cache (por seção 13.10), de modo que, por exemplo, um GET subsequente deve buscar uma cópia fersh eb) que a resposta do POST possa ser armazenada em cache (por seção 9.5), de modo que por exemplo um POST subsequente pode receber a mesma resposta?
Diomidis Spinellis
3
Isso está sendo esclarecido pelo HTTPbis; consulte mnot.net/blog/2012/09/24/caching_POST para obter um resumo.
Mark Nottingham
68

De acordo com a Seção 9.5 da RFC 2616:

"As respostas ao método POST não são armazenáveis ​​em cache, a menos que a resposta inclua campos de cabeçalho de Controle de Cache ou Expira apropriados"

Portanto, SIM, você pode armazenar em cache a resposta da solicitação POST, mas apenas se ela chegar com os cabeçalhos apropriados. Na maioria dos casos, você não deseja armazenar em cache a resposta. Mas em alguns casos - como se você não estiver salvando nenhum dado no servidor - é totalmente apropriado.

Observe que, no entanto, muitos navegadores, incluindo o Firefox 3.0.10 atual, não armazenam em cache a resposta do POST, independentemente dos cabeçalhos. O IE se comporta de maneira mais inteligente a esse respeito.

Agora, quero esclarecer algumas confusões aqui sobre a RFC 2616 S. 13.10. O método POST em um URI não "invalida o recurso para armazenamento em cache", como alguns declararam aqui. Ele torna uma versão em cache anterior desse URI obsoleta, mesmo se seus cabeçalhos de controle de cache indicassem atualização de maior duração.


fonte
2
+1 reBoot, obrigado por explicar o problema dos cabeçalhos e também por corrigir as declarações errôneas relacionadas à 13.10. Surpreendente essas respostas erradas receberam tantos votos positivos.
Eugene Beresovsky
3
Qual é a diferença entre "invalidar o recurso para armazenamento em cache" e "tornar uma versão em cache do URI obsoleta"? Você está dizendo que o servidor tem permissão para armazenar em cache uma resposta POST, mas os clientes podem não?
Gili
1
"tornar uma versão em cache do URI obsoleta" se aplica onde você usa o mesmo URI para GETe POSTsolicitações. Se você é um cache entre o cliente e o servidor, vê GET /fooe armazena em cache a resposta. Em seguida, você vê POST /fooque é necessário invalidar a resposta em cache, GET /foomesmo que a POSTresposta não inclua nenhum cabeçalho de controle de cache porque eles são o mesmo URI , portanto, o próximo GET /footerá que revalidar, mesmo que os cabeçalhos originais indicassem que o cache ainda estaria. ao vivo (se você não tinha visto a POST /foopedido)
Stephen Connolly
But in some cases - such as if you are not saving any data on the server - it's entirely appropriate.. Qual é o sentido de uma API POST em primeiro lugar?
Siddhartha
33

No geral:

Basicamente, o POST não é uma operação idempotente . Portanto, você não pode usá-lo para armazenar em cache. GET deve ser uma operação idempotente, por isso é comumente usada para armazenamento em cache.

Consulte a seção 9.1 do HTTP 1.1 RFC 2616 S. 9.1 .

Diferente da semântica do método GET:

O próprio método POST é destinado semanticamente a publicar algo em um recurso. O POST não pode ser armazenado em cache porque se você fizer algo uma vez ou duas vezes e três vezes, estará alterando o recurso do servidor a cada vez. Cada solicitação é importante e deve ser entregue ao servidor.

O próprio método PUT destina-se semanticamente a colocar ou criar um recurso. É uma operação idempotente, mas não será usada para armazenar em cache porque um DELETE pode ter ocorrido nesse meio tempo.

O próprio método DELETE destina-se semanticamente a excluir um recurso. É uma operação idempotente, mas não será usada para armazenar em cache, porque uma PUT pode ter ocorrido nesse meio tempo.

Em relação ao cache do lado do cliente:

Um navegador da Web sempre encaminhará sua solicitação, mesmo que tenha uma resposta de uma operação anterior do POST. Por exemplo, você pode enviar e-mails com o gmail com alguns dias de diferença. Eles podem ser o mesmo assunto e corpo, mas os dois e-mails devem ser enviados.

Em relação ao cache do proxy:

Um servidor HTTP proxy que encaminha sua mensagem para o servidor nunca armazenaria em cache nada além de uma solicitação GET ou HEAD.

Em relação ao cache do servidor:

Um servidor por padrão não processa automaticamente uma solicitação POST, verificando seu cache. Mas é claro que uma solicitação POST pode ser enviada para o seu aplicativo ou suplemento e você pode ter seu próprio cache do qual lê quando os parâmetros são os mesmos.

Invalidando um recurso:

A verificação do HTTP 1.1 RFC 2616 S. 13.10 mostra que o método POST deve invalidar o recurso para armazenamento em cache.

Brian R. Bondy
fonte
9
"Basicamente, o POST não é uma operação idempotente. Portanto, você não pode usá-lo para armazenar em cache." Isso está errado, e realmente não faz sentido, veja a resposta do reBoot para obter detalhes. Infelizmente, não posso votar ainda, caso contrário teria.
Eugene Beresovsky
1
Eugene: Eu mudei "não é" para "não posso".
Brian R. Bondy
1
Obrigado Brian, isso soa melhor. Meu problema com o "POST não idemp. -> não pode ser armazenado em cache" foi - e eu não deixei isso claro o suficiente - mesmo que uma operação não seja idempotente e não signifique que não é armazenável em cache. Eu acho que a pergunta é: você está olhando do ponto de vista do servidor, que oferece os dados e conhece sua semântica, ou você está olhando pelo lado do recebimento (seja um proxy de cache etc. ou um cliente) . Se for o cliente / proxy proxy, eu concordo totalmente com o seu post. Se for o servidor pov, se o servidor disser: "o cliente pode armazenar em cache", o cliente pode armazenar em cache.
Eugene Beresovsky
1
Eugene: Se faz diferença se é chamado uma ou cinco vezes, como se você estivesse postando uma mensagem em uma lista, então você deseja que a chamada atinja o servidor 5 vezes, certo? E você não deseja armazená-lo em cache para que não atinja o servidor, certo? Porque existem efeitos colaterais que são importantes.
Brian R. Bondy
[continuação] No entanto, ainda não decidi se o servidor realmente enviará o cache, permitindo apenas o cabeçalho de expiração, se a operação for idempotente. No entanto, faz sentido, eu acho. [acabei de ver sua resposta]: Concordo, então acho que me decidi: o servidor só deve sinalizar cache em caso de idempotência - e isso também pode ser um POST, especialmente considerando a necessidade de substituição do método X-HTTP no alguns casos.
Eugene Beresovsky
6

Se você armazenar em cache uma resposta POST, ela deverá estar na direção do aplicativo da web. É isso que significa "As respostas a esse método não podem ser alteradas, a menos que a resposta inclua os campos apropriados de cabeçalho Control-Cache ou Expires".

Pode-se supor com segurança que o aplicativo, que sabe se os resultados de um POST são idempotentes, decide se deve ou não anexar os cabeçalhos de controle de cache necessários e adequados. Se os cabeçalhos que sugerem que o cache é permitido estão presentes, o aplicativo informa que o POST é, na realidade, um super GET; que o uso do POST era necessário apenas devido à quantidade de dados desnecessários e irrelevantes (para o uso do URI como chave de cache) necessários para executar a operação idempotente.

Os GETs a seguir podem ser veiculados no cache sob essa suposição.

Um aplicativo que falha ao anexar os cabeçalhos necessários e corretos para diferenciar entre respostas POST cachable e não cachable é responsável por quaisquer resultados de armazenamento em cache inválidos.

Dito isto, cada POST que atinge o cache requer validação usando cabeçalhos condicionais. Isso é necessário para atualizar o conteúdo do cache para evitar que os resultados de um POST não sejam refletidos nas respostas às solicitações até que a vida útil do objeto expire.

JohnS
fonte
4

Mark Nottingham analisou quando é possível armazenar em cache a resposta de um POST. Observe que os pedidos subsequentes que desejam tirar proveito do armazenamento em cache devem ser pedidos GET ou HEAD. Veja também semântica http

Os POSTs não lidam com representações do estado identificado, 99 vezes em 100. No entanto, há um caso em que ocorre; quando o servidor faz o possível para dizer que essa resposta POST é uma representação de seu URI, configurando um cabeçalho de local do conteúdo igual ao URI da solicitação. Quando isso acontece, a resposta POST é como uma resposta GET para o mesmo URI; pode ser armazenado em cache e reutilizado - mas apenas para solicitações GET futuras.

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

dschulten
fonte
4

Se você está se perguntando se pode armazenar em cache uma solicitação de postagem e tenta pesquisar uma resposta para essa pergunta, provavelmente não terá êxito. Ao pesquisar "solicitação de postagem de cache", o primeiro resultado é essa pergunta do StackOverflow.

As respostas são uma mistura confusa de como o cache deve funcionar, como o cache funciona de acordo com o RFC, como o cache deve funcionar de acordo com o RFC e como o cache funciona na prática. Vamos começar com o RFC, mostrar uma demonstração de como o navegador realmente funciona e depois falar sobre CDNs, GraphQL e outras áreas de preocupação.

RFC 2616

De acordo com o RFC, os pedidos POST devem invalidar o 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

Esse idioma sugere que as solicitações POST não podem ser armazenadas em cache, mas isso não é verdade (neste caso). O cache é invalidado apenas para dados armazenados anteriormente. O RFC (parece) esclarece explicitamente que sim, você pode armazenar em cache POSTsolicitações:

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.

Apesar desse idioma, a configuração de Cache-Controlnão deve armazenar em cache POSTsolicitações subsequentes no mesmo recurso. POSTsolicitações devem ser enviadas ao servidor:

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.

Como isso faz sentido? Bem, você não está armazenando em cache a POSTsolicitação, está armazenando em cache o recurso.

O corpo da resposta POST pode ser armazenado em cache apenas para solicitações GET subsequentes para o mesmo recurso. Defina o LocationouContent-Location cabeçalho na resposta POST para comunicar qual recurso o corpo representa. Portanto, a única maneira tecnicamente válida de armazenar em cache uma solicitação POST é para GETs subsequentes no mesmo recurso.

A resposta correta é ambas:

  • "sim, o RFC permite que você armazene em cache solicitações POST para GETs subsequentes no mesmo recurso"
  • "não, o RFC não permite que você armazene em cache solicitações POST para POSTs subsequentes, porque o POST não é idempotente e deve ser gravado no servidor"

Embora o RFC permita solicitações de cache para o mesmo recurso, na prática, navegadores e CDNs não implementam esse comportamento e não permitem o cache de solicitações POST.

Fontes:

Demonstração de comportamento do navegador

Dado o seguinte exemplo de aplicativo 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!')
    })

E dado o seguinte exemplo de página da web (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>

Instale o NodeJS, Express e inicie o aplicativo JavaScript. Abra a página da web no seu navegador. Experimente alguns cenários diferentes para testar o comportamento do navegador:

  • Clicar em "Trigger GET request" exibe a mesma "contagem" todas as vezes (o cache HTTP funciona).
  • Clicar em "Trigger request POST" aciona uma contagem diferente sempre (o cache HTTP para POST não funciona).
  • Clicar em "Trigger GET request", "Trigger POST request" e "Trigger GET request" mostra que a solicitação POST invalida o cache da solicitação GET.
  • Clicar em "Trigger POST request" e "Trigger GET request" mostra que os navegadores não armazenarão em cache solicitações POST para solicitações GET subsequentes, mesmo que permitidas pelo RFC.

Isso mostra que, embora você possa definir os cabeçalhos Cache-Controle Content-Locationresposta, não há como tornar um navegador em cache uma solicitação HTTP POST.

Eu tenho que seguir a RFC?

O comportamento do navegador não é configurável, mas se você não é um navegador, não está necessariamente vinculado às regras da RFC.

Se você estiver escrevendo o código do aplicativo, não há nada que impeça o armazenamento em cache de solicitações POST explicitamente (pseudocódigo):

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 e gateways também não precisam necessariamente seguir o RFC. Por exemplo, se você usar o Fastly como CDN, o Fastly permitirá que você escreva lógica VCL personalizada para armazenar em cache solicitações POST .

Devo armazenar em cache solicitações POST?

Se sua solicitação POST deve ser armazenada em cache ou não, depende do contexto.

Por exemplo, você pode consultar o Elasticsearch ou o GraphQL usando POST, onde sua consulta subjacente é idempotente. Nesses casos, pode ou não fazer sentido armazenar em cache a resposta, dependendo do caso de uso.

Em uma API RESTful, as solicitações POST geralmente criam um recurso e não devem ser armazenadas em cache. Este também é o entendimento da RFC do POST de que não é uma operação idempotente.

GraphQL

Se você estiver usando o GraphQL e precisar de cache HTTP entre CDNs e navegadores, considere se o envio de consultas usando o método GET atende aos seus requisitos em vez do POST . Como uma ressalva, navegadores e CDNs diferentes podem ter limites de comprimento de URI diferentes, mas a lista segura de operações (lista de permissões), como uma prática recomendada para aplicativos GraphQL de produção voltados para o exterior, pode reduzir os URIs.

timrs2998
fonte
3

Se algo que realmente não altera os dados do seu site, deve ser uma solicitação GET. Mesmo que seja um formulário, você ainda pode defini-lo como uma solicitação de obtenção. Enquanto, como outros apontam, você pode armazenar em cache os resultados de um POST, isso não faria sentido semântico porque, por definição, um POST está alterando os dados.

Kibbee
fonte
A solicitação POST pode não estar alterando nenhum dado usado para gerar a página de resposta; nesse caso, pode fazer sentido armazenar em cache a resposta.
David Z
David Z: Certamente, se um POST está alterando dados, a resposta deve dar alguma indicação de sucesso / fracasso. Não é exatamente necessário, mas não consigo pensar em uma situação em que um POST altere os dados e a resposta seja estática.
Morvael 25/10
6
Se os dados do parâmetro forem muito longos, uma solicitação GET não funcionará com todos os servidores, portanto, o POST será necessário, especialmente se a origem for executada em servidores que o autor do código não configura.
Gogowitsch 06/04
@Gogowitsch é bem verdade, você encontrará um código de erro 414 - stackoverflow.com/a/2891598/792238
Siddhartha
-2

Com o firefox 27.0 e com o httpfox, em 19 de maio de 2014, vi uma linha disso: 00: 03: 58.777 0.488 657 (393) POST (Cache) text / html https://users.jackiszhp.info/S4UP

Claramente, a resposta de um método de postagem é armazenada em cache e também está em https. Inacreditável!

user1462586
fonte
-3

POST é usado no Ajax com monitoração de estado. Retornar uma resposta em cache para um POST anula o canal de comunicação e os efeitos colaterais de receber uma mensagem. Isso é muito, muito ruim. Também é uma dor real rastrear. Altamente recomendado contra.

Um exemplo trivial seria uma mensagem que, como efeito colateral, paga ao seu salário US $ 10.000 na semana atual. Você NÃO deseja obter o "OK, passou!" página de volta que foi armazenada em cache na semana passada. Outros casos reais mais complexos resultam em hilaridade semelhante.

DragonLord
fonte
3
Não é realmente uma resposta - o POST é usado para todos os tipos de coisas e, às vezes, há razões válidas para desejar armazenar em cache a resposta.
Alexei Levenkov 22/09