Behandling av villkorlig vy¶
HTTP-klienter kan skicka ett antal rubriker för att berätta för servern om kopior av en resurs som de redan har sett. Detta används ofta när man hämtar en webbsida (med en HTTP GET
-begäran) för att undvika att skicka alla data för något som klienten redan har hämtat. Samma rubriker kan dock användas för alla HTTP-metoder (POST
, PUT
, DELETE
, etc.).
För varje sida (svar) som Django skickar tillbaka från en vy kan den tillhandahålla två HTTP-rubriker: rubriken ETag
och rubriken Last-Modified
. Dessa rubriker är valfria på HTTP-svar. De kan ställas in av din vyfunktion, eller så kan du förlita dig på ConditionalGetMiddleware
middleware för att ställa in ETag
-rubriken.
När klienten nästa gång begär samma resurs kan den komma att skicka med ett huvud som antingen If-Modified-Since eller If-Unmodified-Since, som innehåller datumet för den senaste modifieringstiden som den skickades, eller antingen If-Match eller If-None-Match, som innehåller den senaste ETag
som den skickades. Om den aktuella versionen av sidan matchar den ETag
som skickades av klienten, eller om resursen inte har ändrats, kan en 304-statuskod skickas tillbaka, istället för ett fullständigt svar, och tala om för klienten att ingenting har ändrats. Om sidan har ändrats eller inte matchar den ETag
som klienten skickat kan, beroende på rubriken, en statuskod 412 (Precondition Failed) returneras.
När du behöver mer finkornig kontroll kan du använda funktioner för villkorlig bearbetning per vy.
Dekoratorn condition
(villkor)¶
Ibland (faktiskt ganska ofta) kan du skapa funktioner för att snabbt beräkna ETag-värdet eller den senast modifierade tiden för en resurs, utan att behöva göra alla beräkningar som krävs för att konstruera hela vyn. Django kan sedan använda dessa funktioner för att tillhandahålla ett ”tidigt bailout”-alternativ för vybehandlingen. Att tala om för klienten att innehållet inte har ändrats sedan den senaste begäran, kanske.
Dessa två funktioner skickas som parametrar till dekoratorn django.views.decorators.http.condition
. Denna dekorator använder de två funktionerna (du behöver bara tillhandahålla en, om du inte kan beräkna båda kvantiteterna enkelt och snabbt) för att räkna ut om rubrikerna i HTTP-begäran matchar dem på resursen. Om de inte matchar måste en ny kopia av resursen beräknas och din normala vy anropas.
Signaturen för dekoratorn condition
ser ut så här:
condition(etag_func=None, last_modified_func=None)
De två funktionerna, för att beräkna ETag och den senaste modifieringstiden, kommer att skickas till det inkommande request
-objektet och samma parametrar, i samma ordning, som den vyfunktion de hjälper till att omsluta. Funktionen som skickas last_modified_func
bör returnera ett standard datetime-värde som anger den senaste gången resursen ändrades, eller None
om resursen inte finns. Funktionen som skickas till etag
-dekoratorn bör returnera en sträng som representerar ETag för resursen, eller None
om den inte existerar.
Dekoratorn ställer in rubrikerna ETag
och Last-Modified
på svaret om de inte redan är inställda av vyn och om begärans metod är säker (GET
eller HEAD
).
Att använda den här funktionen på ett användbart sätt förklaras förmodligen bäst med ett exempel. Anta att du har det här paret av modeller, som representerar ett litet bloggsystem:
import datetime
from django.db import models
class Blog(models.Model): ...
class Entry(models.Model):
blog = models.ForeignKey(Blog, on_delete=models.CASCADE)
published = models.DateTimeField(default=datetime.datetime.now)
...
Om förstasidan, som visar de senaste blogginläggen, bara ändras när du lägger till ett nytt blogginlägg, kan du beräkna den senaste ändringstiden mycket snabbt. Du behöver det senaste publicerade
datumet för varje post som är associerad med den bloggen. Ett sätt att göra detta skulle vara:
def latest_entry(request, blog_id):
return Entry.objects.filter(blog=blog_id).latest("published").published
Du kan sedan använda den här funktionen för att tidigt upptäcka en oförändrad sida för din förstasidesvisning:
from django.views.decorators.http import condition
@condition(last_modified_func=latest_entry)
def front_page(request, blog_id): ...
Var försiktig med beställningen av dekoratörer
När condition()
returnerar ett villkorligt svar, kommer alla dekoratorer under det att hoppas över och inte gälla för svaret. Därför måste alla dekoratorer som behöver tillämpas på både det vanliga vy-svaret och ett villkorligt svar vara ovanför condition()
. I synnerhet bör vary_on_cookie()
, vary_on_headers()
och cache_control()
komma först eftersom RFC 9110 kräver att de rubriker som de anger ska finnas på 304-svar.
Kortkommandon för att bara beräkna ett värde¶
Som en allmän regel gäller att om du kan tillhandahålla funktioner för att beräkna både ETag och senast ändrade tid, bör du göra det. Du vet inte vilka headers en given HTTP-klient kommer att skicka till dig, så var beredd att hantera båda. Ibland är det dock bara ett värde som är lätt att beräkna och Django tillhandahåller dekoratorer som endast hanterar ETag eller endast senast modifierade beräkningar.
Dekoratorerna django.views.decorators.http.etag
och django.views.decorators.http.last_modified
passerar samma typ av funktioner som dekoratorn condition
. Deras signaturer är:
etag(etag_func)
last_modified(last_modified_func)
Vi kan skriva det tidigare exemplet, som bara använder en senast modifierad funktion, med hjälp av en av dessa dekoratorer:
@last_modified(latest_entry)
def front_page(request, blog_id): ...
…eller:
def front_page(request, blog_id): ...
front_page = last_modified(latest_entry)(front_page)
Använd condition
när du testar båda villkoren¶
Det kan se trevligare ut för vissa människor att försöka kedja dekoratorerna etag
och last_modified
om du vill testa båda förutsättningarna. Detta skulle dock leda till felaktigt beteende.
# Bad code. Don't do this!
@etag(etag_func)
@last_modified(last_modified_func)
def my_view(request): ...
# End of bad code.
Den första dekoratorn vet ingenting om den andra och kan svara att svaret inte ändras även om den andra dekoratorn skulle bestämma något annat. Dekoratorn condition
använder båda callback-funktionerna samtidigt för att räkna ut vilken åtgärd som ska vidtas.
Använda dekoratorerna med andra HTTP-metoder¶
Dekoratorn condition
är användbar för mer än bara GET
och HEAD
förfrågningar (HEAD
förfrågningar är samma sak som GET
i denna situation). Den kan också användas för att kontrollera POST
, PUT
och DELETE
förfrågningar. I dessa situationer är tanken inte att returnera ett ”not modified”-svar, utan att tala om för klienten att den resurs som de försöker ändra har ändrats under tiden.
Tänk till exempel på följande utväxling mellan klienten och servern:
Klienten begär
/foo/
.Servern svarar med en del innehåll med ETag
"abcd1234"
.Klienten skickar en HTTP
PUT
-förfrågan till/foo/
för att uppdatera resursen. Den skickar också enIf-Match: "abcd1234"
header för att ange den version som den försöker uppdatera.Servern kontrollerar om resursen har ändrats genom att beräkna ETag på samma sätt som för en
GET
-begäran (med samma funktion). Om resursen har ändrats returneras en statuskod på 412, vilket betyder att ”förvillkoret misslyckades”.Klienten skickar en
GET
-förfrågan till/foo/
, efter att ha fått ett 412-svar, för att hämta en uppdaterad version av innehållet innan det uppdateras.
Det viktiga som exemplet visar är att samma funktioner kan användas för att beräkna ETag-värdet och värdet för senaste ändring i alla situationer. Faktum är att du bör använda samma funktioner, så att samma värden returneras varje gång.
Valideringshuvuden med osäkra förfrågningsmetoder
Dekoratorn condition
ställer endast in valideringsrubriker (ETag
och Last-Modified
) för säkra HTTP-metoder, dvs GET
och HEAD
. Om du vill returnera dem i andra fall, ställ in dem i din vy. Se RFC 9110 Section 9.3.4 för att lära dig mer om skillnaden mellan att ställa in en validator header som svar på förfrågningar gjorda med PUT
kontra POST
.
Jämförelse med middleware villkorlig bearbetning¶
Django tillhandahåller villkorlig GET
-hantering via django.middleware.http.ConditionalGetMiddleware
. Även om det är lämpligt för många situationer har mellanvaran begränsningar för avancerad användning:
Den tillämpas globalt på alla vyer i ditt projekt.
Det innebär inte att du slipper generera svaret, vilket kan bli dyrt.
Det är endast lämpligt för HTTP
GET
-förfrågningar.
Här bör du välja det lämpligaste verktyget för just ditt problem. Om du har ett sätt att snabbt beräkna ETags och ändringstider och om det tar en stund för en vy att generera innehållet bör du överväga att använda dekoratorn condition
som beskrivs i det här dokumentet. Om allt redan går ganska snabbt kan du hålla dig till att använda mellanvaran och mängden nätverkstrafik som skickas tillbaka till klienterna kommer fortfarande att minskas om vyn inte har ändrats.