¿Es posible almacenar en caché los métodos POST en HTTP?

152

Con una semántica de almacenamiento en caché muy simple: si los parámetros son los mismos (y la URL es la misma, por supuesto), entonces es un éxito. ¿Es eso posible? ¿Recomendado?

flybywire
fuente

Respuestas:

93

El RFC 2616 correspondiente en la sección 9.5 (POST) permite el almacenamiento en caché de la respuesta a un mensaje POST, si utiliza los encabezados apropiados.

Las respuestas a este método no se pueden almacenar en caché, a menos que la respuesta incluya los campos de encabezado Cache-Control o Expires apropiados. Sin embargo, la respuesta 303 (Ver otros) se puede usar para indicar al agente de usuario que recupere un recurso almacenable en caché.

Tenga en cuenta que el mismo RFC establece explícitamente en la sección 13 (Almacenamiento en caché en HTTP) que un caché debe invalidar la entidad correspondiente después de una solicitud POST .

Algunos métodos HTTP DEBEN causar que un caché invalide una entidad. Esta es la entidad a la que hace referencia el URI de solicitud o los encabezados de ubicación o ubicación de contenido (si están presentes). Estos métodos son:

  - PUT
  - DELETE
  - POST

No me queda claro cómo estas especificaciones pueden permitir el almacenamiento en caché significativo.

Esto también se refleja y se aclara en RFC 7231 (Sección 4.3.3.), Que obsoleta RFC 2616.

Las respuestas a las solicitudes POST solo se pueden almacenar en caché cuando incluyen
información explícita de actualización (consulte la Sección 4.2.1 de [RFC7234]).
Sin embargo, el almacenamiento en caché POST no está ampliamente implementado. Para los casos en que un servidor de origen desea que el cliente pueda almacenar en caché el resultado de una POST de una manera que pueda ser reutilizada por un GET posterior, el servidor de origen PUEDE enviar una respuesta 200 (OK) que contenga el resultado y una ubicación de contenido campo de encabezado que tiene el mismo valor que el URI de solicitud efectiva de la POST (Sección 3.1.4.2).

De acuerdo con esto, el resultado de una POST en caché (si el servidor indica esta capacidad) puede utilizarse posteriormente como resultado de una solicitud GET para el mismo URI.

Diomidis Spinellis
fuente
1
Esta sección se aplica a un caché intermedio (como un servidor proxy de almacenamiento en caché), no al servidor de origen.
David Z
2
El servidor de origen es un intermediario entre HTTP y la aplicación que maneja las solicitudes POST. La aplicación está más allá del límite de HTTP y puede hacer lo que le plazca. Si el almacenamiento en caché tiene sentido para una solicitud POST específica, es libre de almacenar en caché, tanto como el SO puede almacenar en caché las solicitudes de disco.
Diomidis Spinellis
2
Diomidis, su afirmación de que el almacenamiento en caché de solicitudes POST no sería HTTP, está mal. Por favor, consulte la respuesta de reinicio para más detalles. No es muy útil que aparezca la respuesta incorrecta en la parte superior, pero así es como funciona la democracia. Si está de acuerdo con reiniciar, sería bueno que corrigiera su respuesta.
Eugene Beresovsky
2
Eugene, ¿podemos aceptar que a) POST debe invalidar la entidad en caché (según la sección 13.10), de modo que, por ejemplo, un GET posterior debe obtener una copia fersh yb) que la respuesta de POST se puede almacenar en caché (según la sección 9.5), de modo que eg un POST posterior puede recibir la misma respuesta?
Diomidis Spinellis
3
HTTPbis lo está aclarando; ver mnot.net/blog/2012/09/24/caching_POST para un resumen.
Mark Nottingham el
68

De acuerdo con RFC 2616 Sección 9.5:

"Las respuestas al método POST no se pueden almacenar en caché, A MENOS QUE la respuesta incluya los campos de encabezado Cache-Control apropiados o caduque".

