― Web Technology and Life ―

PerlモジュールFurlから学ぶ省エネコーディング

2011-06-25
いいなーと思ったPerlの書き方を「軽量で速い URL fecher」というFurlの0.32バージョンから簡単にピックアップしていきます。Furlを使うこと自体がエコですね。また、使う側の労力としていろいろなPerlハック的なものを覚えておく大変省エネですね。

Furlから学ぶPerlコーディング

ハッシュリファレンスでもハッシュでも渡せる気遣い

#Furl::HTTP l.40
sub new {
    my $class = shift;
    my %args = @_ == 1 ? %{$_[0]} : @_;

    ...
}

#使う側 ハッシュ
Furl::HTTP->new(
    GET     => 'http://yahoo.co.jp/',
    timeout => 30,
);

#使う側 ハッシュリファレンス
my $args = +{
    GET     => 'http://yahoo.co.jp/',
    timeout => 30,
};

Furl::HTTP->new($args);

これは実務でももりもり使えますね!

オブジェクトをblessすると、メソッドを継承させる

#オブジェクトをbless  Furl l.14
sub new {
    my $class = shift;
    bless \(Furl::HTTP->new(header_format => Furl::HTTP::HEADERS_AS_HASHREF(), @_)), $class;
}

#Furl::HTTPのメソッドをFurlに継承 Furl l.20
{
    no strict 'refs';
    for my $meth (qw/get head post delete put/) {
        *{__PACKAGE__ . '::' . $meth} = sub {
            my $self = shift;
            local $Carp::CarpLevel = $Carp::CarpLevel + 1;
            Furl::Response->new(${$self}->$meth(@_));
        }
    }
}

ちょっとわかりづらいので、実務ではそれほど使わないと思いますが、なんとなくカッコイイですね!

特に、特定のメソッドを該当クラスに生やす*{__PACKAGE__ . '::' . $meth}のくだりは良く見かけますね!

URIモジュールを使わずにurlを分解

#Furl::HTTP l.150
sub _parse_url {
    my($self, $url) = @_;
    $url =~ m{\A
        ([a-z]+)       # scheme
        ://
        ([^/:]+)       # host
        (?: : (\d+) )? # port
        (?: (/ .*)  )? # path_query
    \z}xms or Carp::croak("Passed malformed URL: $url");
    return( $1, $2, $3, $4 );
}

URIモジュールのメソッドをいちいち思い出すのめんどいときとかないけど、ちょっとした正規表現の勉強になりますね。

Universal::Requireとかを使わずにモジュールをload

#Furl::HTTP l.128
sub _requires {
    my($file, $feature, $library) = @_;
    return if exists $INC{$file};
    unless(eval { require $file }) {
        if ($@ =~ /^Can't locate/) {
            $library ||= do {
                local $_ = $file;
                s/ \.pm \z//xms;
                s{/}{::}g;
                $_;
            };
            Carp::croak(
                "$feature requires $library, but it is not available."
                . " Please install $library using your prefer CPAN client"
            );
        } else {
            die $@;
        }
    }
}

#使っている側 Furl::HTTP l.221
if ($host =~ /[^A-Za-z0-9.-]/) {
    _requires('Net/IDN/Encode.pm',
        'Internationalized Domain Name (IDN)');
    $host = Net::IDN::Encode::domain_to_ascii($host);
}

実務ならUniversal::Requireを使うでしょうが、このあたりはどういう意図があるのか興味深いですね。

ショートカットやエイリアスを用意してあげる

# ショートカット Furl::Response l.28
sub content_length   { shift->headers->content_length() }
sub content_type     { shift->headers->content_type() }
sub content_encoding { shift->headers->content_encoding() }
sub header           { shift->headers->header(@_) }

#エイリアス Furl::Response l.24
sub status { shift->code() }
sub body   { shift->content() }

ハッシュの特定のキーのバリューを一気に受け取る

#Furl::HTTP l.24
my %args = @_;

....
my ($scheme, $host, $port, $path_query);
....
($scheme, $host, $port, $path_query) = @args{qw/scheme host port path_query/};

この書き方ははじめて見ました。なかなか使うケースはないと思いますが頭の隅においておきたいですね。

こんなLWP::UserAgentのコードをFurlにするのに挫折

#!/usr/bin/env perl
use strict;
use warnings;
use HTTP::Cookies;
use HTTP::Request;
use LWP::UserAgent;

my $res = fetch('http://www.google.co.jp/');

print $res->content;

sub fetch {
    my $url = shift;

    my $cookies = HTTP::Cookies->new(
        file     => "./lwp_cookies.dat",
        autosave => 1,
    );
    $cookies->save;

    my $req = HTTP::Request->new(GET => $url);

    my $ua = LWP::UserAgent->new(
        max_size   => 1000000,
        cookie_jar => $cookies,
    );

    my $res = $ua->request($req);
}

これをFurlで書いて「こんな場合はLWP::UserAgentのほうがシンプルだよね」って話にしようとして、書いていたんだけど、なんか動かず挫折。。。ちなみに、FurlのドキュメントでCookieを渡す方法に、request_with_http_requestってメソッドが使われているけどもうないから、requestを使う。ちなみに、うまくいかなかったのは、以下。

my $f = Furl->new;

my $cookies = HTTP::Cookies->new(
    file     => "./lwp_cookies.dat",
    autosave => 1,
);

my $req = HTTP::Request->new(GET => $url);

$cookies->add_cookie_header($req);

my $content = '';
my $limit = 1_000_000;
my %special_headers = ('content-length' => undef);

my $res = $f->request(
    $req,
    special_headers => \%special_headers,
    write_code      => sub {
        my ( $status, $msg, $headers, $buf ) = @_;
        if (($special_headers{'content-length'}||0) > $limit || length($content) > $limit) {
            die "over limit: $limit";
        }
        $content .= $buf;
    },
)->as_http_response;

warn $res->content;#undefが返ってくる・・・

write_codeの書き方がうまくいっていないっぽい・・・。

【2011-06-26追記】
Furl::CallbackStreamのappendメソッドって、return $selfじゃなくてreturn $self->{cb}->($partial)な気がするんだけど、どうでしょうか?

最後に

Furl::HTTPのrequestはとても重厚ですね。後半部分はHTTPの詳しいところがよくわかっていないところもあり、よくわかりませんでした。もう少し勉強して読み直したいところですね。

この記事自体は、ホントはモダンPerlモジュールから学ぶPerlの小技とかにして、Tengとか他のモジュールからもいろいろ取り出してこようと思っていたんだけど、量とネタの関係上、こんなタイトルになってしまいました。そのうちシリーズ化しようかな。とにかく、「こんな書き方できるんだ!」っていうのを覚えていくと行数をすくなくできたり、クラスやメソッドをきれいにまとめられたりして、書くときも大変な思いをしなくて済むし、読む方も典型的な書き方であればすぐに飲み込めるので、どんどんチェックしていきたいですね!

Perl hachioji.pm update_at : 2011-06-27T22:50:18
hirobanex.netの更新情報の取得
 RSSリーダーで購読する   
blog comments powered by Disqus