This page linked from [ InPerl ]

InPerl:WebAccess

InPerlでWebにアクセスしてあれこれやるときの便利関数群。
Webサイト/Webアプリのテストを作る/実行する時とかに便利、かも。

use YAML; # Dumpが便利。
use LWP;
use HTTP::Cookies;
use HTML::Form;
use HTML::FormatText;
use HTML::TreeBuilder;

use threads;         #以下の3つはサーバ実装のために必要。
use threads::shared;
use IO::Socket;

sub RES{
    return $res;
}

# TEXT : HTML::FormatTextを使ってページを表示。
sub TEXT{
    my $formatter = HTML::FormatText->new();
    $formatter->format(HTML::TreeBuilder->new_from_content($res->content));
}

# DISP : 適当なブラウザを使ってページを表示。
# ブラウザへの指示(``内)は適当に変更すること。
sub DISP{
    _DISP_WRITE();
    `galeon -n /tmp/DISP_temp.html`;
}

sub _DISP_WRITE{
    open(OUT,'> /tmp/DISP_temp.html');
    print OUT $res->content;
    close(OUT);
}

sub GO{
    $history = 0;
    $req = shift;
    $res = $ua->request($req);
    while($res->header('location')){
	my $req = new HTTP::Request( GET=> $res->header('location') );
	$res = $ua->request($req);
    }
    unshift(@history,$res);
    @form = HTML::Form->parse($res);
    $form = $form[0];
    SERVER_START();
}

sub HISTORY{
    my $i = 0;
    my $num = shift;
    if($num eq 'DEL'){
	undef(@history);
	unshift(@history,$res);
	return;
    }
    if(defined $num){
	$res = $history[$num];
	$req = $res->request;
	@form = HTML::Form->parse($res);
	$form = $form[0];
	$history = $num;
	return;
	SERVER_START();
    }
    foreach $res (@history){
	printf(" %1s%s: %s\n"
	       ,($i == $history)?"*":'',$i++,$res->request->uri);
    }
}

# LINK : リンクの操作
# GO と組み合わせて使う。
#  ex: GO LINK 2
#
# LINK => リンク一覧の表示
# LINK 2 => 指定された番号のリンクに対応したHTTP::Requestオブジェクトを返す。
#           マイナスを指定すれば逆順に数える。
# LINK uri => 直接uriを指定(相対/絶対)
sub LINK{
    my $uri = shift;
    unless( defined $uri ){
	my $content = $res->content;
	print " ".$res->base."\n";
	my $i = 0;
	while($content =~ /<a.*?href=([\"\'])?(.*?)\1.*?>(.*?)<\/a>/mg){
	    printf("  %s: %s [%s]\n",$i++,$2,$3);
	}
	return;
    }
    if($uri =~ /^\-?\d+$/){
	my $i = 0;
	my(@list);
	my $content = $res->content;
	while($content =~ /<a.*?href=([\"\'])?(.*?)\1.*?>(.*?)<\/a>/mg){
	    push(@list,$2);
	}
	$uri = @list[$uri];
    }
    if($uri){
	$uri = URI->new( $uri );
	$uri = $uri->abs($res->request->uri);
	return new HTTP::Request( GET=> $uri );
    }else{
	return 'no target';
    }
}

sub DISP_FORM{
    my $i = -1;
    my(@submits);
    foreach $input ($form->inputs){
	$i++;
	next if($input->type eq 'hidden');
	DISP_INPUT($input,$i);
    }
}

sub DISP_INPUT{
    my $input = shift;
    my $i     = shift;
    my($value,$note);
    if($input->type eq 'hidden'){
	$note = '(hidden) ';
    }
    if($input->possible_values){
	$value = sprintf("( %s )",
			 join (' ',
			       map { ($_ eq $input->value)? "{$_}" : $_; }
			       $input->possible_values));
    }elsif($input->type eq 'submit'){
	printf("%d   [[ %s ]]\n",$i,$input->value);
	return;
    }else{
	$value = $input->value;
    }
    printf("%d %s%s=%s\n",$i,$note,$input->name,$value);
}

# FORM : フォームの操作
# GO と組み合わせて使う。
#  ex: GO FORM 2,1
#
# FORM => 入力可能パラメータ一覧の表示。
# FORM 2 => 指定された番号のパラメータを表示
# FORM 2, 'value' => 指定された番号のパラメータを'value'に設定
#                    マイナスを指定すれば逆順に数える。
#                    但し、指定された番号のパラメータがsubmitだった場合は
#                    HTTP::Requestオブジェクトを返す。
sub FORM{
    my $n = shift;
    unless ( defined $n ){
	return DISP_FORM;
    }
    if($n >= scalar($form->inputs)){
	return 'no exist input $n';
    }

    $input = ($form->inputs)[$n];
    my $val = shift;
    unless(defined $val){
	DISP_INPUT($input,$n);
	return '';
    }
    if($input->type eq 'submit'){
	return $input->click($form);
    }
    return $input->value($val);
}

my $req : shared;
my $res : shared;

# サーバ実装:
# threadのサポートが必要。
#
# http://localhost:10080/ にアクセスすると、
# GO / HISTORY で進んだ「今見ている」ページを見ることができる。

# ものすごくRFCを無視したどうかと思うWebサーバ本体
sub SERVER{
    my $listener = IO::Socket::INET->new(Listen    => SOMAXCONN,
					 LocalPort => 10080,
					 LocalAddr => "127.0.0.1",
					 PeerAddr  => "0.0.0.0",
					 Proto     => 'tcp',
					  Reuse     => 1
                                         ) or die "create listener";
    while(1){
	$listener->listen or die;
	my $sock = $listener->accept();
	unless($sock){
	    sleep 1;
	    next;
	}
	while($line = $sock->getline()){
	    if($line =~ /X-WebAccess-InPerl-Quit:/){
		close($listener);
		close($sock);
		$end = 1;
	    }
	    last if($line =~ /^\r\n$/);
	}
	last if($end);
	my $res = RES();
	my $content = $res->content;
	my $buf = sprintf("HTTP/1.1 200 OK\r\nContent-Type: %s\r\nConnection: close\r\nContent-Length: %s\r\n\r\n%s"
			  ,($res->header('content-type'))[0]
			  ,length($content)
			  ,$content);
	$sock->syswrite($buf,length($buf));
	$sock->close;
    }
}

# ページが切り替わる度に呼ばれる。
# いちいちサーバーを落としてから立て直すのが格好悪い
sub SERVER_START{
    if($SERVER_THREAD){
	my $sock = IO::Socket::INET->new(PeerAddr => '127.0.0.1',
					 PeerPort => 10080,
					 Proto => 'tcp');
	$sock->syswrite("X-WebAccess-InPerl-Quit:\r\n");
	$sock->close;
	$SERVER_THREAD->join();

    }
    $SERVER_THREAD = threads->new(\&SERVER);
}

# コマンドラインであれこれやるためのUserAgentを作る。
$ua = new LWP::UserAgent;
$cookie_jar = HTTP::Cookies->new;
$ua->cookie_jar($cookie_jar);