Entonces, SÍ, puede almacenar en caché la respuesta de solicitud POST, pero solo si llega con los encabezados apropiados. En la mayoría de los casos, no desea almacenar en caché la respuesta. Pero en algunos casos, como si no está guardando ningún dato en el servidor, es completamente apropiado.

Tenga en cuenta que, sin embargo, muchos navegadores, incluido Firefox 3.0.10 actual, no almacenarán en caché la respuesta POST independientemente de los encabezados. IE se comporta de manera más inteligente a este respecto.

Ahora, quiero aclarar alguna confusión aquí con respecto a RFC 2616 S. 13.10. El método POST en un URI no "invalida el recurso para el almacenamiento en caché" como algunos han dicho aquí. Hace una versión previamente almacenada en caché de ese URI obsoleto, incluso si sus encabezados de control de caché indican frescura de mayor duración.


fuente
2
+1 reinicio, gracias por explicar el problema de los encabezados y también corregir las declaraciones erróneas con respecto a 13.10. Sorprendentemente esas respuestas incorrectas recibieron tantos votos positivos.
Eugene Beresovsky
3
¿Cuál es la diferencia entre "invalidar el recurso para el almacenamiento en caché" y "hacer una versión en caché del URI obsoleto"? ¿Está diciendo que el servidor puede almacenar en caché una respuesta POST pero que los clientes no pueden?
Gili
1
"crear una versión en caché del URI obsoleto" se aplica cuando usa el mismo URI para GETy las POSTsolicitudes. Si es un caché sentado entre el cliente y el servidor, verá GET /fooy almacenará en caché la respuesta. A continuación, verá POST /fooque debe invalidar la respuesta almacenada en caché GET /fooincluso si la POSTrespuesta no incluye ningún encabezado de control de caché porque son el mismo URI , por lo tanto, el siguiente GET /footendrá que volver a validar incluso si los encabezados originales indicaron que el caché aún estaría en vivo (si no hubiera visto la POST /foosolicitud)
Stephen Connolly
But in some cases - such as if you are not saving any data on the server - it's entirely appropriate.. ¿Cuál es el punto de tal POST API en primer lugar entonces?
Siddhartha
33

En general:

Básicamente, POST no es una operación idempotente . Por lo tanto, no puede usarlo para el almacenamiento en caché. GET debe ser una operación idempotente, por lo que se usa comúnmente para el almacenamiento en caché.

Consulte la sección 9.1 de HTTP 1.1 RFC 2616 S. 9.1 .

Aparte de la semántica del método GET:

El método POST en sí mismo tiene la intención semántica de publicar algo en un recurso. POST no se puede almacenar en caché porque si hace algo una vez o dos veces o tres veces, está alterando los recursos del servidor cada vez. Cada solicitud es importante y debe entregarse al servidor.

El método PUT en sí mismo tiene la intención semántica de colocar o crear un recurso. Es una operación idempotente, pero no se utilizará para el almacenamiento en caché porque, mientras tanto, podría haberse producido una ELIMINACIÓN.

El método DELETE mismo está semánticamente destinado a eliminar un recurso. Es una operación idempotente, pero no se utilizará para el almacenamiento en caché porque, mientras tanto, podría haber ocurrido un PUT.

Con respecto al almacenamiento en caché del lado del cliente:

Un navegador web siempre reenviará su solicitud, incluso si tiene una respuesta de una operación POST anterior. Por ejemplo, puede enviar correos electrónicos con gmail con un par de días de diferencia. Pueden ser el mismo asunto y cuerpo, pero se deben enviar ambos correos electrónicos.

Con respecto al almacenamiento en caché de proxy:

Un servidor HTTP proxy que reenvía su mensaje al servidor nunca almacenará en caché nada más que una solicitud GET o HEAD.

Con respecto al almacenamiento en caché del servidor:

