Czy można buforować metody POST w HTTP?

152

Z bardzo prostą semantyką buforowania: jeśli parametry są takie same (a adres URL jest taki sam), to jest to strzał w dziesiątkę. Czy to jest możliwe? Zalecana?

Lecieć jak po sznurku
źródło

Odpowiedzi:

93

Odpowiedni dokument RFC 2616 w sekcji 9.5 (POST) pozwala na buforowanie odpowiedzi na wiadomość POST, jeśli używasz odpowiednich nagłówków.

Odpowiedzi na tę metodę nie podlegają buforowaniu, chyba że odpowiedź zawiera odpowiednie pola nagłówka Cache-Control lub Expires. Jednak odpowiedź 303 (Zobacz inne) może służyć do skierowania agenta użytkownika do pobrania zasobu buforowanego.

Należy pamiętać, że sama RFC stwierdza wyraźnie w punkcie 13 (buforowanie w HTTP), że cache musi unieważnić odpowiednią jednostkę po POST żądanie .

Niektóre metody HTTP MUSZĄ powodować, że pamięć podręczna unieważnia jednostkę. Jest to jednostka, do której odwołuje się identyfikator URI żądania albo nagłówki Location lub Content-Location (jeśli są obecne). Te metody to:

  - PUT
  - DELETE
  - POST

Nie jest dla mnie jasne, w jaki sposób te specyfikacje mogą pozwolić na znaczące buforowanie.

Zostało to również odzwierciedlone i dokładniej wyjaśnione w RFC 7231 (sekcja 4.3.3.), Który zastępuje RFC 2616.

Odpowiedzi na żądania POST można
zapisywać w pamięci podręcznej tylko wtedy, gdy zawierają wyraźne informacje o aktualności (patrz sekcja 4.2.1 w [RFC7234]).
Jednak buforowanie POST nie jest powszechnie stosowane. W przypadkach, gdy serwer pochodzenia życzy sobie, aby klient mógł buforować wynik POST w sposób, który może być ponownie wykorzystany przez późniejszy GET, serwer pochodzenia MOŻE wysłać odpowiedź 200 (OK) zawierającą wynik i lokalizację zawartości pole nagłówka, które ma taką samą wartość jak efektywny identyfikator URI żądania POST (sekcja 3.1.4.2).

Zgodnie z tym, wynik buforowanego testu POST (jeśli serwer wskaże taką możliwość) może być następnie wykorzystany jako wynik żądania GET dla tego samego URI.

Diomidis Spinellis
źródło
1
Ta sekcja dotyczy pośredniej pamięci podręcznej (np. Buforującego serwera proxy), a nie serwera pochodzenia.
David Z,
2
Serwer pochodzenia jest brokerem między protokołem HTTP a aplikacją obsługującą żądania POST. Aplikacja znajduje się poza granicami HTTP i może robić, co tylko zechce. Jeśli buforowanie ma sens dla określonego żądania POST, można je buforować bezpłatnie, o ile system operacyjny może buforować żądania dysku.
Diomidis Spinellis,
2
Diomidis, twoje stwierdzenie, że buforowanie żądań POST nie byłoby HTTP, jest błędne. Aby uzyskać szczegółowe informacje, zapoznaj się z odpowiedzią reBoot. Nie warto mieć złej odpowiedzi na górze, ale tak właśnie działa demokracja. Jeśli zgadzasz się z reBoot, byłoby miło, gdybyś poprawił swoją odpowiedź.
Eugene Beresovsky
2
Eugene, czy możemy się zgodzić, że a) POST powinien unieważnić buforowaną jednostkę (zgodnie z sekcją 13.10), tak aby np. Późniejszy GET musiał pobrać kopię Fersh ib) aby odpowiedź POST mogła być buforowana (zgodnie z sekcją 9.5), aby np. kolejny POST może otrzymać tę samą odpowiedź?
Diomidis Spinellis,
3
Jest to wyjaśniane przez HTTPbis; Podsumowanie można znaleźć na mnot.net/blog/2012/09/24/caching_POST .
Mark Nottingham
68

Zgodnie z RFC 2616 sekcja 9.5:

„Odpowiedzi na metodę POST nie podlegają buforowaniu, JEŚLI odpowiedź zawiera odpowiednie pola nagłówka Cache-Control lub Expires”.

