mond-tech Perl 忍者ブログ
焼肉屋さんが大好きです。いや、そうでも無い。 たぷたぷになってきてピンチです。
07
×

[PR]上記の広告は3ヶ月以上新規記事投稿のないブログに表示されています。新しい記事を書く事で広告が消えます。

AnyEventを使ってHTTPD書いてみた。モジュールリロードもやれる子。
まぁ、アプリケーションサーバライクな部分のコードを書いて無いから、
モジュールリロード部分はさっぱり役に立たないけれどwww。
試したい場合は、create_responseでreq、resを受け取ってホゲホゲするパッケージを書いてやると良いよ。

AnyEvent::HTTPDを使えば良いじゃん、って話もあるけど、あれって微妙で・・・。
Object::Eventとか要らんもの使っていて、モジュールのリロードが痛いコードになったり、
ヘッダに同じキーが2作るとアウトだったり(Cookieで刺さる)。HTTP::Requestじゃないのが痛かったり。
他も微妙と言うか、べっつにこんくらい自分で書けよ、シリーズだったので。
なんでもCPAN使えば良いってもんじゃない。と言うかそれやると無駄な苦労が増える場合の方が多かったりwww。
最近ホント酷いものねぇ。困ったもんです。Ratingが一切アテにならないと言う・・・。

CPANに対する愚痴はほどほどにしといて、このコード、前書いたechoより随分コードが増えてると思う。特にレスポンス周り。
echoのサンプル、実は、レスポンスで返すデータが増えると、データが途中でぶった切られるのよね。
サンプルのエコーサーバ程度だからあれで済むんだけど、本気でサーバやるとなるとそれじゃ困る。

何故にデータが途中で切られるか、と言うと、ソケットが書き込み可能の状態じゃないのに無理やり書こうとするから。
この原因は、ソケットが読み書き両用でオープンされてて、あっちからデータが来たりこっちから書いたりがブツかって、
いやーんな状態になったりするから。

そう言う場合EAGAINって言うエラーが飛んでくる。これは読めないのに無理やりソケットからデータを読もうとしても飛ぶエラー。
データが無いのに読んだりね。

だから、ソケットが書ける状態なのかをAnyEventのioで監視して、書ける状態になったら書き込み開始。
で、書いてる最中にEAGAIN起こっちゃったら、書くの中止して、再びソケットの監視へ、ってな風にやれば良い。
それをwrite_responseで再帰使ってやってる。

これで、最低限動くHTTPDの出来上がり。


#!/usr/bin/perl

use AnyEvent;
use AnyEvent::Socket;
use IO::Handle;
use HTTP::Parser;
use Errno qw(EAGAIN);

use Data::Dumper;

my $HOST = '192.168.0.100';
my $PORT = 8080;
my $HTTP_VERSION = '1.1';
my $MODULE_RELOAD = 0.5;

$| = 1;

server_listen();

sub server_listen {
tcp_server $HOST, $PORT, sub {
my ($sock, $host, $port) = @_;
autoflush $sock 1;
print "Client connected: $host($port)\n";
my $sock_watcher; $sock_watcher = AnyEvent->io(
fh => $sock,
poll => 'r',
cb => sub {
my $data;
while (<$sock>) { $data .= $_; }
my $res = create_response(parse_request($data));
$res->header('Content-Length' => length $res->content);
my $res_data = "HTTP/$HTTP_VERSION " . $res->as_string;
my $res_len = length $res_data;
write_response($sock, $res_data, $res_len);
undef $sock_watcher;
},
);
}, sub {
my ($fh, $host, $port) = @_;
print "HTTP server is listening on $host($port)\n";
};
if ($MODULE_RELOAD) { reload_check(); }
else { AnyEvent->condvar->recv; }
}

sub parse_request {
my $parser = new HTTP::Parser(request => 1);
if ($parser->add(shift) == 0) {
my $req = $parser->request;
warn 'URI: ' . $req->uri;
warn 'METHOD: ' . $req->method;
warn Dumper $req->headers;
return $req;
}
}

