Hatena::Grouperlang

檜山正幸のErlang未確認情報 RSSフィード

 | 

2009-04-13 (月)

YAWSのクッキー処理 その1

| 18:47

「YAWSのクッキーセッション」にて:

yaws_apiにあるいくつかの関数を使う。説明をそのうち書くだろう、たぶん。

今日は、次の2つの関数だけ。

  • yaws_api:setcookie/[2-6]
  • yaws_api:find_cookie_val/2

クッキーについての記述は次が原典(つっても詳しくない)。

setcookie関数

  • setcookie(Name, Value, [Path, [ Expire, [Domain , [Secure]]]])

Sets a cookie to the browser.

クッキーのデータは、名前/値ペアの集合なので、基本は NameとValueを指定する。

引数 説明 データ型
Name セットするクッキー項目の名前 アトムまたは文字列
Value 名前に対応する値(内容) アトムまたは文字列
Path そのドメインのどのパスから下で有効になるか文字列
Expire 有効期限(無効になる時刻)文字列、秒単位までの時刻文字列
Domain どのドメインに送るか 文字列
Secure 暗号化するときだけ送るかどうか アトム on, off

このsetcookie関数は、{header,{set_cookie, "foo=bar;"} } のようなSet-Cookie:ヘッダを作って返すだけ。戻り値であるヘッダ指定をそのままYAWSに出力すればよい。関数名は make_cookie_headerとかが適切だったろう。

ブラウザにSet-Cookie:ヘッダが入ったレスポンスが届くと、クッキーの名前/値ペアがローカルストアに押し込まれる。

幸いに、次にクッキーの詳しい解説がある。

YAWSのsetcookie関数は、引数から次のようにしてSet-Cookie:の文字列を作っている。

  • " Domain="++Domain++";"
  • " Expires="++Expire++";"
  • Secureがonなら、" secure;"
  • 最後に "~s=~s;~s~s~s Path=~s", [Name,Value,SetDomain,SetExpire, SetSecure, SetPath]

EShellで実験できる。

305> {header, {set_cookie, X}} = yaws_api:setcookie("User", "hiyama"), io:format("~n~s~n", [X]).

User=hiyama;
ok
306> 

find_cookie_val関数

  • find_cookie_val(Cookie, Header)

This function can be used to search for a cookie that was previously set by setcookie/2-6. For example if we set a cookie as yaws_api:setcookie("sid",SomeRandomSid), then on subsequent requests from the browser we can call: find_cookie("sid",(Arg#arg.headers)#headers.cookie)

The function returns [] if no cookie was found, otherwise the actual cookie is returned as a string.

上記のhttp://www.studyinghttp.net/cookiesによると、クッキー文字列はこんな感じの文字列らしい。

Customer="Tarou_YAMADA"; Part_Number="IBMPC_01";

エスケープすると、 "Customer=\"Tarou_YAMADA\"; Part_Number=\"IBMPC_01\";" ですな。

値がダブルクォートされているのは仕様(構文)ではない気がする。イコールとセミコロンがデリミタで、イコールからセミコロンのあいだが問答無用で値とみなされるようだ。空白は無視されるようだが、名前とイコールの間の空白は許されない。セミコロンをエスケープする方法はないみたい(みたいみたい)。

find_cookie_val(Cookie, A) when record(A, arg) ->
    find_cookie_val(Cookie,  (A#arg.headers)#headers.cookie);

となっているので、第二引数にArgを入れても大丈夫。

find_cookie_val関数は単なる文字列処理関数で、第1引数の名前文字列をキーとする値を探して返すだけ。値が見つからなければ空文字列("" = 空リスト)を返す。ただし、テストするときは、第二引数にArgじゃなくて、文字列のリストを入れることができる。僕は、シェルでテストしてクッキー文字列の構文を推測したのだった。

Erlang文字列処理ってコンナだよ、ってサンプルとしてソースを貼っておく。


%% 第2引数文字列のリスト!

find_cookie_val(_Cookie, []) ->
    [];
find_cookie_val(Cookie, [FullCookie | FullCookieList]) ->
    case eat_cookie(Cookie, FullCookie) of
        [] ->
            find_cookie_val(Cookie, FullCookieList);
        Val ->
            Val
    end.

%% Remove leading spaces before eating.
eat_cookie([], _)           -> [];
eat_cookie([$\s|T], Str)    -> eat_cookie(T, Str);
eat_cookie(_, [])           -> [];
eat_cookie(Cookie, [$\s|T]) -> eat_cookie(Cookie, T);
eat_cookie(Cookie, Str) when list(Cookie),list(Str) ->
    try
        eat_cookie2(Cookie++"=", Str, Cookie)
    catch
        _:_ -> []
    end.

%% Look for the Cookie and extract its value.
eat_cookie2(_, [], _)    -> 
    throw("not found");
eat_cookie2([H|T], [H|R], C) -> 
    eat_cookie2(T, R, C);
eat_cookie2([H|_], [X|R], C) when H =/= X ->
    {_,Rest} = eat_until(R, $;),
    eat_cookie(C, Rest);
eat_cookie2([], L, _) -> 
    {Meat,_} = eat_until(L, $;),
    Meat.

eat_until(L, U) ->
    eat_until(L, U, []).

eat_until([H|T], H, Acc)              -> {lists:reverse(Acc), T};
eat_until([H|T], U, Acc) when H =/= U -> eat_until(T, U, [H|Acc]);
eat_until([], _, Acc)                 -> {lists:reverse(Acc), []}.

 |