Więc TAK, możesz buforować odpowiedź na żądanie POST, ale tylko wtedy, gdy nadejdzie z odpowiednimi nagłówkami. W większości przypadków nie chcesz buforować odpowiedzi. Ale w niektórych przypadkach - na przykład jeśli nie zapisujesz żadnych danych na serwerze - jest to całkowicie właściwe.

Należy jednak pamiętać, że wiele przeglądarek, w tym aktualna Firefox 3.0.10, nie buforuje odpowiedzi POST niezależnie od nagłówków. IE zachowuje się pod tym względem inteligentniej.

Teraz chcę wyjaśnić tutaj pewne zamieszanie dotyczące RFC 2616 S. 13.10. Metoda POST na URI nie „unieważnia zasobu do buforowania”, jak niektórzy tutaj stwierdzili. Powoduje, że poprzednio zbuforowana wersja tego identyfikatora URI jest przestarzała, nawet jeśli jej nagłówki kontroli pamięci podręcznej wskazywały na świeżość i dłuższy czas trwania.


źródło
2
+1 reBoot, dzięki za wyjaśnienie problemu z nagłówkami i poprawienie błędnych stwierdzeń dotyczących 13.10. Zaskakujące, że te błędne odpowiedzi otrzymały tak wiele pozytywnych głosów.
Eugene Beresovsky,
3
Jaka jest różnica między „unieważnieniem zasobu do buforowania” a „uczynieniem buforowanej wersji URI przestarzałą”? Czy chcesz powiedzieć, że serwer może buforować odpowiedź POST, ale klienci nie?
Gili
1
„Uaktualnianie wersji URI przechowywanej w pamięci podręcznej” dotyczy sytuacji, gdy używasz tego samego identyfikatora URI dla żądań GETi POST. Jeśli jesteś pamięcią podręczną znajdującą się między klientem a serwerem, widzisz GET /fooi buforujesz odpowiedź. Następny widzisz POST /footo jesteś zobowiązany do unieważnienia pamięci podręcznej odpowiedź od GET /foonawet jeżeli POSTodpowiedź nie zawiera żadnych nagłówków kontroli cache ponieważ są one takie same URI , więc następny GET /foobędzie musiał revalidate nawet jeśli oryginalne nagłówki wskazane cache będzie nadal na żywo (jeśli nie widziałeś POST /fooprośby)
Stephen Connolly
But in some cases - such as if you are not saving any data on the server - it's entirely appropriate.. Jaki jest zatem sens takiego API POST w pierwszej kolejności?
Siddhartha
33

Ogólny:

Zasadniczo POST nie jest idempotentną operacją . Nie możesz więc używać go do buforowania. GET powinna być operacją idempotentną, więc jest powszechnie używana do buforowania.

Zobacz sekcję 9.1 protokołu HTTP 1.1 RFC 2616 S. 9.1 .

Poza semantyką metody GET:

Sama metoda POST jest semantycznie przeznaczona do wysyłania czegoś do zasobu. POST nie może być buforowany, ponieważ jeśli zrobisz coś raz, dwa razy vs trzy razy, za każdym razem zmieniasz zasoby serwera. Każde żądanie ma znaczenie i powinno zostać dostarczone na serwer.

Sama metoda PUT jest semantycznie przeznaczona do umieszczania lub tworzenia zasobu. Jest to operacja idempotentna, ale nie będzie używana do buforowania, ponieważ w międzyczasie mogło nastąpić DELETE.

Sama metoda DELETE jest semantycznie przeznaczona do usuwania zasobu. Jest to operacja idempotentna, ale nie będzie używana do buforowania, ponieważ w międzyczasie mogło dojść do PUT.

Odnośnie buforowania po stronie klienta:

Przeglądarka internetowa zawsze przekaże Twoje żądanie, nawet jeśli otrzyma odpowiedź z poprzedniej operacji POST. Na przykład możesz wysyłać e-maile za pomocą Gmaila w odstępie kilku dni. Mogą mieć ten sam temat i treść, ale należy wysłać oba e-maile.

Odnośnie buforowania proxy:

Serwer proxy HTTP, który przekazuje Twoją wiadomość do serwera, nigdy nie buforowałby niczego poza żądaniem GET lub HEAD.

Odnośnie buforowania serwera:

Serwer domyślnie nie obsługiwałby automatycznie żądania POST poprzez sprawdzanie swojej pamięci podręcznej. Ale oczywiście żądanie POST może zostać wysłane do twojej aplikacji lub dodatku i możesz mieć własną pamięć podręczną, z której odczytujesz, gdy parametry są takie same.

