pátek 30. září 2005

Využití continuations k tvorbě webových aplikací

Interakce mezi klientem a serverem je ve webových aplikacích podřízena bezestavovosti HTTP protokolu. Uživatel vytvoří požadavek, například klikne na odkaz nebo odešle formulář, a server poskytne odpověď v podobě HTML stránky. Při dalším požadavku toho samého klienta server netuší, že nějaká komunikace mezi nimi již proběhla. Představme si, že chceme realizovat objednávku zboží pomocí několika formulářů.

 
  Klient                     Server
         ** Stranka 1 **           
   <-- tady je formulář 1
   tady jsou vyplněná data    --> 
        ** Konec stranky 1 **
      
        ** Stranka 2 ** 
kontrola dat serverem (nevalidní data) návrat na stranku 1
   <-- tady je formulář 2
   tady jsou vyplněná data    -->             
        ** Konec stranky 2 **       

        ** Stranka 3 ** 
 kontrola dat serverem (nevalidní data) návrat na stranku 2
   <-- stránka s informací o odeslání objednávky k zpracování               
        ** Konec stranky 3 **  
 

Pro realizaci tohoto scénáře použijeme určitě HTTP session a obslužnou logiku projednotlivé formuláře rozdělíme do více controllerů, anebo použijeme jeden s hromadou testů na předchozí kroky. Vykoupení z bezestavovosti HTTP protokolu za pomoci session, má tu nevýhodu, že musíme s každým klientským požadavkem restaurovat stav předchozí komunikace z hodnot, které máme na session uložené, abychom dokázali rozhodnout o dalším zpracování (pokračuj a nebo se vrať o krok zpět).

Zkusme se zamyslet, jak by zpracování každého požadavku vypadalo v případě, že by server mohl s každým zasláním dat klienta navázat na předchozí stav komunikace a pokračovat dále.

 
1:   function novaObjednavka(request){
2:      boolean success = false;
3:      do{
4:        sendPageAndWait("formular1");
5:        success = validate(request);
6:      } while (!success)
7:
8:      success = false;  
9:  
10:     do{
11:       sendPageAndWait("formular2");
12:       success = validate(request);
13:     } while (!success)
14:  
15:     zpracujObjednavku();
16:     sendPage("objednavkaPrijata");  
17: }
 
  1. Klient požádá server o založení nové objednávky, ten vyhodnotí (je úplně lhostejno jak), že ji obsluhuje funkce novaObjednavka parametr request, představuje vlastní HTTP požadavek.
  2. Vykonávání programu se zastaví na řádku 4. Funkce sendPageAndWait je nativní funkcí serveru a způsobí to, že server vrátí první formulář a předá řízení klientu. Parametrem se serveru předává informace o tom, kterou stránku má klientovi vrátit.
  3. Klient po nějaké době vyplní formulář a odešle formulář na server, tím pádem server přebírá řízení. Server ví, že naposledy skončili s klientem na řádku 4, a proto pokračují řádkem pět. Na řádku 5 proběhne validace (vrací boolean hodnotu) formuláře
    1. pokud je úspěšná - posune se vykonávání přes řádek 8 až na řádek 11. Opět vstupuje do hry známá funkce sendPageAndWait, klientovi je společně s druhým formulářem předáno řízení.
    2. pokud není úspěšná - posune se vykonávání díky splněné podmínce cyklu zpět na řádek 4 a klientovi je společně s prvním formulářem opět předáno řízení.
  4. Při dalším předání řízení server opět ví, kde skončila poslední interakce. V závislosti na tom se pokračuje buďto na řádku 5 nebo 12.
  5. Interakce , která probíhá při založení nové objednávky, je ukončena zpracováním objednávky (uložení do databáze apod.) na řádku 15 a předáním řízení klientovi na řádku 16.

Jak je vidět na první pohled, kód je velice dobře čitelný a nemusíme vynakládat veliké úsilí proto, abychom s každým požadavkem restaurovali předchozí stav a rozhodovali se, jak dále pokračovat. Problém použitého postupu je v tom, jak realizovat ono přerušení, vyvolané funkcí sendPageAndWait a především jak jej později uvolnit a pokračovat dále ve zpracování ve chvíli, kdy klient poskytne data. Tu magickou část skládanky, která nám umožní realizovat přerušení a jeho uvolnění, představuje tzv. Continuations.

Co je continuation?

Continuation je vypůjčený výraz z jazyku Scheme a označuje se pomocí něj funkce nebo objekt, který reprezentuje aktuální stav (stacktrace a hodnoty proměnných) programu během jeho vykonávání. Představte si, že pustíte program v debug módu a vykonávání programu se zastaví na breakpointu. Stacktrace a lokální proměnné, které vidíte v debuggeru, představují continuation pro aktuálně zahaltovaný program.

Když se na continuation podíváte z druhé strany, tak představuje "zbytek" toho, co se má v programu udělat. Představme si, že se právě vykonává řádek číslo pět níže uvedeného programu. Pro ilustraci následuje pod kódem screenshot se vším podstatným.

 
  1: public class Foo{
  2:  public static void main(String args[]){
  3:    int foo = 1;
  4:                    int hoo = 1;
  5:                    int bar = foo + hoo;
  6:                    System.out.println(bar);  
  7:  }
  8:  }
 

Obrázek z debuggování programu

Continuation vypovída nejen o tom, v jakém stavu se nachází vykonávání programu, ale také jak bude vykonávání dále pokračovat (v našem příkladě se provede řádek šest). Díky tomu, že continuation poskytuje informaci o aktuálním stavu a o tom jak se z něj bude pokračovat, je možné udělat přerušení (pause) běhu programu a posléze jeho budoucí spuštění (resume) od bodu přerušení. Stačí nám k tomu pouze získat Continuation objekt či funkci.

Bohužel Java nenabízí, narozdíl od jiných programovacích jazyků, žádné standardní prostředky k tomu, abychom jsme mohli s Continuation jakkoliv pracovat. Pokud je mi známo, tak ani .NET či PHP podporu pro práci s Continuation nemá. I když Java nemá žádné standardní prostředky, Continuation v ní lze použít!

Z těch známějších frameworků má podporu Continuation web application framework RIFE, ta je popsána v článku RIFE Web continuations. Autoři frameworku RIFE se minulý týden rozhodli vyseparovat jejich continuation engine do samostatného subprojektu, který bude na zbytku RIFe nezávislý, viz Announcing RIFE/Continuations, pure Java continuations for everyone.

Dalším frameworkem je Apache Cocoon, který umožňuje napsat flow script s podporou continuations jak v Jave, tak v JavaScriptu (Rhino engine). Vřele doporučuji článek Abhijita Belapurkara Use continuations to develop complex Web applications, kde najdete rozšiřující informace, které se mi do tohoto článku nevešly. Ke Cocoonu ještě přidávám zajímavý článek Rhino with Continuations.

Pokud narazíte na informaci, že servletový kontejner Jetty má podporu Continuations, tak tomu nemusí být pravda. Minimálně to, co prezentoval Greg Wilkins v článku Jetty 6.0 Continuations - AJAX Ready!, není continuations, nýbrž jakási forma request parkingu, viz diskuse na TSS.