やはり必要以上にゆるいと感じたので。

リダイレクトはエラー扱いに

以下、問題と感じたところ。

  • $ua->is_success は300番台でも成立する
  • LWP に限らず User Agent のほとんどはデフォルトではリダイレクト先まで見に行ってしまう

このままだと以下のような場合もOKになってしまう。

% lwp-request -S -mHEAD http://www.dan.co.jp/~dankogai/hijitsuzai
HEAD http://www.dan.co.jp/~dankogai/hijitsuzai --> 302 Found
HEAD http://blog.livedoor.jp/dankogai/ --> 200 OK
Connection: close
…

死活確認の場合、通常のブラウザーとしては正常なリダイレクトの追っかけはしない方がいい。

というわけで、まずはリダイレクトをエラーとして扱うようにしてみた。元記事ではmailしていたが、ここでは標準出力するにとどめている。

#!/usr/bin/perl
use strict;
use warnings;
use LWP::UserAgent;
use YAML ();
use autodie;

my $file = shift;
my $config = YAML::LoadFile( $file || \*DATA );

my $ua = LWP::UserAgent->new(
    agent        => 'Monita/0.01',
    timeout      => $config->{timeout},
    max_redirect => 0
);

for my $url ( @{ $config->{url} } ) {
    my $res = $ua->head($url);
    report($res) unless $res->code =~ /^2/;
}

sub report {
    my $res = shift;
    printf "%s => %s\n", $res->request->uri, $res->status_line;
}

__END__
from: xxxxxx@xxxxxx
to: xxxxxx@xxxxxx
timeout: 5
url:
  - http://www.google.co.jp/
  - http://www.google.co.jp/nonexistent
  - http://hijitsuzai.google.co.jp/
% perl monita.pl
http://www.google.co.jp/nonexistent => 404 Not Found
http://hijitsuzai.google.co.jp/ => 500 Can't connect to hijitsuzai.google.co.jp:80 (Bad hostname 'hijitsuzai.google.co.jp')

出来れば非同期で

さらにもう一つの問題は、死活確認にLWPを使っていることそのものにある。LWPのアクセスは同期的なので、落ちているサイトにアクセスすると最悪でタイムアウト分待たされることになる。元記事の30秒というのは実に長いが、仮に3秒だとしても100URL監視したら300秒、5分もかかることになる。これはぜひ非同期でやりたいところだ。

というわけで、非同期化したのが以下。

#!/usr/bin/perl
use strict;
use warnings;
use AnyEvent;
use AnyEvent::HTTP;
use YAML ();
use autodie;

my $file = shift;
my $config = YAML::LoadFile( $file || \*DATA );

my $cv = AE::cv;
for my $url ( @{ $config->{url} } ) {
    $cv->begin;
    http_head $url,
      recurse => 0,
      timeout => $config->{timeout},
      sub {
        report( $_[1] ) if $_[1]->{Status} !~ /^2/;
        $cv->end;
      }
}
$cv->recv;

sub report {
    my $hdr = shift;
    printf "%s => %d %s\n", $hdr->{URL}, $hdr->{Status}, $hdr->{Reason}
}

__END__
from: xxxxxx@xxxxxx
to: xxxxxx@xxxxxx
timeout: 5
url:
  - http://www.google.co.jp/
  - http://www.google.co.jp/nonexistent
  - http://hijitsuzai.google.co.jp/
http://hijitsuzai.google.co.jp/ => 599 Device not configured
http://www.google.co.jp/nonexistent => 404 Not Found

効果を確かめるために、以下のようなCGIを用意する。指定した秒数だけ待ってから返事をするだけのひどく簡単なものだ。あくまで遅延をシミュレートするだけなので、本番で使ってはいけません。

#!/usr/bin/env perl
use strict;
use warnings;

my ($delay) = ($ENV{PATH_INFO} =~ m{^/(\d+)});
$delay ||= 1;
sleep $delay;
print "Content-type: text/plain;\n\n";
print "Good Morning!\n";

これに対して以下の設定でアクセスしてみた結果、こうなった。

timeout: 5
url:
 - http://localhost/~dankogai/test/slow.cgi/0
 - http://localhost/~dankogai/test/slow.cgi/1
 - http://localhost/~dankogai/test/slow.cgi/2
 - http://localhost/~dankogai/test/slow.cgi/3
 - http://localhost/~dankogai/test/slow.cgi/4
 - http://localhost/~dankogai/test/slow.cgi/5
 - http://localhost/~dankogai/test/slow.cgi/6
 - http://localhost/~dankogai/test/slow.cgi/7
 - http://localhost/~dankogai/test/slow.cgi/8
 - http://localhost/~dankogai/test/slow.cgi/9

同期版

% /usr/bin/time perl monita.pl slow.yaml
http://localhost/~dankogai/test/slow.cgi/5 => 500 read timeout
http://localhost/~dankogai/test/slow.cgi/6 => 500 read timeout
http://localhost/~dankogai/test/slow.cgi/7 => 500 read timeout
http://localhost/~dankogai/test/slow.cgi/8 => 500 read timeout
http://localhost/~dankogai/test/slow.cgi/9 => 500 read timeout
       36.28 real         0.22 user         0.01 sys

非同期版

% /usr/bin/time perl monitae.pl slow.yaml
http://localhost/~dankogai/test/slow.cgi/5 => 599 Operation timed out
http://localhost/~dankogai/test/slow.cgi/6 => 599 Operation timed out
http://localhost/~dankogai/test/slow.cgi/7 => 599 Operation timed out
http://localhost/~dankogai/test/slow.cgi/8 => 599 Operation timed out
http://localhost/~dankogai/test/slow.cgi/9 => 599 Operation timed out
       11.19 real         0.17 user         0.01 sys

それにしてもAnyEventは楽である。fork()で同じ事をしていた頃に比べたら天国みたいだ。

Happy Monitoring!

Dan the Man with Too Many Websites to Manage