Un servidor por defecto no manejaría automáticamente una solicitud POST mediante la comprobación de su caché. Pero, por supuesto, se puede enviar una solicitud POST a su aplicación o complemento y puede tener su propia memoria caché de la que leer cuando los parámetros son los mismos.

Invalidación de un recurso:

La comprobación de HTTP 1.1 RFC 2616 S. 13.10 muestra que el método POST debería invalidar el recurso para el almacenamiento en caché.

Brian R. Bondy
fuente
9
"Básicamente, POST no es una operación idempotente. Por lo tanto, no se puede usar para el almacenamiento en caché". Eso está mal, y realmente no tiene sentido, vea la respuesta de reinicio para más detalles. Desafortunadamente, aún no puedo votar a favor, de lo contrario lo habría hecho.
Eugene Beresovsky
1
Eugene: Cambié "no es" a "no puede".
Brian R. Bondy
1
Gracias Brian, eso suena mejor. Sin embargo, mi problema con su "POST no idemp. -> no se puede almacenar en caché" fue, y no lo hice lo suficientemente claro, a pesar de que una operación no es idempotente, lo que no significa que no se pueda almacenar en caché. Supongo que la pregunta es si lo está mirando desde el punto de vista del servidor, quién ofrece los datos y conoce su semántica, o lo está mirando desde el lado receptor (ya sea un proxy de almacenamiento en caché, etc. o un cliente) . Si es el punto de vista del cliente / proxy, estoy totalmente de acuerdo con tu publicación. Si es el servidor pov, si el servidor dice: "el cliente puede almacenar en caché", entonces el cliente puede almacenar en caché.
Eugene Beresovsky
1
Eugene: Si hace una diferencia si se llama una vez o 5 veces, como si está publicando un mensaje en una lista, entonces desea que esa llamada llegue al servidor 5 veces, ¿verdad? Y no quieres almacenarlo en caché para que no llegue al servidor, ¿verdad? Porque hay efectos secundarios que son importantes.
Brian R. Bondy
[continúa] Sin embargo, no he decidido si el servidor debería enviar un encabezado que expire la memoria caché SOLAMENTE si la operación es idempotente. Sin embargo, tiene sentido. [acabo de ver su respuesta]: De acuerdo, así que supongo que decidí: el servidor solo debería indicar la capacidad de almacenamiento en caché en caso de idempotencia, y eso también podría ser una POST, especialmente teniendo en cuenta la necesidad de X-HTTP-Method-Override en algunos casos.
Eugene Beresovsky
6

Si almacena en caché una respuesta POST, debe estar en la dirección de la aplicación web. Esto es lo que se entiende por "Las respuestas a este método no se pueden almacenar en caché, a menos que la respuesta incluya los campos de encabezado Cache-Control o Expires apropiados".

Se puede suponer con seguridad que la aplicación, que sabe si los resultados de un POST son o no idempotentes, decide si se deben adjuntar o no los encabezados de control de caché necesarios y adecuados. Si hay encabezados que sugieren que el almacenamiento en caché está permitido, la aplicación le indica que la POST es, en realidad, un super-GET; que el uso de POST solo era necesario debido a la cantidad de datos innecesarios e irrelevantes (para el uso del URI como clave de caché) necesarios para realizar la operación idempotente.

Los siguientes GET se pueden servir desde el caché bajo esta suposición.

Una aplicación que no puede adjuntar los encabezados correctos y necesarios para diferenciar entre las respuestas POST almacenables en caché y no almacenables en el caché tiene la culpa de los resultados de almacenamiento en caché no válidos.

Dicho esto, cada POST que llega al caché requiere validación mediante encabezados condicionales. Esto es necesario para actualizar el contenido de la memoria caché para evitar que los resultados de una POST no se reflejen en las respuestas a las solicitudes hasta después de que expire la vida útil del objeto.

JohnS
fuente
4

Mark Nottingham ha analizado cuándo es posible almacenar en caché la respuesta de un POST. Tenga en cuenta que las solicitudes posteriores que desean aprovechar el almacenamiento en caché deben ser solicitudes GET o HEAD. Ver también semántica http