Unieważnienie zasobu:

Sprawdzenie protokołu HTTP 1.1 RFC 2616 S. 13.10 pokazuje, że metoda POST powinna unieważnić zasób do buforowania.

Brian R. Bondy
źródło
9
„Zasadniczo POST nie jest idempotentną operacją. Dlatego nie można jej używać do buforowania”. To po prostu nie tak i nie ma to sensu, zobacz odpowiedź reBoot, aby uzyskać szczegółowe informacje. Niestety, nie mogę jeszcze głosować przeciw, inaczej bym to zrobił.
Eugene Beresovsky
1
Eugene: Zmieniłem „nie jest” na „nie może”.
Brian R. Bondy,
1
Dzięki Brian, to brzmi lepiej. Mój problem z twoim "POST not idemp. -> nie może być buforowany" był - i nie wyjaśniłem tego wystarczająco jasno - nawet jeśli operacja nie jest idempotentna, co nie oznacza, że ​​nie można jej buforować. Chyba chodzi o to, czy patrzysz na to z punktu widzenia serwera, który oferuje dane i zna ich semantykę, czy też patrzysz na to od strony odbierającej (czy to buforujący serwer proxy itp., Czy też klient) . Jeśli jest to punkt widzenia klienta / proxy, całkowicie zgadzam się z Twoim postem. Jeśli jest to punkt pov serwera, jeśli serwer mówi: „klient może buforować”, to klient może buforować.
Eugene Beresovsky
1
Eugene: Jeśli ma znaczenie, czy jest wywoływane raz, czy 5 razy, na przykład, jeśli wysyłasz wiadomość na listę, to chcesz, aby to wywołanie trafiło do serwera 5 razy, prawda? I nie chcesz go buforować, aby nie trafił na serwer, prawda? Ponieważ istnieją skutki uboczne, które są ważne.
Brian R. Bondy
[cd.] Jednak nie zdecydowałem, czy serwer rzeczywiście powinien wysyłać nagłówek pozwalający na wygasanie pamięci podręcznej TYLKO, jeśli operacja jest idempotentna. Chociaż wydaje mi się, że to ma sens. [właśnie zobaczyłem twoją odpowiedź]: Zgoda, więc wydaje mi się, że zdecydowałem się: serwer powinien sygnalizować buforowanie tylko w przypadku idempotencji - i to może być również POST, zwłaszcza biorąc pod uwagę potrzebę X-HTTP-Method-Override w w niektórych przypadkach.
Eugene Beresovsky
6

Jeśli buforujesz odpowiedź POST, musi ona być skierowana do aplikacji internetowej. To właśnie oznacza „Odpowiedzi na tę metodę nie mogą być buforowane, chyba że odpowiedź zawiera odpowiednie pola nagłówka Cache-Control lub Expires”.

Można spokojnie założyć, że aplikacja, która wie, czy wyniki testu POST są idempotentne, decyduje o dołączeniu niezbędnych i odpowiednich nagłówków kontroli pamięci podręcznej. Jeśli nagłówki sugerujące, że buforowanie jest dozwolone, są obecne, aplikacja informuje cię, że POST jest w rzeczywistości super-GET; że użycie POST było wymagane tylko ze względu na ilość niepotrzebnych i nieistotnych (ze względu na użycie URI jako klucza pamięci podręcznej) danych niezbędnych do wykonania idempotentnej operacji.

Przy tym założeniu następujące GET-y mogą być obsługiwane z pamięci podręcznej.

Aplikacja, której nie udało się dołączyć niezbędnych i poprawnych nagłówków w celu rozróżnienia między buforowanymi i niepodlegającymi buforowaniu odpowiedziami POST, jest winna wszelkich nieprawidłowych wyników buforowania.

To powiedziawszy, każdy POST, który trafia do pamięci podręcznej, wymaga weryfikacji przy użyciu warunkowych nagłówków. Jest to wymagane, aby odświeżyć zawartość pamięci podręcznej, aby wyniki testu POST nie były odzwierciedlane w odpowiedziach na żądania do czasu wygaśnięcia okresu istnienia obiektu.

JohnS
źródło
4

Mark Nottingham przeanalizował, kiedy można buforować odpowiedź testu POST. Zauważ, że kolejne żądania, które chcą skorzystać z buforowania, muszą być żądaniami GET lub HEAD. Zobacz także semantykę http