sub create_response {
my ($req) = @_;
my $res = new HTTP::Response;
$res->code(200);
$res->header('Content-Type' => 'text/html');
my $content;
for ('a' .. 'z') { $content .= $_ x 10000 . "
\n"; }
$res->content($content);
return $res;
}

sub write_response {
my ($sock, $res_data, $res_len, $offset) = @_;
my $w_sock_watcher; $w_sock_watcher = AnyEvent->io(
fh => $sock,
poll => 'w',
cb => sub {
defined $offset or $offset = 0;
while (my $len = syswrite $sock, $res_data, $res_len, $offset) {
$offset += $len;
$offset == $res_len and last;
}
if ($! == EAGAIN && $offset != $res_len) {
undef $w_sock_watcher;
write_response($sock, $res_data, $res_len, $offset);
}
undef $w_sock_watcher;
}
);
}

sub reload_check {
my $cv = AnyEvent->condvar;
my $time_watcher = AnyEvent->timer(
interval => $MODULE_RELOAD,
cb => sub {
if (my $target = module_reload()) {
for (keys %$target) { print "Reloaded: $_($target->{$_})\n"; }
}
},
);
$cv->wait;
}

sub module_reload {
my %reloaded = ();
for (keys %INC) {
my $lm = -M $INC{$_};
my $path = $INC{$_};
$lm == $LAST_MODIFY{$_} and next;
if ($LAST_MODIFY{$_}) {
delete $INC{$_};
eval { require $path; };
if ($@) { $INC{$_} = $path; warn $@; }
else { $reloaded{$_} = $path; }
}
$LAST_MODIFY{$_} = $lm;
}
return %reloaded ? \%reloaded : undef;
}

PR
モジュールファイルに変更があった際にそのモジュールを自動で読み込みなおしたい、そんな時・・・

サーバ系やら常駐系のアプリ開発しない限りは必要性は無いと思うけれど。

CPANのModule::Reloadは、モジュールがエラーでこけると、そのモジュールの再読み込みが行われない、だとか、
モジュール再読み込みの際にどのモジュールが読み込まれたか知りたい場合それを外からハンドリング不能、
だとか、微妙なので、こんなもんは書いて、自分のライブラリに追加した方が早いと思うんだ、わたくし。


sub module_reload {
my %reloaded = ();
for (keys %INC) {
my $lm = -M $INC{$_};
my $path = $INC{$_};
$lm == $LAST_MODIFY{$_} and next;
if ($LAST_MODIFY{$_}) {
delete $INC{$_};
eval { require $path; };
if ($@) { $INC{$_} = $path; warn $@; }
else { $reloaded{$_} = $path; }
}
$LAST_MODIFY{$_} = $lm;
}
return %reloaded ? \%reloaded : undef;
}

if (my $target = module_reload) {
warn 'RELOADED: ' . Dumper $target;
}


こんな感じで。

%INCには読み込み済みのモジュール情報が詰まってる。
Keyには、パッケージ名をファイルパスに直した謎フォーマットのデータ、
Valueには、パッケージへのファイルフルパス。
それをぶん回して最終更新日をチェックするだけ、と言う代物。まぁ、何とも単純。

返り値は、再読み込みしたモジュールがあれば、
その情報をINCのKey-Valueそのままにハッシュのリファレンスで返す。
無ければundef。

そーんなかんじでー。よろぴこめかどっく。

#!/usr/bin/perl

use strict;

use Proc::Fork;
use POSIX;

chdir '/';
close $_ for *STDIN, *STDOUT, *STDERR;

run_fork {
child {
POSIX::setsid;
while(1) {
# daemon operation.
}
}
parent { exit; }
};

AnyEventのechoサーバサンプル


#!/usr/bin/perl

use AnyEvent;
use AnyEvent::Socket;
use IO::Handle;

$| = 1;

my $HOST = '192.168.1.200';
my $PORT = 9999;

tcp_server $HOST, $PORT, sub {
my ($sock, $host, $port) = @_;
autoflush $sock 1;
print "Client connected: $host($port)\n";
my $watcher; $watcher = AnyEvent->io(
fh => $sock,
poll => 'r',
cb => sub {
my $data = <$sock>;
$data =~ s/\r?\n//g;
if ($data eq 'quit') {
print "Client disconnected: $host($port)\n";
undef $watcher;
return;
}
print "$data by $host($port)\n";
print $sock "[$data]\n";
},
);
}, sub {
my ($fh, $host, $port) = @_;
print "Echo server is listening on $host($port)\n";
};
AnyEvent->condvar->recv;


WEBには、sysread、syswriteのサンプルしかないんだよね。
バッファリングされちゃうからsys*系使うんだろうけどautoflushしちゃえば良い。
変数が開放されたタイミングで走る関数を定義する事が出来る。
AnyEventのサンプルコード見てもらえれば解かる通り、これを用いたコードは、
使い手側に、一見、使いもしない謎の変数確保を強要されるので、気持ち悪い事になる。
普通には使わない方が良いと思う。DESTROY、MooseならDEMOLISHでやれば良い。

#!/usr/bin/perl

use AnyEvent::Util;

my $guard1 = create_guard(1);
my $guard3 = hoge();

{
my $guard4 = create_guard(4);
}

undef $guard3;
undef $guard1;

sub hoge {
my $guard2 = create_guard(2);
return create_guard(3);
}

sub create_guard {
my ($no) = @_;
return AnyEvent::Util::guard {
warn "Destroy $no";
};
}

2, 4, 3, 1の順でクロージャが呼ばれる。
Copyright c mond-tech All Rights Reserved
忍者ブログ / [PR]
にほんブログ村 IT技術ブログ Webサイト構築へ