Los POST no se ocupan de representaciones del estado identificado, 99 de cada 100 veces. Sin embargo, hay un caso en el que lo hace; cuando el servidor se sale de su camino para decir que esta respuesta POST es una representación de su URI, al establecer un encabezado Content-Location que es el mismo que el URI de solicitud. Cuando eso sucede, la respuesta POST es como una respuesta GET al mismo URI; se puede almacenar en caché y reutilizar, pero solo para futuras solicitudes GET.

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

dschulten
fuente
4

Si se pregunta si puede almacenar en caché una solicitud de publicación e intentar buscar una respuesta a esa pregunta, es probable que no tenga éxito. Al buscar "solicitud de publicación en caché", el primer resultado es esta pregunta de StackOverflow.

Las respuestas son una mezcla confusa de cómo debería funcionar el almacenamiento en caché, cómo funciona el almacenamiento en caché de acuerdo con el RFC, cómo debería funcionar el almacenamiento en caché de acuerdo con el RFC y cómo funciona el almacenamiento en caché en la práctica. Comencemos con el RFC, recorramos una demostración de cómo funciona realmente el navegador, luego hablemos de CDN, GraphQL y otras áreas de interés.

RFC 2616

Según el RFC, las solicitudes POST deben invalidar el caché:

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

Este lenguaje sugiere que las solicitudes POST no se pueden almacenar en caché, pero eso no es cierto (en este caso). El caché solo se invalida para los datos almacenados previamente. El RFC (parece) aclara explícitamente que sí, puede almacenar en caché las POSTsolicitudes:

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.

A pesar de este lenguaje, la configuración de Cache-Controlno debe almacenar en caché las POSTsolicitudes posteriores en el mismo recurso. POSTlas solicitudes deben enviarse al 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.

¿Cómo eso tiene sentido? Bueno, no está almacenando en caché la POSTsolicitud, está almacenando en caché el recurso.

El cuerpo de respuesta POST solo se puede almacenar en caché para solicitudes GET posteriores al mismo recurso. Establezca el encabezado Locationo Content-Locationen la respuesta POST para comunicar qué recurso representa el cuerpo. Por lo tanto, la única forma técnicamente válida de almacenar en caché una solicitud POST es para obtener GET posteriores en el mismo recurso.

La respuesta correcta es ambas:

  • "sí, el RFC le permite almacenar en caché las solicitudes POST para GET posteriores en el mismo recurso"
  • "no, el RFC no le permite almacenar en caché las solicitudes POST para POST posteriores porque POST no es idempotente y debe escribirse en el servidor"

Aunque el RFC permite el almacenamiento en caché de solicitudes para el mismo recurso, en la práctica, los navegadores y las CDN no implementan este comportamiento y no le permiten almacenar en caché las solicitudes POST.

Fuentes:

Demostración del comportamiento del navegador

Dado el siguiente ejemplo de aplicación 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!')
    })

Y dado el siguiente ejemplo de página 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 NodeJS, Express e inicie la aplicación JavaScript. Abra la página web en su navegador. Pruebe algunos escenarios diferentes para probar el comportamiento del navegador:

  • Al hacer clic en "Activar solicitud GET", se muestra el mismo "recuento" cada vez (el almacenamiento en caché HTTP funciona).
  • Al hacer clic en "Activar solicitud POST", se activa un recuento diferente cada vez (el almacenamiento en caché HTTP para POST no funciona).
  • Al hacer clic en "Activar solicitud GET", "Activar solicitud POST" y "Activar solicitud GET" muestra que la solicitud POST invalida el caché de la solicitud GET.
  • Al hacer clic en "Activar solicitud POST" y luego en "Activar solicitud GET", se muestra que los navegadores no almacenarán en caché las solicitudes POST para las solicitudes GET posteriores, aunque lo permita el RFC.