POST nie zajmują się reprezentacjami określonego stanu, 99 razy na 100. Jest jednak jeden przypadek, w którym tak się dzieje; gdy serwer robi wszystko, aby powiedzieć, że ta odpowiedź POST jest reprezentacją jego URI, ustawiając nagłówek Content-Location, który jest taki sam jak identyfikator URI żądania. W takim przypadku odpowiedź POST jest podobna do odpowiedzi GET na ten sam identyfikator URI; może być buforowany i ponownie używany - ale tylko w przypadku przyszłych żądań GET.

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

dschulten
źródło
4

Jeśli zastanawiasz się, czy możesz zapisać w pamięci podręcznej żądanie posta i spróbować znaleźć odpowiedź na to pytanie, prawdopodobnie nie odniesiesz sukcesu. Podczas wyszukiwania „żądania publikacji w pamięci podręcznej” pierwszym wynikiem jest pytanie StackOverflow.

Odpowiedzi są zagmatwaną mieszanką tego, jak powinno działać buforowanie, jak działa buforowanie zgodnie z RFC, jak buforowanie powinno działać zgodnie z RFC i jak działa buforowanie w praktyce. Zacznijmy od RFC, zobaczmy, jak faktycznie działa przeglądarka, a następnie porozmawiajmy o sieciach CDN, GraphQL i innych problematycznych obszarach.

RFC 2616

Zgodnie z RFC, żądania POST muszą unieważniać pamięć podręczną:

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

Ten język sugeruje, że żądania POST nie mogą być buforowane, ale to nieprawda (w tym przypadku). Pamięć podręczna jest unieważniana tylko dla wcześniej zapisanych danych. Dokument RFC (wydaje się) wyraźnie wyjaśnia, że ​​tak, POSTżądania można buforować :

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.

Pomimo tego języka ustawienie Cache-Controlnie może buforować kolejnych POSTżądań do tego samego zasobu. POSTżądania należy wysyłać do serwera:

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.

W jaki sposób to ma sens? Cóż, nie buforujesz POSTżądania, buforujesz zasób.

Treść odpowiedzi POST może być buforowana tylko dla kolejnych żądań GET do tego samego zasobu. Ustaw nagłówek Locationlub Content-Locationw odpowiedzi POST, aby poinformować, który zasób reprezentuje treść. Zatem jedynym technicznie poprawnym sposobem buforowania żądania POST jest wykonanie kolejnych GET w tym samym zasobie.

Prawidłowa odpowiedź to:

  • „tak, RFC pozwala na buforowanie żądań POST dla kolejnych GET do tego samego zasobu”
  • „nie, RFC nie pozwala na buforowanie żądań POST dla kolejnych POST, ponieważ POST nie jest idempotentny i musi być zapisywany na serwerze”

Chociaż RFC zezwala na buforowanie żądań do tego samego zasobu, w praktyce przeglądarki i sieci CDN nie implementują tego zachowania i nie pozwalają na buforowanie żądań POST.

Źródła:

Demonstracja zachowania przeglądarki

Biorąc pod uwagę następującą przykładową aplikację 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!')
    })

Biorąc pod uwagę następującą przykładową stronę internetową (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>

Zainstaluj NodeJS, Express i uruchom aplikację JavaScript. Otwórz stronę internetową w przeglądarce. Wypróbuj kilka różnych scenariuszy, aby przetestować zachowanie przeglądarki:

  • Kliknięcie „Uruchom żądanie GET” powoduje wyświetlenie tej samej „liczby” za każdym razem (działa buforowanie HTTP).
  • Kliknięcie „Wyzwalaj żądanie POST” powoduje za każdym razem inny licznik (nie działa buforowanie HTTP dla POST).
  • Kliknięcie opcji „Uruchom żądanie GET”, „Uruchom żądanie POST” i „Uruchom żądanie GET” spowoduje wyświetlenie żądania POST, które unieważnia pamięć podręczną żądania GET.
  • Kliknięcie „Uruchom żądanie POST”, a następnie „Uruchom żądanie GET” pokazuje, że przeglądarki nie będą buforować żądań POST dla kolejnych żądań GET, mimo że zezwala na to RFC.

Pokazuje to, że nawet jeśli można ustawić nagłówki Cache-Controli Content-Locationodpowiedzi, nie ma sposobu, aby pamięć podręczna przeglądarki była żądaniem HTTP POST.

Czy muszę przestrzegać RFC?

Zachowania przeglądarki nie można konfigurować, ale jeśli nie jesteś przeglądarką, niekoniecznie obowiązują Cię zasady RFC.

Jeśli piszesz kod aplikacji, nic nie stoi na przeszkodzie, aby jawnie buforować żądania POST (pseudokod):

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
}

