HTTP požadavky a odpovědi – Část 1.
Nette nabízí pro práci s HTTP dvě vrstvy abstrakce.
První, nízkoúrovňovou, obstarávají třídy ze jmenného prostoru
Nette\Http
. Nabízejí příjemné API pro práci s HTTP
hlavičkami, vyžádanou URL, příchozími parametry a soubory, nebo nastavení
číselného kódu odpovědi. Od této vrtvy jsme v Nette aplikaci téměř
odstíněni.
Druhá vrstva svým způsobem zapouzdřuje tu první. Třídy se nacházejí
ve jmenném prostoru Nette\Application
a využijí se, pokud
pracujete s Nette aplikací (a teď myslím doslovně
Nette\Application\Application
) a jejími presentery a akcemi.
Objekt třídy Nette\Application\Request
je výsledkem práce URL
routeru. Router (Nette\Aplication\IRouter
) vezme HTTP požadavek a
podle definovaných rout ho přetaví na Nette\Application\Request
.
Ten nese informaci, jaký :modul:presenter:akce
se má vykonat.
Dále moc zajímavý není. Daleko zajímavější je
Nette\Application\IResponse
. Zejména té se bude věnovat tato
krátká série třech článků.
HTTP odpovědi z aplikace
Než se k manipulaci s Nette\Application\IResponse
dostaneme,
shrnu nástroje pro ovládání HTTP protokolu, které máme při běhu
presenteru k dispozici. Potomci třídy
Nette\Application\UI\Presenter
, pravděpodobně tedy všechny naše
presentery, jich mají k dispozici opravdu hodně.
Metodu redirect()
určitě znáte z tutoriálů. Co ale
následující kód přesně udělá z pohledu HTTP protokolu?
public function actionDefault(): void
{
$this->redirect(':Front:Homepage:help');
}
V konečném výsledku nastaví kód HTTP odpovědi na 302 (nebo 303),
nastaví HTTP hlavičku Location
na URL, kterou vytvoří podle
vzoru :Front:Homepage:help
, ukončí běh presenteru a pošle HTTP
odpověď klientovi. Klient, například webový prohlížeč, si odpověď
přečte a načte novou stránku z URL, kterou dostal. Pro nás velmi
pohodlné.
Dále tu máme metodu redirectPermanent()
, která funguje
stejně, ale jako kód odpovědi použije 301, tedy trvalé přesměrování.
Vybrat ten správný kód je někdy věda.
A do třetice, metoda redirectUrl()
. Metodě předáváme URL a
volitelně kód přesměrování. Například:
public function actionDefault(): void
{
$this->redirectUrl('https://my.new.web/');
}
Další pomocník, metoda error()
. Vše se zase točí kolem
návratového kódu HTTP odpovědi. Metoda má jako výchozí 404, tedy
„stránka nebyla nalezena“. Kdy metodu error()
použít?
Záleží na vás. Dejme tomu, že máme blog a někdo si chce přečíst
neexistující článek https://example.com/read/123456
.
public function actionRead(int $id): void
{
try {
$article = $this->articles->get($id);
} catch (ArticleNotFoundException $e) {
# Článek neexistuje, co teď?
# Můžeme uživatele upozornit a přesměrovat na seznam článků.
$this->flashMessage("Sorry, article with ID '$id' does not exist. Try another one.", 'warning');
$this->redirect('Blog:list');
# Anebo použít HTTP kód 404 - Not Found
$this->error("Article with ID '$id' was not found.");
}
}
Největší rozdíl je v tom, co se zobrazí uživateli. V prvním
případě seznam existujících článků, v případě volání
error()
se zobrazí ErrorPresenter a chybu 404 navíc zaloguje
webserver. To se může hodit. Volání error()
mi přijde
korektnější, lépe vystihuje situaci.
EDIT: Jakub Vrána v komentářích připomněl, že přesměrování z neexistujícího článku je antipattern. Znepříjemní to ruční zadávání URL při překlepu a chybné odkazy vyhledávače zaindexují jako seznam článků. Jednoznačné argumenty pro použití 404.
Jako druhý parametr metody error()
můžeme uvést číslený
kód chyby. Chybových HTTP kódů jsou mraky. Tak například
„forbidden“:
public function actionRead(int $id): void
{
try {
$article = $this->articles->get($id);
} catch (PermissionDeniedException $e) {
$this->error('You Shall Not Pass!', Nette\HTTP\IResponse::S403_FORBIDDEN);
}
}
Další metoda presenteru slouží pro odeslání dat ve formátu JSON.
Jmenuje se sendJson()
a použití nemůže být
intuitivnější:
public function actionGetUserInfo(string $username): void
{
$this->sendJson([
'username' => ...,
'firstName' => ...,
'lastName' => ...,
]);
}
Nemusíme se o nic starat. Pole se správně převede do JSON formátu,
nastaví se správné HTTP hlavičky Content-Type
a
Content-Length
, klient bude spokojený.
Další dvě metody presenteru, getHttpRequest()
a
getHttpResponse()
. Názvy trochu napoví. Vracejí objekty
Nette\Http\IRequest
a Nette\Http\IResponse
, ono
nízkoúrovňové HTTP API, o kterém jsem se zmínil v úvodu. Díky všem
těm metodám, které jsem výše popsal, je skoro nepotřebujeme. Ale jeden
zjednodušený příklad bych měl.
Představte si, že náš blog, naše Nette aplikace, má poskytnout jeden REST API endpoint. Bude vracet jméno autora, nadpis článku a čas publikování. Nebude ale pro každého, jen pro klienty s tajuplným tokenem.
public function actionArticleData(int $id): void
{
$request = $this->getHttpRequest();
if ($request->getHeader('Token') !== 'eb0f21d63594e58d6b9995a7d2ac156c') {
$this->error('Invalid or missing access token.', Nette\HTTP\IResponse::S403_FORBIDDEN);
die();
}
try {
$article = $this->articles->get($id);
} catch (ArticleNotFoundException $e) {
$this->error('Article not found.');
die();
}
$this->sendJson([
'id' => $id,
'author' => $article->author->fullName,
'title' => $article->title,
'publishedAt' => $article->publishedAt->format(\DateTime::ISO8601),
]);
}
Asi jste si všimli volání die()
po
error()
. Není nutné. Všechny metody o kterých jsem dosud psal
ukončí zpracování a die()
se ani nezavolá. Používám to,
protože při prvním pohledu do kódu metody vidím, kde její běh může
skončit.
A to je vše. Zbývá už jen metoda sendResponse()
a na tu se
podíváme v dalším dílu.
Komentáře
Přesměrování při nenalezení článku je anti-pattern. Co když píšu URL ručně a udělám překlep? Přesměrování mi ho nedovolí upravit. Ale je s tím i mnoho dalších problémů, např. se neplatné odkazy zaindexují jako odkaz na seznam článků.
Volání die() je anti-pattern kvůli testům.
Díky za doplnění, souhlasím s oběma body.
Moc pěkné shrnutí.
Chcete-li odeslat komentář, přihlaste se