Можно ли кешировать POST-методы в HTTP?

152

С очень простой семантикой кэширования: если параметры одинаковы (и URL, конечно, одинаков), то это хит. Это возможно? Рекомендуемые?

ЭЛЕКТРОДИСТАНЦИОННАЯ СИСТЕМА УПРАВЛЕНИЯ
источник

Ответы:

93

Соответствующий RFC 2616 в разделе 9.5 (POST) позволяет кэшировать ответ на сообщение 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 кешируются только в том случае, если они содержат
явную информацию о свежести (см. Раздел 4.2.1 [RFC7234]).
Однако POST-кеширование широко не применяется. В случаях, когда исходный сервер желает, чтобы клиент мог кэшировать результат POST таким образом, который может быть повторно использован последующим GET, исходный сервер МОЖЕТ отправить ответ 200 (ОК), содержащий результат и Content-Location поле заголовка, которое имеет то же значение, что и эффективный URI запроса POST (раздел 3.1.4.2).

В соответствии с этим, результат кэшированного POST (если эта способность указана сервером) может впоследствии использоваться как результат запроса GET для того же URI.

Диомидис Спинеллис
источник
1
Этот раздел относится к промежуточному кешу (например, кеширующему прокси-серверу), а не к исходному серверу.
Дэвид З
2
Исходный сервер является посредником между HTTP и приложением, которое обрабатывает запросы POST. Приложение выходит за пределы HTTP и может делать все, что пожелает. Если кеширование имеет смысл для определенного запроса POST, его можно кэшировать столько, сколько ОС может кэшировать запросы к диску.
Диомидис Спинеллис
2
Diomidis, ваше утверждение, что кэширование POST-запросов не будет HTTP, неверно. Пожалуйста, смотрите ответ reBoot для деталей. Не очень полезно показывать неправильный ответ наверху, но так работает демократия. Если вы согласны с reBoot, было бы хорошо, если бы вы исправили свой ответ.
Евгений Бересовский
2
Евгений, можем ли мы согласиться с тем, что a) POST должен сделать недействительной кэшированную сущность (в соответствии с разделом 13.10), так что, например, последующий GET должен получить копию fersh и b) что ответ POST может быть кэширован (в соответствии с разделом 9.5), так что, например, последующий POST может получить такой же ответ?
Диомидис Спинеллис
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. Метод POST для URI не «лишает законной силы ресурс для кэширования», как некоторые из них заявили здесь. Это делает ранее кэшированную версию этого URI устаревшей, даже если его заголовки управления кешем указывали свежесть большей продолжительности.


источник
2
+1 reBoot, спасибо за объяснение проблемы с заголовками, а также за исправление ошибочных утверждений относительно 13.10. Удивительные эти неправильные ответы получили так много положительных голосов.
Евгений Бересовский
3
В чем разница между «аннулировать ресурс для кэширования» и «сделать кэшированную версию устаревшего URI»? Вы говорите, что серверу разрешено кэшировать POST-ответ, а клиентам - нет?
Гили
1
«Создание кэшированной версии URI устаревшего» применяется, когда вы используете один и тот же URI для запросов GETи POSTзапросов. Если вы находитесь в кеш-памяти между клиентом и сервером, вы видите GET /fooи кешируете ответ. Затем вы увидите, что от POST /fooвас требуется сделать недействительным кэшированный ответ, GET /fooдаже если в POSTответ не включены какие-либо заголовки элемента управления кэшем, поскольку они представляют собой один и тот же URI , поэтому следующий GET /fooдолжен будет выполнить повторную проверку, даже если исходные заголовки указывают, что кэш все еще будет в прямом эфире (если вы не видели POST /fooпросьбу)
Стивен Коннолли
But in some cases - such as if you are not saving any data on the server - it's entirely appropriate., Какой смысл в таком API POST?
Сиддхартха
33

В общем и целом:

В основном POST не является идемпотентной операцией . Таким образом, вы не можете использовать его для кэширования. GET должен быть идемпотентной операцией, поэтому он обычно используется для кэширования.

Пожалуйста, смотрите раздел 9.1 HTTP 1.1 RFC 2616 S. 9.1 .

Кроме семантики метода GET:

Сам метод POST семантически предназначен для публикации чего-либо на ресурсе. POST не может быть кэширован, потому что если вы делаете что-то один раз против двух против трех раз, то вы каждый раз изменяете ресурс сервера. Каждый запрос имеет значение и должен быть доставлен на сервер.

Сам метод PUT семантически предназначен для размещения или создания ресурса. Это идемпотентная операция, но она не будет использоваться для кэширования, поскольку в это время могла произойти УДАЛЕНИЕ.