Sieci CDN, serwery proxy i bramy również nie muszą być zgodne ze specyfikacją RFC. Na przykład, jeśli używasz Fastly jako swojej sieci CDN, Fastly umożliwia pisanie niestandardowej logiki VCL w celu buforowania żądań POST .

Czy powinienem buforować żądania POST?

To, czy żądanie POST powinno być buforowane, czy nie, zależy od kontekstu.

Na przykład możesz wysyłać zapytania do Elasticsearch lub GraphQL za pomocą POST, gdy Twoje zapytanie bazowe jest idempotentne. W takich przypadkach buforowanie odpowiedzi w pamięci podręcznej może mieć sens lub nie, w zależności od przypadku użycia.

W RESTful API żądania POST zwykle tworzą zasób i nie powinny być buforowane. Jest to również rozumienie przez RFC POST, że nie jest to operacja idempotentna.

GraphQL

Jeśli używasz GraphQL i potrzebujesz buforowania HTTP w sieciach CDN i przeglądarkach, zastanów się, czy wysyłanie zapytań za pomocą metody GET spełnia Twoje wymagania zamiast POST . Uwaga: różne przeglądarki i sieci CDN mogą mieć różne limity długości URI, ale lista bezpiecznych operacji (biała lista zapytań), jako najlepsza praktyka dla zewnętrznych aplikacji GraphQL, może skrócić identyfikatory URI.

timrs2998
źródło
3

Jeśli jest to coś, co w rzeczywistości nie zmienia danych w Twojej witrynie, powinno to być żądanie GET. Nawet jeśli jest to formularz, nadal możesz ustawić go jako żądanie pobierania. Chociaż, jak wskazują inni, można buforować wyniki testu POST, nie miałoby to sensu semantycznego, ponieważ z definicji POST zmienia dane.

Kibbee
źródło
Żądanie POST może nie zmieniać żadnych danych używanych do generowania strony odpowiedzi. W takim przypadku buforowanie odpowiedzi może mieć sens.
David Z,
David Z: Z pewnością, jeśli POST zmienia dane, odpowiedź powinna wskazywać na sukces / porażkę. Nie jest to dokładnie wymagane, ale nie przychodzi mi do głowy sytuacja, w której POST zmieniłby dane, a odpowiedź byłaby statyczna.
Morvael,
6
Jeśli dane parametrów są zbyt długie, żądanie GET nie będzie działać ze wszystkimi serwerami, dlatego POST jest potrzebny, zwłaszcza jeśli źródło powinno działać na serwerach, których autor kodu nie konfiguruje.
Gogowitsch
@Gogowitsch bardzo prawda, napotkasz kod błędu 414 - stackoverflow.com/a/2891598/792238
Siddhartha
-2

W przypadku przeglądarki Firefox 27.0 i httpfox 19 maja 2014 r. Zobaczyłem jedną linię: 00: 03: 58.777 0,488 657 (393) POST (Cache) text / html https://users.jackiszhp.info/S4UP

Oczywiście odpowiedź metody post jest przechowywana w pamięci podręcznej, a także w https. Nie do wiary!

user1462586
źródło
-3

POST jest używany w stanowym Ajaxie. Zwracanie buforowanej odpowiedzi na POST niweczy kanał komunikacyjny i skutki uboczne otrzymania wiadomości. To jest bardzo, bardzo złe. To także prawdziwy ból do wytropienia. Gorąco polecam przeciwko.

Trywialnym przykładem może być wiadomość, że efektem ubocznym jest wypłata pensji w wysokości 10 000 USD w bieżącym tygodniu. NIE chcesz, aby komunikat „OK, przeszedł!” strona wstecz, która została zapisana w pamięci podręcznej w zeszłym tygodniu. Inne, bardziej złożone przypadki w świecie rzeczywistym skutkują podobną wesołością.

Władca Smoków
źródło
3
Niezupełnie odpowiedź - POST jest używany do różnych rzeczy i czasami istnieją ważne powody, aby chcieć buforować odpowiedź.
Alexei Levenkov