Esto muestra que, aunque puede configurar los encabezados Cache-Controly la Content-Locationrespuesta, no hay forma de hacer que el caché del navegador sea una solicitud HTTP POST.

¿Tengo que seguir el RFC?

El comportamiento del navegador no es configurable, pero si no eres un navegador, no estás obligado necesariamente por las reglas de la RFC.

Si está escribiendo código de aplicación, no hay nada que le impida almacenar en caché explícitamente las solicitudes POST (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
}

Las CDN, los servidores proxy y las puertas de enlace tampoco tienen que seguir necesariamente la RFC. Por ejemplo, si usa Fastly como su CDN, Fastly le permite escribir lógica VCL personalizada para almacenar en caché las solicitudes POST .

¿Debo almacenar en caché las solicitudes POST?

Si su solicitud POST debe almacenarse en caché o no depende del contexto.

Por ejemplo, puede consultar Elasticsearch o GraphQL usando POST donde su consulta subyacente es idempotente. En esos casos, puede o no tener sentido almacenar en caché la respuesta según el caso de uso.

En una API RESTful, las solicitudes POST generalmente crean un recurso y no deben almacenarse en caché. Esta es también la comprensión de RFC de POST de que no es una operación idempotente.

GraphQL

Si está utilizando GraphQL y requiere almacenamiento en caché HTTP en CDN y navegadores, considere si el envío de consultas utilizando el método GET cumple con sus requisitos en lugar de POST . Como advertencia, los diferentes navegadores y CDN pueden tener diferentes límites de longitud de URI, pero la operación segura (lista blanca de consultas), como una mejor práctica para aplicaciones GraphQL de producción externas, puede acortar los URI.

timrs2998
fuente
3

Si es algo que en realidad no cambia los datos en su sitio, debería ser una solicitud GET. Incluso si es un formulario, puede configurarlo como una solicitud de obtención. Si bien, como señalan otros, podría almacenar en caché los resultados de una POST, no tendría sentido semántico porque una POST por definición está cambiando los datos.

Kibbee
fuente
Es posible que la solicitud POST no esté cambiando los datos que se utilizan para generar la página de respuesta, en cuyo caso podría tener sentido almacenar en caché la respuesta.
David Z
David Z: Sin duda, si un POST está cambiando los datos, la respuesta debería dar alguna indicación de éxito / fracaso. No se requiere exactamente, pero no puedo pensar en una situación en la que una POST cambiaría los datos y la respuesta sería estática.
Morvael
66
Si los datos del parámetro son demasiado largos, una solicitud GET no funcionará con todos los servidores, por lo tanto, se necesita POST, especialmente si la fuente debe ejecutarse en servidores que el autor del código no configura.
Gogowitsch
@Gogowitsch muy cierto, te encontrarás con un código de error 414 - stackoverflow.com/a/2891598/792238
Siddhartha
-2

Con firefox 27.0 y con httpfox, el 19 de mayo de 2014, vi una línea de esto: 00: 03: 58.777 0.488 657 (393) POST (caché) texto / html https://users.jackiszhp.info/S4UP

Claramente, la respuesta de un método de publicación se almacena en caché, y también está en https. ¡Increíble!

usuario1462586
fuente
-3

POST se usa en Ajax con estado. Devolver una respuesta en caché para un POST derrota el canal de comunicación y los efectos secundarios de recibir un mensaje. Esto es muy, muy malo. También es un verdadero dolor rastrear. Muy recomendable contra.

Un ejemplo trivial sería un mensaje que, como efecto secundario, paga su salario $ 10,000 la semana actual. NO DESEA obtener el "OK, ¡pasó!" página anterior que se almacenó en caché la semana pasada. Otros casos más complejos del mundo real dan como resultado una hilaridad similar.

Señor de los Dragones
fuente
3
Realmente no es una respuesta: POST se usa para todo tipo de cosas y, a veces, hay razones válidas para desear almacenar la respuesta en caché.
Alexei Levenkov