Až Vám zčerná obrazovka, přejděte na Linux!

Proč používat Linux

čtvrtek 26. listopadu 2009

Kdy parametrizovaný GET a kdy dvě operace GET a QUERY?

Na můj předchozí blogpost ze série oRESTování reagoval Petr Ferschmann, člen týmu WinStromu. Kromě poděkování za pochvalu ale také píše:
Osobně považuji za ekvivalentní tyto formy zápisu:
/faktura-vydana/(stavUhrK='uhrazeno%27)
/faktura-vydana/?filter=stavUhrK=%3d%27uhrazeno%27
Jak jistě tušíte, nemohu s tímto souhlasit, a proto vznikl tento článeček.

Věc, která vypadá z pohledu HTTP eqivalentně, z pohledu Java kódu eqivalentně, dokonce i z pohledu uživatele, z pohledu RESTu eqivalentní není. Webovému prohlížeči je jedno, které URL uživatel napíše, je to jenom směs znamének. Jestli je na serveru logika udělaná tak, že v metodě doGet() HttpServletu vezmete z request.getRequestPath() jednu část a prohlásíte ji za vyhledávací řetězec (filtr) nebo tutéž část získáte pomocí request.getParameter("filter"), je také celkem buřt. Pro HTTP protokol je to jedno, protože první řádek bude pokaždé vypadat stejně, tedy buď

GET /faktura-vydana/(stavUhrK='uhrazeno%27) HTTP/1.1

nebo

GET /faktura-vydana/?filter=stavUhrK=%3d%27uhrazeno%27 HTTP/1.1

Jak je to ale z pohledu RESTu? Začněměž tím, že si řekneme kousek základní definice RESTu - je to množina N operací, kde N je dostatečně malé číslo, společným pro všechny objekty systému (kterým říkáme zdroje neboli resources). Každý zdroj je unikátně identifikovatelný pomocí URI, za které se považuje buď URL nebo URN.

Tolik základní definice. Teď něco k operacím. Smysl mít několik málo jednotných operací je fakt, že se klient nemusí příliš zajímat o jaký zdroj se jedná a jaké má rozhraní, prostě s ním provede operaci, kterou požaduje, a ta buď půjde nebo nepůjde nebo udělá něco jiného než na jiném zdroji atd. Vzhledem k tomu, že operace se provádějí na zdrojích, které jsou URIčkované, můžeme prohlásit, že parametrem každé obecné operace je URI zdroje.

Těch parametrů ale může být povícero, a to hned ve dvou směrech. Uvedu dva příklady:

RESTová implementace Facebookového "Sdílet" by měla čtyři parametry: URI mojí zdi (tedy URI kolekce, kde se má zdroj (nový záznam o sdílení) vytvořit), URI toho, co chci sdílet, URI autora (ano, URI, Facebook mne musí UNIKÁTNĚ identifikovat!!!), a komentář.

Druhým příkladem budiž operace GET z WinStromu. GET na URL s jedním záznamem má parametr Accept, který definuje formát záznamu, který klient požaduje (XML,PDF,...).

Když jsme si toto řekli, vraťme se k WinStromu a jeho filtru. Každé URI reprezentuje zdroj. Jiný zdroj! Pakliže budeme chápat operaci GET klasicky, tedy "vrať obsah", tak GET na /faktura-vydana/ vrátí obsah kolekce faktura-vydaná, kdežto GET na /faktura-vydana/(stavUhrK='uhrazeno%27) vrací obsah - ČEHO? Z logiky věci zdroje reprezentujícího všechny zdroje z kolekce "faktura-vydana", které mají stav UhrK "uhrazeno%27". Jenomže - co můžeme říct o tom zdroji? Jedná se o zdroj nezávislý? Můžeme ho čapnout a přenést jinam? Jak se vlastně vytváří? Jaké jsou na něm nadefinovány jiné operace než GET? Není to vlastně jenom takový "virtuální" zdroj?

Musím se přiznat, že při slovním spojení "Virtuální RESTový zdroj" zamrazí v zádech. Zkusme se na to podívat z jiné stránky věci. To, co chceme, není IMHO až tak nový zdroj, jako spíše JINÝ POHLED kolekci "faktura-vydana", tedy na zdroj /faktura-vydana/ . Takový pohled, který bude obsahovat pouze takové zdroje, které odpovídají podmínce 'mají stav UhrK "uhrazeno%27" '. Tento pohled pak z hlediska RESTu můžeme udělat na třech místech: na straně klienta, poskytovatele zdroje či interceptorem. Protože chceme dát klientovi maximální komfort a navíc minimalizovat tok dat, předpokládejme, že tento nový pohled budeme budovat na straně poskytovatele zdroje, tedy na serveru.

Jak toho docílit? Velice jednoduše. Rozšíříme operaci GET o nepovinný parametr Filtr (nebo povinný s existující konstantou reprezentující nefiltrování). Na serveru se pak rozhodneme, zda máme či nemáme filtrovat a jak se k tomu dostaneme. Nemáme tak žádný další, tfuj, "virtuální zdroj", tfuj. A jak to v praxi provedeme? Jak to dělá HTTP - k operaci GET na kolekci přidáme parametr filter.

Pojďme ale ještě dál. Onen pohled reprezentovaný parametrem Filtr mění poměrně významně obsah, který klient dostane. Nejde tedy o čistý "jiný pohled na data". Ihned po interní operaci GET se na výsledek provede operace FILTR (a jestli je to uděláno v Javě, v XSLT/XPath či SQL, je celkem buřt). Můžeme tedy tuto dvojoperaci považovat za jednu - FILTEREDGET. GET pak nemá parametr Filtr, tento parametr je parametrem nové operace.

Zeptáte se - no jo, ale když HTTP operaci FILTEREDGET nemá, tak co s tím? Ale to je přece jednoduché, milý Watsone! Tak jak RESTová operace GET není HTTP GET, pouze se na ni mapuje, pak si řekněmež, že si operaci FILTEREDGET namapujeme tak, že URL HTTP GETové operace na kolekci rozšíříme o parametr filter ... :-D

Že je to stejné jako v případě parametru? Ale jděte. Pouze převod do HTTP je stejný. Na straně serveru vám to napoví, že tuto cestu je dobré řešit jinou interní operací. Při vytvoření RESTové (nebo i SOAPové) WebServisy to budete zveřejňovat jako jinou operaci.

Zkusím to teď ještě zobecnit:

Základní otázka Života, vesmíru a vůbec má jasnou odpověď - 42. Nejasná je právě ona otázka.

Otázka, zda cpát či necpát jazyk do URL má také jasnou odpověď - pro všechnu matku přírodu NEE!!!

Jasná otázka, zda použít princip separátních zdrojů, parametrizovanou operaci GET nebo dvě operace GET a FILTEREDGET/QUERY/COKOLI_PODOBNÉHO, jasnou odpověď nemá. Je třeba k tomu přistupovat s mužskou schopností plnohodnotné syntézy či ženskou intuicí:

Rozdílné zdroje použijte tam, kde se opravdu o dva rozdílné zdroje jedná. GET "Aktuální seznam členů CZLUGu", GET "seznam členů CZLUGu počáteční", GET "seznam členů CZLUGu verze 234".

Parametrizujte tam, kde se jedná o jiné formy reprezentace téhož zdroje. GET "Seznam členů CZLUGu"?platny_ke_dni=[puvodni|fcil|YYYY-MM-DD]. Nebo jiný formát záznamu ala WinStrom.

Jinou operaci, pokud se jedná o stejný zdroj dat, ale s až diametrálně odlišným obsahem - typicky vyhledávání. GET "Seznam členů CZLUGu", QUERY "Seznam členů CZLUGu"?query=ti_co_nezaplatili_a_zacinaji_pismenem_fň "

Ufff. Doufám, že to někomu pomůže při rozhodování či pro lepší pochopení principů RESTu.

3 komentáře:

Anonymní řekl(a)...

porad ale nevim, jak se dela autentifikace a jak si ji bezestavovy protokol pamatuje - pomoci hashe ?

Oto 'tapik' Buchta řekl(a)...

OK, příště se budu věnovat sekuritě. V krátkosti asi takto: klient s každým požadavkem posílá data, která jej plně autentifikují a na základě kterých vystaví security provider něco, čemu se říká credentials - identifikace uživatele plus práva ke zdrojům. Existují v principu dva druhy řešení, jak toho dosáhnout: permanentní a dočasné. Permanentní znamená, že uživatel v každém požadavku odešle sdílený klíč nebo jméno a heslo. Dočasné znamená, že se klient autentifikuje vůči security providerovi, dostane dočasný ticket (s dočasnou platností a je tedy třeba jej obnovovat), který odesílá místo jména a hesla. Server si pak vyžádá credentials buď na základě jména a hesla nebo onoho ticketu. Autentifikace se tedy neprovádí jednou pro vždy (v rámci sezení), ale při každém požadavku.

Anonymní řekl(a)...

Diky. Ja tohle vsechno musim teprve dohnat. Uf