Continuity 是個給網頁應用程式所用的函式庫 (library),它很魔奇地處理了「session」這個概念,讓寫程式的人可以不必特別管理 session 內容的存放。

比方說,一個典型的 web cgi counter:

use Continuity;
Continuity->new(port => 5000)->loop;

my $counter = 0;

sub main {
    my ($request) = @_;

    $counter++;
    $request->print("$counter\n");
}

# GET http://localhost:5000/

此處的 main 函式為 Web 程式的進入點。也是 Continuity 唯一需要其使用者去特別定義的函式。在此例中,$coutner 這個全域變數被遞增,然後以 $request->print 的方式,傳回給瀏覽器去顯示。

光這樣也這不覺得它有什麼神奇之處,那麼我們來亂加一下這個 counter 的功能: 當 counter 數到 10 時,要求訪客輸入大名,並在輸入後,顯示「某某某,您中獎了」的訊息,然後再將 counter 重置回 0,重新起算。

實做此功能的程式碼如下:

use Continuity;
Continuity->new(port => 5000)->loop;

my $counter = 0;

sub main {
    my ($request) = @_;

    $counter++;

    if ($counter == 10) {
        prize_winner($request);
    }

    $request->print("$counter\n");
}

sub prize_winner {
    my ($request) = @_;

    $counter = 0;

    my $name = prompt_for_name($request);
    $request->print("$name, you are the winner!");

    $request->next;
}

sub prompt_for_name {
    my ($request) = @_;

    $request->print(<<FORM_FOR_NAME);
<html><body>
<form method="post" action="/">
<p>Tell us your name: <input name="name" type="text" /></p>
<p><input type="submit" value="Send">
</form>
</body></html>
FORM_FOR_NAME

    $request->next;

    return $request->param("name");
}

我將「告知中獎」及「問名字」拆成兩個函式,可以看見,main 函式本身變得相當的好讀。如果這個功能寫成桌面程式的話,也就是這種邏輯。

但請注意,這是個網頁程式,prize_winner 函式為了要問使用者大名,少說也要花去一個 request,並接受 form submit 回來。為何它可以被放在一個 'if' 條件之中 ?

Continuity 施展的魔法在於它通透地包裝了 http request/response,使得網頁程式設計的邏輯,可以跟桌面程式一樣。只有單一進入點,並可在要求使用者輸入時,暫停程式執行(嗯,至少看來很像如此)

暫且不管細節。看程式碼追蹤下去。prize_winner取得使用者大名的方式是呼叫 prompt_for_name 函式,它的傳回值便是使用者輸入的名字。

再追下去,prompt_for_name 則是以 $request->print 送了一個 html form, 裡面有 "name" 這個欄位,然後呼叫 $request->next,然後 $request->param("name") 便會有使用者大名了。

這個 $request->next 就是神奇的地方了。它會使這個程式流程「暫停」,等待使用者輸入(也就是送出表單)。在收到表單之後,繼續執行。

目前這個程式並沒有查使用者是否真的有輸入名字,他/她可能輸入的是空白。如果要再加上: 「檢查是否有輸入名字,沒有的話該就再次要求輸入」這樣的功能,要如何做 ? 答案很驚人: 寫個 while 迴圈去檢查便行:

sub prompt_for_name {
    my ($request) = @_;

    my $name;
    while (!$name) {
        $request->print(<<FORM_FOR_NAME);
<html><body>
<form method="post" action="/">
<p>Tell us your name: <input name="name" type="text" /></p>
<p><input type="submit" value="Send">
</form>
</body></html>
FORM_FOR_NAME

        $request->next;

        $name = $request->param("name");
    }
    return $name
}

這樣的邏輯跟寫終端機用的程式沒什麼兩樣。而且,在這個程式中,完全沒有特別處理 URL / Route / Session 的程式碼。雖然還是要知道一些關於 request 的知識,但像這樣的工具實在是大大地簡化了網頁程式的架構形式。仍然可以用 MVC 架構來整理程式碼,但再也不必把邏輯安排成「造出 Response」為終點的模式了。

我個人覺得這是近幾個月學習各種 Web framework / library 中最令我驚奇的一個工具了。雖說各家工具都是以簡御繁,不過像這種連「繁」在哪裡都不用知道的簡化法,只能說他真是太神奇了。而且目前的實做試起來也很穩定。我試著以 MarkaplText::Template::Extended 來做為樣版系統,也都非常成功。由內附的範例看來,做 ajax-based server push 也不是問題。目前正繼續深入了解中。