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.

pátek 20. listopadu 2009

WinStrom - krásný příklad REST API

Tak jsem se zase rozhodl vrátit se k oRESTování. První příspěvek bude o tom, že i u nás v republice umíme udělat pěkné RESTózní API.
Při shánění materiálů jsem dnes narazil na REST API k účetnímu systému WinStrom. A je opravdu pěkné. Ale má i své mouchy.

Samozřejmě jako transportní protokol použili HTTP. Podporují HTTPS a HttpBasic - což ve spolupráci s přístupovými právy dostatečně zabezpečuje bezpečnost dat. Používají klasické operace GET, POST, PUT a DELETE (PUT a POST zeqivaletnili, což ale samozřejmě není nic proti RESTóznosti API), používají celkem hojně HTTP status.

URL jsou taky HTTP. Celkem logicky nejsou neprůhledná - mají svoji logiku.

Pro práci s kolekcí jedné evidence to jsou:
/ID.firmy/typevidence{/[reports|properties|relations|filtr]}

a pro práci s konkrétní položkou
/ID.firmy/typevidence/ID.záznamu{.formát}{/podevidence} (tady si nejsem jist, jestli ten formát je povinný nebo není, IMHO by být neměl, ale z docky plyne, že je)

Co se týká RESTových operací, tak si vystačí se čtyřmi - klasický CRUD.

CREATE se volá na kolekci. Návratová hodnota je buď neúspěch nebo úspěch. Úspěch v sobě obsahuje identifikátor vytvořeného zdroje.

UPDATE se volá na konkrétní zdroj - tam jdou nová data, zpátky status operace.

DELETE je klasický - jenom jsem nenašel, na co vše se dá volat.

GET je pěkný. Je sice parametrizovaný, ale parametry lze rozdělit do tří kategorií - stránkování a řazení, přidání metadat a (s tím mám kapku problém) XPath na výsledek, jestliže je ve formátu XML (z důvodu omezení přenášených dat)

Až doposud popis a chvála. Teď ale vady na kráse:
  • XPath jsem už zmínil, ale to není zas takový problém
  • Operace CREATE vrátí identifikátor záznamu, nikoli URL, takže URL si uživatel musí sám poskládat, a to je samozřejmě proti RESTu
  • GETové URL obsahuje formát výsledku - zde by bylo na místě použít parametr v QUERY_STRING, ale chápu, že MS Explorer by si s tím nemusel poradit (alespoň v dřevních dobách používal MSIE příponu ke stanovení content-type :-) )
  • A na závěr to nejhorší. PARAMETR filtr pro fitrování dat na kolekci není použit jako parametr, nýbrž jako součást identifikátoru, což je VYSOCE nehezké. Cpát přímo do URL ořezanou obdobu SQL (viz syntaxe Filtrování záznamů) mi přijde více než nechutné.

pondělí 16. listopadu 2009

Lotus Domino na OpenSuSE a Unable to bind to port Port = 6400 Error

Poslední dobou se kromě jiného zase věnuji Notesům (čti notesům :-D) a při poslední instalaci jsem narazil na poměrně fatální problém:
Unable to bind to port Port = 6400

Gůglím a gůglím, až jsem našel http://www-01.ibm.com/support/docview.wss?rs=899&uid=swg21167181 . Přečti si to a nevěřím vlastním očím:
Note: Port 25 is incorrectly reported as port 6400 in the error message because Hex 0x0019 is intepreted as 0x1900, which is 6400.


O co se tedy jedná? Domino se při startu SMTP modulu snaží samozřejmě provést bind() serverového portu číslo 25. Pokud už ale na stroji nějaké to MTAčko běží (např. postfix, který instaluji na všechny servery), je onen port 25, default pro MTA, samozřejmě obsazený. Proto musí naprosto logicky zařvat, že se onen bind() nepodařil a SMTP modul nenastartoval. Jenomže v tom mají kluci drobnou chybku. Domino místo portu 25 zahlásí port 6400. Proč? 25 je v hexa 0x19. 6400 je v hexa 0x1900 ... pochopili? Ano, pravděpodobně opačný 2bajtový indyán. Takže až na Vás Lotus Domino zařve "Unable to bind to port Port=xyzž, podělte ono číslo 256, zbytek vynásobte 256, sečtěte oba výsledky a dostanete správné číslo portu (prostě převeďte do hexa, prohoďte horný a dolní bajt a převeďte zase zpátky do desítkové soustavy). Takže pro port 20480 to bude 80 atd.

Snad to někomu pomůže.