Сам метод DELETE семантически предназначен для удаления ресурса. Это идемпотентная операция, но она не будет использоваться для кэширования, поскольку в это время мог произойти PUT.

Что касается кэширования на стороне клиента:

Веб-браузер всегда будет перенаправлять ваш запрос, даже если у него есть ответ от предыдущей операции POST. Например, вы можете отправлять электронные письма с Gmail через пару дней. Они могут иметь одинаковую тему и текст, но оба сообщения должны быть отправлены.

Что касается кеширования прокси:

Прокси-сервер HTTP, который пересылает ваше сообщение на сервер, никогда не будет кэшировать ничего, кроме запроса GET или HEAD.

Что касается кэширования сервера:

Сервер по умолчанию не будет автоматически обрабатывать запрос POST, проверяя свой кэш. Но, конечно, запрос POST может быть отправлен в ваше приложение или надстройку, и вы можете иметь свой собственный кеш, который вы читаете, когда параметры совпадают.

Аннулирование ресурса:

Проверка HTTP 1.1 RFC 2616 S. 13.10 показывает, что метод POST должен сделать недействительным ресурс для кэширования.

Брайан Р. Бонди
источник
10
«По сути, POST не является идемпотентной операцией. Поэтому вы не можете использовать его для кэширования». Это просто неправильно, и это на самом деле не имеет смысла, подробности смотрите в ответе reBoot. К сожалению, я пока не могу понизить голос, иначе я бы это сделал.
Евгений Бересовский
1
Евгений: Я изменил «нет» на «не могу».
Брайан Р. Бонди
1
Спасибо Брайан, это звучит лучше. Моя проблема с вашим "POST not idemp. -> не может быть кэширована", хотя была - и я не сделал этого достаточно ясно - даже если операция не идемпотентна, что не означает, что она не кэшируется. Я предполагаю, что вопрос заключается в том, смотрите ли вы на это с точки зрения сервера, который предлагает данные и знает их семантику, или вы смотрите на них со стороны получателя (будь то прокси-сервер кэширования и т. Д. Или клиент) , Если это клиент / прокси POV, я полностью согласен с вашим постом. Если это сервер pov, если сервер говорит: «клиент может кэшировать», то клиент может кэшировать.
Евгений Бересовский
1
Евгений: Если имеет значение, будет ли он вызываться один или 5 раз, например, если вы отправляете сообщение в список, то вы хотите, чтобы этот вызов попадал на сервер 5 раз, верно? И вы не хотите его кэшировать, чтобы он не попал на сервер, верно? Потому что есть побочные эффекты, которые важны.
Брайан Р. Бонди
[продолжение] Однако я не решил, должен ли сервер действительно отправлять разрешающий кеш заголовок, истекающий ТОЛЬКО, если операция идемпотентна. Я думаю, это имеет смысл. [только что увидел ваш ответ]: Согласен, поэтому я думаю, что решил: сервер должен сигнализировать только о кешируемости в случае идемпотентности - и это может быть также POST, особенно учитывая необходимость использования X-HTTP-Method-Override в некоторые случаи.
Евгений Бересовский
6

Если вы кешируете POST-ответ, он должен быть в направлении веб-приложения. Это то, что подразумевается под «ответами на этот метод, которые нельзя кэшировать, если ответ не включает в себя соответствующие поля заголовка Cache-Control или Expires».

Можно смело предположить, что приложение, которое знает, являются ли результаты POST идемпотентными, решает, добавлять ли необходимые и правильные заголовки управления кэшем. Если присутствуют заголовки, предлагающие разрешить кэширование, приложение сообщает вам, что POST на самом деле является супер-GET; что использование POST требовалось только из-за количества ненужных и не относящихся к делу (из-за использования URI в качестве ключа кэша) данных, необходимых для выполнения идемпотентной операции.

Следующие GET могут быть поданы из кэша в соответствии с этим предположением.

Приложение, которое не может прикрепить необходимые и правильные заголовки для разграничения между кэшируемыми и не кэшируемыми ответами POST, является ошибочным для любых неверных результатов кэширования.

Тем не менее, каждый POST, который попадает в кэш, требует проверки с использованием условных заголовков. Это необходимо для обновления содержимого кэша, чтобы результаты POST не отражались в ответах на запросы до тех пор, пока не истечет время жизни объекта.

JohnS
источник
4

Марк Ноттингем проанализировал, когда возможно кэшировать ответ POST. Обратите внимание, что последующие запросы, которые хотят использовать преимущества кэширования, должны быть запросами GET или HEAD. Смотрите также http семантику

