Recepty z programátorské kuchařky
Protokol HTTP
Aktuální verze kuchařky: listopad 2024
HTTP (Hypertext Transfer Protocol) je jedním z nejpoužívanějších síťových protokolů. Původně byl vymyšlen pro stahování webových stránek a odesílání webových formulářů, ale postupně si našel spoustu jiných aplikací. Povídají si po něm nejrůznější síťová API, nebo třeba servery a klienti chatovacího protokolu Matrix.
HTTP existuje ve více verzích. My si budeme povídat o nejběžnější verzi 1.1. Už existují i verze 2 a 3, které fungují podobně, ale snaží se o vyšší efektivitu, což je komplikuje.
Po HTTP si povídají dvě strany: server a klient. Server poskytuje nějakou službu (třeba webové stránky), klienti tuto službu využívají. Když se klient chce připojit k serveru, nejdříve si pomocí DNS přeloží jméno serveru na IP adresu. Pak s ní naváže spojení přes TCP typicky na portu 80. TCP spojení si můžeme představit jako obousměrnou trubku mezi serverem a klientem, která umí spolehlivě přenášet bajty – když do ní na jednom konci nějaké vložíme, za nějakou dobu vypadnou z druhého konce.
Pokud nám záleží na bezpečnosti, chceme data šifrovat a podepisovat – o to se typicky stará protokol TLS, který z TCP udělá „bezpečnou trubku.“ Skrz ni pak proudí obyčejné HTTP. Této kombinaci se říká HTTPS a místo portu 80 bydlí obvykle na 443.
HTTP je protokol košatý, takže se naše kuchařka zaměří hlavně na základy. Kdyby vás zajímaly detaily, najdete je ve standardech RFC 9110 a RFC 9112.
Objekty a URL
HTTP popisuje operace na nějakých objektech (resources). V nejjednodušším případě může být objektem soubor na disku serveru a operací „chci stáhnout obsah souboru.“ Nebo je objektem meteostanice na střeše budovy a operací „chci naměřené hodnoty ve formátu JSON.“
Síťové protokoly se obecně na objekty odkazují pomocí jejich URL (Uniform Resource Locator). Typické URL pro HTTP vypadá takto:
http://ksp.mff.cuni.cz/h/ulohy/37/reseni1.html
Skládá se ze schématu (názvu protokolu) http:
, za ním následuje //
adresa serveru
(IP adresa nebo doménové jméno) a případně :
port, pokud máme použít jiný
TCP port než běžný 80. Zbytek URL je tvořen cestou v rámci serveru.
Na další, méně obvyklé součástky URL narazíme později.
Někdy se stane, že do URL potřebujeme zapsat znak, který by jinak měl speciální
význam – třeba mezeru. V takovém případě ho zakódujeme jako %
xy, kde xy
je hexadecimální kód znaku v ASCII.
Takže mezera je %20
a procento %25
. Nechce-li se nám studovat
v RFC 3986, které znaky jsou
speciální, kódujme vše kromě písmen, číslic a tečky.
a - _ . ~
.
Ještě dodejme, že občas potkáváme i zkratku URI (Uniform Resource Identifier),
což je obecnější pojem, ale v kontextu HTTP mezi nimi není potřeba rozlišovat.
Požadavek a odpověď
Podívejme se, co se děje, když si klient chce stáhnout stránku z uvedeného URL.
Spojí se po TCP s ksp.mff.cuni.cz
na portu 80 a pošle požadavek:
GET /h/ulohy/37/reseni1.html HTTP/1.1
Host: ksp.mff.cuni.cz
Connection: close
Požadavek má tvar několika řádků textu (pozor, řádky se ukončují CR+LF).
První řádek obsahuje metodu GET
(to je ta operace, která se má s daným
objektem provést – v tomto případě stáhnout si jeho obsah), cestu k objektu
v rámci serveru a verzi HTTP, kterou se bavíme.
Následující řádky tvoří hlavičku požadavku složenou z polí tvaru
klíč:
hodnota.
Pole Host
je povinné a obsahuje doménové jméno serveru (posílá se proto, aby na jedné
IP adrese mohly běžet servery pro více domén). Jelikož chceme být slušní, tak pomocí
Connection: close
řekneme serveru, že po tomto spojení už nechceme posílat další
požadavky.
Požadavek je ukončen prázdným řádkem.
Server pak odpoví třeba takto:
HTTP/1.1 200 OK
Content-Type: text/html; charset=utf-8
Content-Length: 38771
Connection: close
Na prvním řádku nám server sděluje, že také hovoří verzí 1.1 a že operace byla provedena
úspěšně. To říká jednak stavovým kódem 200 (určeným pro stroje), jednak zprávou OK
určenou pro lidi.
Následující řádky obsahují hlavičku odpovědi:
Content-Type
– typ dat, která server posílá. Jedná se o text ve formátu HTML, tedy webovou stránku. A znaky má zakódované v UTF-8 (jelikož software, kterým se na odpověď díváme, nejspíš také používá UTF-8, nemusíme se o kódování starat).Content-Length
– délka dat v bajtech.Connection: close
– server potvrzuje, že po této odpovědi spojení zavře.
Hlavičku opět ukončuje prázdný řádek. Za ním následuje tělo odpovědi – data, která si stahujeme.
Metody
Náš příklad používá metodu GET
, ale existují i jiné. Pojďme se podívat
na nejběžnější metody.
Požadavky s metodou GET
a HEAD
nemají žádné tělo,
s ostatními metodami ho mít mohou.
GET
– vyžádá si data daného objektu (přesněji řečeno reprezentaci objektu v nějakém formátu; časem uvidíme, že jich může být na výběr víc). Pokud webovému prohlížeči zadáme URL stránky, stáhne si ji touto metodou. Reprezentace může být soubor na disku, ale často ji server vytváří v okamžiku vyřizování požadavku.HEAD
– jakoGET
, ale vyžádá si od serveru jen hlavičku odpovědi bez těla. To se hodí, pokud nás zajímají informace o objektu, aniž bychom si ho chtěli celý stáhnout.POST
– řekne objektu, ať zpracuje nějaká data. Může to být třeba odeslání webového formuláře (viz dále), vytištění něčeho na tiskárně nebo přihlášení uživatele.PUT
– nahrazuje už existující objekt novým obsahem.PATCH
– mění jen některé části daného objektu.DELETE
– odstraňuje daný objekt.
Kromě nich ještě existují CONNECT
, OPTIONS
a TRACE
, které
mají poměrně specifické použítí a nebudeme se jimi zabývat.
Metody GET
a HEAD
nesmí měnit stav objektu na serveru – prohlížeč se tedy
může sám od sebe rozhodnout načíst na pozadí stránku, na níž někde viděl odkaz,
a nic tím nezkazí. Takovým metodám se říká bezpečné.
Metody PUT
a DELETE
sice mění stav, ale jsou idempotentní –
pošleme-li stejný požadavek vícekrát, dopadne to stejně jako jednou. (Všimněte
si, že smazat nebo nahradit něco dvakrát má stejný efekt, jako to udělat
jednou.) Pokud tedy klient zjistí, že se mu spojení se serverem rozpadlo, může
GET
, HEAD
, DELETE
nebo PUT
automaticky zopakovat;
s POST
em by to ovšem dělat neměl (aby vám neobjednal tři krabice zmrzliny
místo jedné).
Metoda PATCH
nemusí být ani bezpečná, ani idempotentní.
Data požadavku/odpovědi
Jak už víme, požadavek i odpověď mohou obsahovat tělo s daty. Jak se pozná, kde data končí?
Jestliže dopředu víme, jak jsou data velká, pošleme Content-Length
a problém je vyřešen.
Pokud to ale nevíme, pomůžeme si nakouskováním dat. Do hlavičky přidáme Transfer-Encoding:
chunked
a tělo pošleme jako posloupnost kousků. Každý kousek vypadá takto: délka dat zapsaná
hexadecimálně, CR+LF, data kousku, CR+LF. Poslední kousek má délku 0.
Historické verze HTTP po skončení těla zavíraly celé TCP spojení. To bylo
zbytečně neefektivní, protože pro stažení každého objektu se muselo navázat
spojení nové. Dnes lze poslat po stejném spojení další požadavek a přijmout
další odpověď, dokud se jedna ze stran nerozhodne, že chce spojení ukončit.
Pokud chceme poslat jen jeden požadavek, dá se toto chování předem zakázat
pomocí Connection: close
, jak už jsme viděli výše.
Formát dat je specifikován v hlavičce požadavku/odpovědi:
Content-Type
jsme již potkali. Typů dat je mnoho a definuje je standard MIME, proto se jim říká MIME-typy. Běžný je třebatext/plain
pro čistý text,text/html
pro webovou stránku,image/jpeg
aimage/png
pro obrázky,application/zip
pro ZIPové archivy atd. Za typem mohou být další parametry oddělené středníkem: pro textové typy je to třebacharset
.Podle
Content-Type
se prohlížeče rozhodují, jestli data zobrazí (případně jak), nebo je nabídnou ke stažení.Content-Encoding
popisuje transformaci dat, nejčastěji kompresi, která se stala ještě před odesláním dat. To znamená, že třebaContent-Length
už bude udávat velikost po zkomprimování, aleContent-Type
se nezmění. Tedy například zkomprimovaná webová stránka může mítContent-Type: text/html
aContent-Encoding: gzip
. V kontrastu s tím dříve zmíněnýTransfer-Encoding
popisuje transformaci, která se stala až při přenosu. Klienti se obvykle k obojímu chovají stejně: před dalším zpracováním data dekódují.Content-Language
říká, jakým (lidským) jazykem je dokument napsán, přesněji řečeno pro mluvčí jakého jazyka je určen. Kupříkladucs
pro text psaný česky,cs, en
pro dvojjazyčné vydání knížky, ale učebnice arabštiny psaná anglicky by měla jenen
, ač obsahuje i arabské věty.
Kromě Content-Type
nejsou tato pole povinná.
Další pole hlavičky
Uvedeme ještě pár zajímavých polí hlavičky, všechna jsou nepovinná. V požadavcích:
User-Agent
– software klienta (produkt/verze
, případně více oddělených čárkou; může obsahovat komentáře v závorkách). Hodí se, aby servery mohly obcházet chyby v konkrétních klientech. Ale také útočníkům, aby poznali, kdo má nezabezpečeného klienta :)Referer
– pokud požadavek vznikl následováním nějakého odkazu (třeba klikacího na webové stránce), můžeme uvést URL, kde se odkaz nacházel. To se hodí správcům serveru k ulovení chybných odkazů.Cookie
– server může pomocí poleSet-Cookie
v odpovědi předat klientovi „sušenku“. To je libovolný řetězec, který si klient zapamatuje a pak ho posílá v hlavičkách dalších požadavků. Cookies se používají pro udržování stavu webových aplikací (třeba přihlášení uživatele nebo nákupní košík v obchodě). Jsou specifikované v RFC 2965, my si odpustíme jak detaily, tak úvahy o důsledcích pro soukromí uživatelů.
A v odpovědích:
Server
– software serveru (formát jakoUser-Agent
).
Ještě dodejme, že ve jménech polí se nerozlišují velká a malá písmena – psát
velká počáteční je jenom (dobrý) zvyk. Kromě standardních polí si každý může
přidat svá vlastní, jejichž jména začínají na X-
. A lidé někdy nepořádně
říkají „hlavička“ jednomu poli a „hlavičky“ množině všech polí.
Stavové kódy
Jak už víme, server začíná odpověď řádkem se stavovým kódem. Pojďme se podívat, co kódy znamenají. Uvádíme obvyklé názvy stavů, server ovšem může být kreativní. Méně běžné kódy vynecháváme.
1xx
– operace stále probíhá (to nenastane v žádné ze situací, které popisujeme).2xx
– operace byla provedena úspěšně:200 OK
– nic zvláštního se nestalo201 Created
– operací (typickyPUT
em) vznikl nový objekt,Location
v hlavičce říká jeho URL.202 Accepted
– operace byla přijata ke zpracování a provádí se na pozadí. HTTP nedefinuje, jak se časem dozvědět výsledek.204 No Content
– operace se provedla, ale výsledek nemá tělo (častá reakce naPUT
).206 Partial Content
– viz intervalové dotazy níže.
3xx
– přesměrování: místo uvedeného URL se máme obrátit na jiné, typicky uvedené v poliLocation
. Druhů přesměrování je vícero:300 Multiple Choices
– server nabízí víc reprezentací objektu (třeba obrázky v různých formátech). Tělo obsahuje odkazy na URL jednotlivých reprezentací (typicky jako HTML),Location
říká, kterou reprezentaci server preferuje.301 Moved Permanently
– objekt se přesunul na jiné URL, zkuste to tam a zapamatujte si nové umístění. Prohlížeče z historických důvodů po přesměrování mění metoduPOST
naGET
, ačkoliv to standard zakazoval.302 Found
– objekt dočasně najdete na jiném URL, ale příště zase zkuste původní URL. Prohlížeče opět měníPOST
naGET
.303 See Other
– operace nemá výstup, ale uživateli chceme ukázat nějaká související data na jiném URL (z nějž se provedeGET
).304 Not Modified
– viz podmíněné dotazy níže.307 Temporary Redirect
– obdoba302
, která nemění metody.308 Permanent Redirect
– obdoba301
, která nemění metody.
4xx
– chyba klienta: operaci nebylo možné provést z důvodů na straně klienta:400 Bad Request
– kdykoliv chybu nejde popsat přesnějším kódem.401 Unauthorized
– viz autentikace níže.403 Forbidden
– tuto operaci nemáte právo provést.404 Not Found
– URL ukazuje na neexistující objekt.405 Method Not Allowed
– objekt nepodporuje použitou metodu; seznam podporovaných metod najdete v poliAllow
.406 Not Acceptable
– objekt nemá požadovanou reprezentaci, viz domlouvání na formátu dat níže.410 Gone
– URL ukazuje na objekt, který už neexistuje, ač dříve existoval.411 Length Required
– server vyžaduje, abyste uvedli délku těla.413 Content Too Large
– tělo je moc dlouhé.414 URI Too Long
– URL je moc dlouhé.415 Unsupported Media Type
– poslali jsteContent-Type
, kterému server nerozumí.416 Range Not Satisfiable
– viz intervalové dotazy níže.418 I'm a Teapot
– jeden dávný aprílový vtip :)422 Unprocessable Content
– typu dat server rozumí, ale obsahu dat už ne (to by mohla být reakce naPUT
neboPOST
syntakticky chybného souboru).
5xx
– chyba serveru:500 Internal Server Error
– kdykoliv chybu nejde popsat přesnějším kódem.501 Not Implemented
– požádali jste o něco, co server vůbec nepodporuje. Třeba úplně neznámou metodu.503 Service Unavailable
– server momentálně neobsluhuje požadavky. Možná je vypnutý kvůli údržbě, možná jen přetížený.Retry-After
může říci, za jak dlouho to máte zkusit znovu.505 HTTP Version Not Supported
– danou verzí protokolu server nehovoří.
Všechny stavové kódy mohou být doprovázeny tělem. Často to bývá chybová zpráva v HTML, hezky zformátovaná pro pohodlí uživatele.
Použití
Dotazy a předávání argumentů
GET
nemusí nutně vracet celý dlouhý dokument. Server nad ním může
umět provádět dotazy – například v seznamu vyučujících na webu
školy najít položku s konkrétním jménem a příjmením.
Dotaz se specifikuje přidáním argumentů na konec URL (za cestu) ve tvaru
?jmeno=Tim&prijmeni=Berners-Lee
.
Dotaz začíná otazníkem a pak následují dvojice klíč=
hodnota
oddělené ampersandy. Pokud hodnoty obsahují nějaké „divné“ znaky, kódují se
už známým %xy
; místo mezery můžeme poslat +
.
Jak přesně přítomnost dotazu ovlivní operaci, je definované serverem.
Stejně jako co se stane, když dotaz přidáme k jiné metodě než GET
.
Webové formuláře
Součástí webové stránky může být formulář (<form>
).
Ten nechá uživatele zadat data do pojmenovaných políček
a pak tato data odešle serveru na určené URL buď metodou GET
,
nebo POST
. Výběr metody také určí, jakým způsobem se data formuláře
odešlou.
Metoda GET
je jednodušší. Klient využije syntaxi URL s dotazem
a jako argumenty vyplní data formuláře.
To se hodí třeba pro vyhledávací okénka nebo stránkování dlouhých seznamů.
Server na GET
z daného URL může poslat dynamicky generovanou
odpověď (třeba podle zadaného vyhledávacího dotazu).
Pozor na to, že GET
je z definice bezpečná metoda, takže takto
odesílané formuláře nesmí měnit stav světa.
Pokud odeslání formuláře má měnit stav (třeba někomu poslat zprávu),
nebo pokud je dat tolik, že se do URL prakticky nevejdou, použijeme metodu
POST
. Tou pošleme tělo s Content-Type: application/x-www-form-urlencoded
obsahující data zakódovaná stejně jako dotaz na konci URL.
(Pokud by byl součástí formuláře upload souboru, posílá se jiný
formát multipart/form-data
, který nebudeme rozebírat.)
Autentikace
Pokud chcete po HTTP řídit svou vzducholoď, jistě nestojíte o to, aby jí mohl posílat příkazy každý. HTTP nabízí autentikační mechanismy (kterými může klient dokázat, kdo je) a servery pak na jejich základě klienty autorizují k provedení konkrétní operace. (Přestože HTTP takové věci umí, dnešní weby si obvykle přihlašování řeší samy přes formuláře a kryptograficky podepsané cookies. Ale u různých API je autentikace na úrovni HTTP naprosto běžná.)
Když se klient pokusí přistoupit na stránku vyžadující autorizaci,
dostane stav 401 Unauthorized
a v hlavičce něco jako
WWW-Authenticate: Basic realm="Hrochovo"
. Z toho se dozvěděl,
že má použít autentikační metodu jménem Basic
v „říši“
Hrochovo
(říše je nápověda pro uživatele, jaký druh účtu potřebuje).
Požadavek pak pošle znovu a do hlavičky přidá něco jako
Authorization: Basic aHJvY2g6aHVtcGY=
. Tutéž hlavičku
pak posílá s dalšími požadavky na totéž URL a často i na URL
„pod ním v hierarchii“.
Konkrétní podoba autorizačního řetězce je definovaná autentikační
metodou. Pro Basic
se řídíme RFC 7617,
které nám řekne, že máme vzít řetězec jmeno:heslo
a zakódovat ho do
Base64. To není šifra, ale
usnadní to přenášení divných znaků a také si nedopatřením nepřečtete cizí
heslo. (V praxi samozřejmě chcete jakákoliv citlivá data přenášet přes HTTPS
místo obyčejného HTTP.)
Server může klientovi nabídnout víc autentikačních metod (oddělených čárkou), klient si z nich jednu vybere.
Syntaxe URL dovoluje uvést jméno a heslo, která se mají použít, jako
http://jméno:heslo@doména/…
Jelikož aplikace málokdy předpokládají, že URL je citlivý údaj, nebývá
to moudré používat.
Optimalizace
Domlouvání na reprezentaci dat
Server může data poskytovat ve více reprezentacích, které se hodí v různých situacích. Například u obrázku může nabízet běžný JPEG, pak JPEG XL (který je menší, ale zatím ho málokdo zná) a PNG (bezztrátové, takže kvalitnější, leč mnohem větší). Jak server pozná, o kterou reprezentaci má klient zájem?
Jedna možnost je využít stavový kód 300 Multiple Choices
a poslat klientovi
seznam všech variant. Klient, který si vybírat neumí, si prostě z Location
přečte default a následuje ho, jako u každého jiného přesměrování. Každý výběr varianty
nás ale stojí poslání dalšího požadavku. A navíc jsou varianty popsané odkazy v HTML,
takže se těžko zpracovávají strojově.
Dnes je běžnější, že klient pošle serveru své preference a server podle toho sám vybere vhodnou reprezentaci. Preference na MIME-typ dat můžeme poslat třeba takto:
Accept: image/jxl, image/*; q=0.5, */*; q=0.1
Nabízíme několik variant oddělených čárkami. Varianty můžeme ohodnotit
kvalitou mezi 0 a 1 (s přesností na tisíciny). Neuvedená kvalita je rovna 1.
Uvedeme-li q=0
, explicitně říkáme, že o takový typ nestojíme. Náš příklad
tedy říká, že preferujeme JPEG XL,
spokojíme se s libovolným jiným obrázkovým formátem, a když není ani ten, přežijeme s čímkoliv.
Server nám pak pošle tu variantu, které vyjde nejvyšší kvalita, případně chybu 406
Not Acceptable
, pokud žádná dostupná varianta nesplňuje naše požadavky.
Podobně existují pole Accept-Charset
, Accept-Encoding
a Accept-Language
pro domlouvání se na dalších vlastnostech reprezentace dat. Pokud preference neudáme,
server si zvolí po svém.
Kromě toho má HTTP hlavičku požadavku s lehce obskurním názvem TE
, která
uvádí klientem podporované hodnoty Transfer-Encoding
. Ty se ale nehodnotí kvalitou.
Server tedy může pro jedno URL posílat různým klientům různý obsah podle toho,
jakou hlavičku požadavku pošlou. Aby bylo jasné, že se něco takového děje,
server do odpovědi připíše pole Vary
, kde vyjmenuje všechna
pole hlavičky požadavku, kterými se řídila volba reprezentace. Třeba
Vary: Accept, Accept-Encoding
.
Podmíněné operace
Představte si, že máme stažený ohromný soubor a chceme zjistit, zda se
mezitím na serveru změnil. K tomu můžeme použít metodu HEAD
, jež
nám v poli Last-Modified
řekne datum a čas poslední modifikace souboru
(pokud reprezentace není soubor, ale něco dynamicky generovaného, tak toto pole
buď bude rovno aktuálnímu času, nebo bude úplně chybět). Tento čas pak můžeme
porovnat s hodnotou z předchozího stažení.
Pozor na to, že hodiny serveru nemusí být synchronizované s našimi, takže
nedává smysl porovnávat Last-Modified
s naším časem modifikace souboru,
do nějž jsme si data uložili. Server nám ovšem v poli Date
řekne svůj čas,
kdy odpověď vytvořil (takže umíme dopočítat stáří dat).
Časy bývají dost nepraktické identifikátory verzí – data mohou vznikat dynamicky kombinací
různých zdrojů, data se mohou měnit vícekrát za sekundu atd. Proto server může
verzi dat identifikovat také polem ETag
(entity tag). To je nějaký
řetězec, který si každý server může vytvořit po svém. Jediná povinnost je,
aby jakákoliv změna reprezentace dat vyvolala změnu ETagu. (To striktně
vzato není pravda, protože existují i slabé ETagy začínající na W/
,
které mohou zůstat stejné, pokud se nezměnil význam dat. Ale ty
potkáme málokdy.)
Kombinace „zjistím, jestli se data změnila, a pokud ano, stáhnu si novou
verzi“ je ovšem natolik častá, že pro ni existuje zkratka. Libovolný požadavek
jde podmínit. Pokud například do hlavičky GET
u připíšeme pole
If-Modified-Since
s nějakým časem, dostaneme data pouze tehdy, pokud
je jejich Last-Modified
pozdější než tento čas. V opačném případě
server odpoví stavem 304 Not Modified
.
Podobně existuje:
If-Unmodified-Since
– čili opakIf-Modified-Since
. Typické použití: stáhli jsme si starou verzi, chceme jiPUT
em vyměnit za novou, ale jen tehdy, když se na serveru mezitím data nezměnila.If-Match
– ETag odpovídá některému z uvedených.If-None-Match
– ETag neodpovídá ani jednomu z uvedených.
Dodejme, že server se může rozhodnout podmínku ignorovat.
Intervalové dotazy
Teď si představte, že si prohlížíme obrovský videozáznam. Tehdy se hodí stáhnout si jen prvních pár megabytů, abychom nemuseli čekat, až se přenese všechno. A pak postupně stahovat další data, třeba i na přeskáčku, pokud po videu chceme skákat.
HTTP tuto situaci řeší intervalovými dotazy. Nejdříve
pošleme HEAD
a server odpoví polem Accept-Ranges: bytes
. Tím říká,
že podporuje intervalové dotazy a že intervaly se uvádí v bajtech (specifikace
připouští jiné jednotky, ale zatím žádné nedefinuje).
Intervalový GET
pak v hlavičce specifikuje například Range: bytes=1000-1999
.
Tím si řekne o 1000 bajtů, na což server odpoví stavem 206 Partial Content
a v hlavičce dodá Content-Range: bytes=1000-1999/8888
, tedy že posílá
bajty 1000–1999 z celkem 8888 (pokud není celková délka známá, uvede se /*
).
Kdybychom se zeptali na interval -100
, dostaneme posledních 100 bajtů.
Můžeme uvést více intervalů, ale pak odpověď dostaneme v poměrně komplikovaném formátu.
Pokud se zeptáme na interval, který zčásti leží mimo soubor, dostaneme existující
podinterval. A pokud leží celý mimo, dostaneme chybu 416 Range Not Satisfiable
.
Nezapomeňme, že mezi intervalovými dotazy by se soubor mohl změnit.
Často tedy přidáváme If-Match
, případně If-Range
, s nímž server otestuje,
zda objekt odpovídá danému ETagu, a pokud nikoliv, ignoruje Range
a pošle celý soubor.
Chování intervalů u jiných metod než GET
není zatím definováno.
Proxies
Server se smí rozhodnout, že požadavek nevyřídí sám, ale předá ho někomu jinému. Předávajícímu serveru se říká proxy (zmocněnec). Proxies existují dvou základních druhů: dopředné (ty si vybírá klient) a reverzní (na klienta se tváří jako cílový server, ale požadavky předávají back-endovým serverům, třeba kvůli rozdělování zátěže mezi více strojů).
Pro proxies platí speciální pravidla, které nebudeme rozebírat. Alespoň
poznamenejme, že kdykoliv požadavek projde přes proxy, ta by se měla podepsat
do pole Via
a uvést verzi HTTP, kterou mluvila. U reverzní proxy se navíc hodí,
aby předala adresu klienta. Pro to existuje nestandardní, ale běžné pole X-Forwarded-For
.
Kešování
Proxy může kromě předávání požadavků i kešovat odpovědi – pamatovat si u požadavků, které přes ni prošly, jaká byla odpověď, a pokud se nějaký klient zeptá na totéž, rovnou mu poskytnout zapamatovanou odpověď. Keše mohou být sdílené (pro více klientů) nebo soukromé (pro jednoho, třeba zabudovaná keš webového prohlížeče).
Keš si ovšem musí dávat pozor, aby vždy poskytovala relevantní data. Proto pro chování keší
existují poměrně přísná pravidla standardizovaná v RFC 9111 (interní keše v prohlížečích se jimi bohužel ne vždy řídí,
což je častým zdrojem potíží). Především se obvykle kešují jenom odpovědi na GET
a HEAD
(přičemž negativní odpovědi jen velmi opatrně). Sdílená keš neukládá odpovědi
na požadavky, které obsahovaly autorizaci. Aby se nerozbíjel mechanismus domlouvání na
reprezentaci, musí keš při objevení pole Vary
v zakešované odpovědi ověřit,
že se nový požadavek shoduje s původním ve všech polích, jež byla ve Vary
uvedena.
Keš si pro každou odpověď spočítá, jak dlouho ji může poskytovat dalším klientům.
Po uplynutí této doby je odpověď prošlá. Prošlou odpověď může keš revalidovat
– poslat serveru HEAD
nebo podmíněný požadavek, aby si ověřila, že uložená odpověď je stále platná.
Keše smí v některých případech revalidaci vynechat a dávat klientům prošlé odpovědi
(například není-li server zrovna dostupný).
Server může v poli Cache-Control
sdělit instrukce pro kešování (oddělené čárkami):
max-age=N
– odpověď se smí kešovat nejvýšeN
sekund.must-revalidate
– je zakázáno vynechat revalidaci.no-cache
– navzdory názvu je kešování povoleno, ale musí se revalidovat pokaždé. V praxi se implementuje jako zákaz kešování.no-store
– data není povoleno ukládat na permanentní médium (disk).private
– data smí ukládat jen soukromé keše.
Kromě max-age
může server poslat též pole Expires
, kterým uvádí čas (serverový),
do kdy je odpověď platná. Pokud nepošle ani jedno, smí keš dobu platnosti odhadnout.
Klient může také instruovat keše pomocí Cache-Control
, konkrétně:
max-age
ano-store
– jako u serveru.no-cache
– zde opravdu zakazuje kešování.max-stale=N
– klient je ochoten akceptovat prošlá data (která neprošla revalidací), pokud jsou prošlá nejvýšeN
sekund.
Ještě doplníme, že odpověď z keše se doprovází polem Age
, jehož hodnota říká,
kolik sekund uplynulo od zakešování nebo revalidace odpovědi.
Knihovny
HTTP je docela rozsáhlé a implementovat vlastní server nebo klienta dá dost práce a znamená to strávit pár dní detailním studiem specifikací. Proto nejspíš chcete použít nějakou existující knihovnu. Můžeme doporučit například:
- requests pro Python
- libcurl pro C/C++.
- curl, což je program použitelný z příkazové řádky a ve skriptech, velmi praktický při ručním experimentování.
- httpie je další podobný nástroj.
Také vás mohou inspirovat vývojářské nástroje webových prohlížečů, které umí ukazovat hlavičky HTTP požadavků odeslaných prohlížečem i odpovědí na ně.