Lekce 8 - Standardy jazyka PHP - PSR-7 (Obecná specifikace a proudy)
V předchozí lekci, Standardy jazyka PHP - PSR-6 (cachovací rozhraní), jsme si vysvětlili podstatu cachovacího rozhraní a některé důležité pojmy s ním související.
PSR-7, který se zabývá rozhraním pro HTTP dotazy a odpovědi, je poněkud obsáhlejší a v několika následujících lekcích se budeme věnovat právě jemu.
Pro lepší pochopení standardu si můžeme prohlédnout následující RFC:
Struktura
HTTP protokol je bezesporu základem webu. Webové prohlížeče a HTTP klienti (například cURL) vytváří HTTP dotazy, na které odpovídá server pomocí HTTP odpovědi. Tyto zprávy jsou pro koncového uživatele pro jednoduchost a přehlednost zobecněné, ale pokud nějakou aplikaci vyvíjíme, zpravidla potřebujeme přesně znát jejich strukturu a také způsob, jak s nimi manipulovat.
Každý HTTP dotaz má specifickou strukturu:
POST /path HTTP/1.1
Host: example.com
foo=bar&baz=bat
První řádek obsahuje:
- metodu dotazu,
- cíl (většinou absolutní URI cestu nebo cestu na web) a
- verzi HTTP protokolu
Za ním následuje jedna nebo více hlaviček, prázdný řádek a poté tělo dotazu.
Podobná je i struktura HTTP odpovědi:
HTTP/1.1 200 OK Content-Type: text/plain Toto je tělo odpovědi
První neboli „stavový“ řádek obsahuje v tomto pořadí:
- verzi HTTP protokolu,
- stavový kód a
- tzv. reason phrase
Reason phrase je slovní hodnota a pro člověka stravitelnější vysvětlení daného stavového kódu.
Podobně jako v HTTP dotazu následuje i v odpovědi hlavička, prázdný řádek a tělo odpovědi.
Specifikace
Uvedeme si specifikace zpráv, HTTP hlaviček a proudů (streamů).
Zprávy
Pod pojmem HTTP zpráva je myšlen buď požadavek ze strany klienta
na server, nebo odpověď serveru klientovi. Oba druhy
mají definováno své vlastní rozhraní a sice
Psr\Http\Message\RequestInterface
a
Psr\Http\Message\ResponseInterface
.
Obě tato rozhraní rozšiřují
Psr\Http\Message\MessageInterface
. Zatímco toto výchozí
rozhraní MŮŽE být implementováno přímo, implementátoři (ti, co
rozhraní implementují) BY MĚLI implementovat navazující
Psr\Http\Message\RequestInterface
a
Psr\Http\Message\ResponseInterface
.
Zde jsme si uvedli plnou cestu k rozhraním požadavků i odpovědí. Dále
budeme Psr\Http\Message
pro přehlednost vynechávat.
HTTP hlavičky
Ukážeme si hlavičky case-insensitive, s více hodnotami a také host header.
Case-insensitive hlavičky
HTTP zprávy obsahují case-insensitive hlavičky. Ty jsou
vyhledávány pomocí názvu z tříd implementujících
MessageInterface
nehledě na velikost písmen. Výsledek je
stejný, ať už se snažíme vyhledat foo
nebo třeba
FoO
. Stejně tak se hlavička foo
přepíše, pokud
později nastavíme Foo
:
$message = $message->withHeader('foo', 'bar'); echo $message->getHeaderLine('foo'); // Vypíše se „bar“ echo $message->getHeaderLine('FOO'); // Vypíše se „bar“ $message = $message->withHeader('fOO', 'baz'); echo $message->getHeaderLine('foo'); // Vypíše se „baz“
I když hlavičky mohou být hledány bez důrazu na velikost písmen, původní zápis MUSÍ být implementací zachován, konkrétně při volání
getHeaders().
Některé (ne zcela vyhovující) HTTP aplikace mohou záviset na velikosti písmen, takže je pro uživatele užitečné, aby ji mohl při vytváření HTTP požadavku přesně definovat.
Hlavičky s více hodnotami
Aby bylo možné pracovat s více hodnotami a zároveň zachovat výhody
práce s hlavičkami jako s řetězci, hlavičky mohou být získány z instance
MessageInterface
jako pole nebo string
. Použitím
metody getHeaderLine()
získáme všechny (case-insensitive)
hodnoty hlavičky zřetězené pomocí čárky. Pomocí funkce
getHeader()
získáme pole všech hodnot (opět
case-insensitive):
$message = $message ->withHeader('foo', 'bar') ->withAddedHeader('foo', 'baz'); $header = $message->getHeaderLine('foo'); // $header obsahuje: 'bar,baz' $header = $message->getHeader('foo'); // ['bar', 'baz']
Ne všechny hodnoty odesílané v hlavičkách mohou být zřetězeny pomocí
čárky, například Set-Cookie
.
Pokud pracujeme s těmito konkrétními hlavičkami, jako uživatelé tříd založených na
MessageInterface
bychom MĚLI spoléhat spíše nagetHeader()
namístogetHeaderLine()
.
Host header
V dotazech host header typicky napodobuje host komponentu URI stejně jako host používaný při navazování TCP spojení. Specifikace HTTP ale umožňuje tyto dvě situace od sebe navzájem rozlišit.
Pokud při konstrukci není dodán host header, implementace se MUSÍ pokusit tento header nastavit podle URI.
RequestInterface::withUri()
ve výchozím nastavení nahradí
host header požadavku host headerem odpovídajícím host komponentě
předaného UriInterface.
Je možné zvolit, aby byl původní stav host headeru zachován pomocí
druhého argumentu funkce ($preserveHost
). Pokud tento argument
nastavíme na TRUE
a zpráva už nějaký host header obsahuje,
požadavek ho aktualizovat nebude.
Následující tabulka ilustruje, co getHeaderLine('Host')
v
různých situacích vrátí na požadavek vrácený pomocí
withUri()
s argumentem $preserveHost
nastaveným jako
TRUE
.
REQUEST HOST HEADER ★1 | REQUEST HOST KOMPONENTA ★2 | URI HOST KOMPONENTA ★3 | VÝSLEDEK |
---|---|---|---|
"" | "" | "" | "" |
"" | foo.com | "" | foo.com |
"" | foo.com | bar.com | foo.com |
foo.com | "" | bar.com | foo.com |
foo.com | bar.com | baz.com | foo.com |
★1 – hodnota host headeru před operací
★2 – host komponenta URI vytvořena v požadavku před operací
★3 – host komponenta URI vytvořena pomocí withUri()
Proudy (streams)
HTTP zprávy se skládají z tzv. start-line, hlaviček a těla. Tělo zprávy může být různých velikostí, od jednoho řádku až po extrémně obsáhlá data.
Pokus o reprezentaci dlouhého těla jako řetězce může jednoduše vyústit ve větší obsazení paměti, než bylo původně zamýšleno, protože v ní musí být uloženo úplně celé tělo. Při takovémto pokusu o uložení do paměti, ať už požadavku nebo odpovědi, je znemožněna práce implementace s tělem.
Když zapisujeme do proudu dat nebo z něj čteme, jsou pomocí rozhraní
StreamInterface
detaily o implementaci schovány. V situacích, kdy
je pro nás implementace pomocí řetězce výhodná, mohou být použity
vestavěné proudy jako php://memory
a php://temp
.
StreamInterface
vystavuje své schopnosti pomocí tří
metod:
isReadable()
,isWritable()
aisSeekable()
.
Tyto metody mohou být použity dalšími funkcemi (metodami, rozhraními, …) pro zjištění, jestli proud vyhovuje všem nárokům a podmínkám.
Každá instance proudu má různorodé schopnosti a může být:
- read-only (pouze pro čtení)
- write-only (pouze pro zápis)
- read-write (čtení i zápis)
Také mohou povolit nahodilý přístup (hledání zepředu i zezadu), nebo pouze sekvenční přístup (třeba v případě socketu, pipe (roury) nebo callback-based proudu).
Rozhraní StreamInterface
definuje ještě jednu metodu, a to
__toString()
. Je určena ke zjednodušení získání nebo
emitování celého obsahu těla najednou.
Na rozdíl od ostatních rozhraní týkajících se HTTP zpráv
StreamInterface
nezajišťuje neměnnost dat
(immutability).
Pokud jsou použity tzv. wrappovací funkce, neměnnost je nemožné zajistit, protože jakýkoliv kód, který pracuje se zdrojem, může teoreticky změnit jeho stav (a to včetně polohy kurzoru, obsahu a mnoha dalšího). Autor doporučuje, aby uživatelé při implementaci rozhraní používali pouze read-only streamy pro serverové požadavky a dotazy klienta.
Uživatelé by si měli být vědomi, že instance proudu může být změnitelná (mutable) a jako taková může změnit stav zprávy. Pokud jsme na pochybách, je vhodné vytvořit novou instanci a přiložit ji ke zprávě, abychom stav vynutili.
V další lekci, Standardy jazyka PHP - PSR-7 (Rozhraní HTTP zpráv a proudů), se budeme zabývat rozhraními pro datové proudy a HTTP zprávy, jednotlivými funkcemi a jejich použitím.