POST не имеют дело с представлениями идентифицированного состояния, 99 раз из 100. Однако, есть один случай, когда это происходит; когда сервер делает все возможное, чтобы сказать, что этот ответ POST является представлением его URI, путем установки заголовка Content-Location, который совпадает с URI запроса. Когда это происходит, ответ POST подобен ответу GET на тот же URI; его можно кэшировать и использовать повторно, но только для будущих запросов GET.

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

dschulten
источник
4

Если вам интересно, можете ли вы кешировать почтовый запрос и попытаться найти ответ на этот вопрос, скорее всего, у вас ничего не получится. При поиске "запроса кеша" первым результатом является вопрос 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 к тому же ресурсу. Установите заголовок Locationor Content-Locationв ответе POST, чтобы указать, какой ресурс представляет тело. Таким образом, единственный технически верный способ кэширования POST-запроса - последующие GET для того же ресурса.

Правильный ответ:

  • «да, RFC позволяет вам кэшировать POST-запросы для последующих GET к тому же ресурсу»
  • «Нет, 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» каждый раз запускает различное количество (HTTP-кэширование для POST не работает).
  • Нажатие «Запрос GET триггера», «Запрос POST триггера» и «Запрос GET триггера» показывает, что запрос POST делает недействительным кэш запроса GET.
  • Если щелкнуть «Trigger POST request», а затем «Trigger GET request», браузеры не будут кэшировать POST-запросы для последующих GET-запросов, даже если это разрешено RFC.

Это показывает, что, хотя вы можете установить заголовки Cache-Controlи Content-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-запрос кэшироваться или нет, зависит от контекста.

Например, вы можете запросить Elasticsearch или GraphQL, используя POST, если ваш базовый запрос идемпотентен. В этих случаях может иметь или не иметь смысла кэшировать ответ в зависимости от варианта использования.

В RESTful API запросы POST обычно создают ресурс и не должны кэшироваться. Это также понимание RFC POST, что это не идемпотентная операция.

GraphQL

Если вы используете GraphQL и вам требуется HTTP-кэширование в CDN и браузерах, подумайте, соответствует ли отправка запросов методом GET вашим требованиям вместо POST . В качестве предостережения, разные браузеры и CDN могут иметь разные ограничения длины URI, но списки надежных рабочих мест (белый список запросов), как лучший метод для внешних приложений GraphQL, могут сократить URI.

timrs2998
источник
3

Если это то, что на самом деле не меняет данные на вашем сайте, это должен быть запрос GET. Даже если это форма, вы все равно можете установить ее как запрос на получение. Хотя, как отмечают другие, вы можете кэшировать результаты POST, это не будет иметь смысловой смысл, потому что POST по определению меняет данные.

Kibbee
источник
Запрос POST может не изменять какие-либо данные, используемые для создания страницы ответа, и в этом случае может иметь смысл кэшировать ответ.
Дэвид Z
Дэвид З: Конечно, если POST изменяет данные, тогда в ответе должны быть некоторые признаки успеха / неудачи. Точно не требуется, но я не могу вспомнить ситуацию, когда POST изменил бы данные, и ответ был бы статическим.
Морваэль
6
Если данные параметра слишком длинные, запрос GET не будет работать со всеми серверами, поэтому POST необходим, особенно если источник должен работать на серверах, которые автор кода не настраивает.
Гогович
@Gogowitsch очень верно, вы столкнетесь с кодом ошибки 414 - stackoverflow.com/a/2891598/792238
Сиддхартха
-2

В Firefox 27.0 и httpfox 19 мая 2014 года я увидел одну строчку этого: 00: 03: 58.777 0,488 657 (393) POST (кэш) text / html https://users.jackiszhp.info/S4UP

Очевидно, что ответ метода post кэшируется, и он также находится в https. Невероятно!

user1462586
источник
-3

POST используется в Ajax с сохранением состояния. Возврат кэшированного ответа для POST разрушает канал связи и побочные эффекты при получении сообщения. Это очень, очень плохо. Это также настоящая боль, чтобы выследить. Настоятельно рекомендуется против.

Тривиальным примером может служить сообщение о том, что в качестве побочного эффекта выплачивается ваша зарплата в размере 10 000 долларов США на текущей неделе. Вы не хотите, чтобы получить "ОК, это прошло!" страница назад, которая была кеширована на прошлой неделе. Другие, более сложные случаи из реальной жизни приводят к аналогичной веселости.

Dragonlord
источник
3
Не совсем ответ - POST используется для всех видов вещей, и иногда есть веские причины желать кэшировать ответ.
Алексей Левенков