Pixel Pedals of Tomakomai

北海道苫小牧市出身の初老の日常

LWPとWWW::Mechanizeのバージョンが揃ってなかった

不具合が起こったので自分のためのメモ。

現象

$mech->back すると、以降Cookieが飛ばない

原因

LWPとWWW::Mechanizeのバージョンの相性。

  • LWP:5.825 (16 Feb 2009)
  • WWW::Mechanize:1.34 (10 Dec 2007)

Mechanizeを最新にするか、LWPがMechanizeと同じ頃のバージョンになってれば大丈夫です。

備考

この不具合は、後述するようにMechanizeの1.51_01の方針変更によって修正されています。

1.51_01     Thu Nov  6 15:13:03 CST 2008
========================================
[FIXES]
Page history is now working much better.  The $mech->back() method
should behave more like a browser now.  Most notably, it no longer
restores the cookie state, just like your browser doesn't restore
cookie state when you page back.  It also should use much less
memory.

また、後述する clone() でアクセサを経由せずにcookie_jarを上書きしている問題は、1.51_03 で直されてます。


簡単な解説

WWW::Mechanize の history は、当時のバージョンでは*1 $self をclone することで実装してますが、LWP::UserAgentではcookie_jarのコピーには対応してません。

# UserAgent.pm 5.824

sub clone
{
    my $self = shift;
    my $copy = bless { %$self }, ref $self;  # copy most fields

... 略 ...

    # no easy way to clone the cookie jar; so let's just remove it for now
    $copy->cookie_jar(undef);

    $copy;
}

そこでMechanizeではこれに対応するため、cloneをオーバライドしてcookie_jarのコピー(正確には共有)に対応させているのですが、その中で以下のようにアクセサを経由せずに直接突っ込んでます。

# Mechanize.pm 1.34

sub clone {
    my $self = shift;
    my $clone =  $self->SUPER::clone();
    $clone->{cookie_jar} = $self->cookie_jar;
    return $clone;
}

ところが、最近のLWP::UserAgentでは、Cookieの埋め込みをhookを利用して実装しているため、アクセサ経由でcookie_jarをセットしないとhookがかからなくてうまく動きません。

# UserAgent.pm 5.824

sub cookie_jar {
...略...

	$self->{cookie_jar} = $jar;
        $self->set_my_handler("request_prepare",
            $jar ? sub { $jar->add_cookie_header($_[0]); } : undef,
        );
        $self->set_my_handler("response_done",
            $jar ? sub { $jar->extract_cookies($_[0]); } : undef,
        );
    }

...略...
}

昔のLWP::UserAgentだとこのような仕掛けはなかったので、Mechanize側の上記のようなコードに対してもうまく動きます。

# UserAgent.pm 2.036 (LWP 5.808 / 05 Aug 2007)

sub cookie_jar {
    my $self = shift;
    my $old = $self->{cookie_jar};
    if (@_) {
        my $jar = shift;
        if (ref($jar) eq "HASH") {
            require HTTP::Cookies;
            $jar = HTTP::Cookies->new(%$jar);
        }
        $self->{cookie_jar} = $jar;
    }
    $old;
}

また、最新のMechanizeを使った場合は、最初に書いたようにhistoryがcloneに依存してないので、問題なく動きます。

*1:今はrequestとresponseだけ持ってるみたいです