web newbie このページをアンテナに追加 RSSフィード

2007-02-10 disるのはほどほどに。。wtfとか。

[][]Catalystでフォームの値が、ある条件化において正確に取れない件

問題

ご存じのように、

$c->req->param('fieldname')としてフォームの値を参照できますが、

今構築中のアプリで、BodyParameterログをみてみると、

(※リクエスト値があるときにデバッグログに表示されるやつ)

.... 入力されていないフィールドの名前と値('')が取れていない ....

つまり、

$c->req->param('empty_field_name') => undef,
exists $c->req->params->{'empty_field_name'} => False

っておおっ??


普通は困らないのかもですが、

今はHTML::FillInFormを使う事前提の組み方なので、

空でもなんでも、値(というか、そのフィールド名)が検知できないと

色々××なことになるのです。

これはアカンということで調査。

結果

Catalyst自身の問題かなっと思ったけど.....

問題の箇所は別のところでした。

HTTPリクエスト BodyをパースするHTTP::Body::MultiPartモジュールが、

空の値をもったフィールドが取り除いてしまっていた、

というのが原因でした。。

詳細は以下に。お時間あれば一読していただければと。


Catalystのリクエスト処理を追う

CatalystHTTPリクエストがくるとまずはprepareフェーズに入ります。

最初にcontext( あの$c )を準備して、次に、リクエストを解析するprepare_*メソッドを順次実行します。

$c->prepare_request(@argument);   # $ENVを受け取る
$c->prepare_connection;            # HTTPかHTTPSか判定してごにょ
$c->prepare_query_parameters;     # $ENV{QUERY_STRING}をパースしてparamsにセット
$c->prepare_headers;               # HTTPリクエストヘッダを解析してセット
$c->prepare_cookies;               # 受け取ったヘッダからCookieを取り出してセット
$c->prepare_path;                   # BaseURIなどをセット(PROXYで動かしても動くような処理も)
                                      #基本的には「GET」ではここまで
# On-demand parsing
$c->prepare_body unless $c->config->{parse_on_demand}; #HTTPリクエストBODYを解析してparamsにセット

Catalyst::Engine(::*)系で上のような処理を行います。

上記の問題は、POSTでしか(しかも特定のフォーム)起きていないこともあり、prepare_bodyメソッドに注目。


Catalyst::Engine

sub prepare_body {
    my ( $self, $c ) = @_;
    my $type = $c->request->header('Content-Type');

    $c->request->{_body} = HTTP::Body->new( $type, $self->read_length );

    while ( my $buffer = $self->read($c) ) {
        $c->prepare_body_chunk($buffer);
    }
}

prepare_body_chunk($buffer)

sub prepare_body_chunk {
    my ( $self, $c, $chunk ) = @_;
    $c->request->{_body}->add($chunk);
}

端折ってるけど、重要な部分を取り出すとこんな感じ。


HTTP::BODYモジュールを使用して、リクエストBodyのパースをし、

読み込んだHTTPリクエストBodyを

そのモジュールのaddメソッドに渡してパラメータを取得しています。


HTTP::Bodyをnewして、addする。ここに問題が潜んでいました。


HTTP::Bodyに潜む問題

HTTP::Bodyでは、newメソッド内で、エンコードタイプ($type)に合わせて読み込むモジュールを変えています。

読み込むモジュールエンコードタイプ
HTTP::Body::UrlEncodedapplication/x-www-form-urlencoded(標準)|
HTTP::Body::MultiPart multipart/form-data
HTTP::Body::OctetStream上のどちらでもなかったら

基本的なPOSTでは、エンコードタイプは標準と記されたものになるため、

$c->body->{_body}には、HTTP::Body::UrlEncodedが、newされます。

また、ファイルをアップロードするときなど、

form にenctype="multipart/form-data"を指定しているときには、

HTTP::Body::MultiPartがnewされることになります。

そして、addメソッドでは、それぞれのモジュールのspinメソッドが呼ばれます。


HTTP::Body::UrlEncodedは特に問題ないのですが、

(つまり、普通のPOSTでは$c->req->paramsは正常)

一方、HTTP::Body::MultiPartにありました、問題。


sub spin {
    my $self = shift;
    while (1) {
        if ( $self->{state} =~ /^(preamble|boundary|header|body)$/ ) {
            my $method = "parse_$1";
            return unless $self->$method;
        }
    }
}

MultiPartでのspinは、MultiPartとして渡ってくるデータ(*1)をパースし、

その中の実データ部分(body)を取得しています。

取得したものを $self->parse_bodyすることによって、

次のデータをセットします。


$self->{part}{done}: 読み込み完了フラグ

$self->{part}{size}: bodyのデータサイズ

$self->{part}{data}: 読み込んだデータ部分

そして、最後に handlerを呼ぶのですが、ここ注目!


sub handler {
    my ( $self, $part ) = @_;

    # skip parts without content
    if ( $part->{done} && $part->{size} == 0 ) {
        return 0;
    }else{
         $self->param( $part->{name}, $part->{data} );
    }
}

(かなり省略)


おっと、、、

$part->{size} == 0のときには、

paramにセットしないで、return 0しちゃってます。

ここだ。

結論

というわけで、

formで method="POST" enctype="multipart/form-data"
を指定してるときには、
HTTPリクエストのパーサー HTTP::Body::MultiPartの中で
sizeが0のパラメータは除去されている

というオチでした。

実際に、このreturn 0を除去したらきちっと取得できるようになりました。

うーん、、、なんでここでreturn 0することにしたのか...



(*1)備考 [POST時のHTTPリクエストのBodyの様相]

(i) application/x-www-form-urlencoded(通常)の場合

Content-Type: application/x-www-form-urlencoded

Content-Length: 58

user_name=yupug&user_sex=man

(ii) multipart/form-dataを指定したときのリクエスト(Content部分)

Content-Type: multipart/form-data; boundary=---------------------------132067928117778102532114132971
Content-Length: 1421
-----------------------------132067928117778102532114132971
Content-Disposition: form-data; name="user_name"

yupug
-----------------------------132067928117778102532114132971
Content-Disposition: form-data; name="user_sex"

man
-----------------------------132067928117778102532114132971
Content-Disposition: form-data; name="user_image"; filename="user.jpg"

Content-Type: image/jpeg

(binary)


-----------------------------132067928117778102532114132971

TOTOTOTO 2007/04/19 12:10 JS分からないので、
こっちの方が面白かったです ><
がんばれ2年目です!

yupugyupug 2007/04/20 03:10 僕もJSはまだ駆け出しで、主にはサーバ側やってます。
でも、新しいことを始めると主業務のところの理解も深まるのが不思議なところで、
お互い頑張ってきまひょー★

スパム対策のためのダミーです。もし見えても何も入力しないでください
ゲスト


画像認証

Connection: close