Tatsumaki の eg/chat をほとんどそのまま流用しました。
概要
Tatsumaki
Non-blocking web framework based on Plack and AnyEvent
Twiggy
AnyEvent HTTP server for PSGI (like Thin)
ニコ生アラートサーバ
ニコニコ生放送というサービスを使って開始された番組の情報が XMLSocket の形式で送られるもの。
とりあえずデモ
(deprecated)
仕組み
- live.html
- live.psgi
- nicoalert.pl
nicoalert.pl でアラートサーバに接続し、取得したものを GET パラメータでサーバに送る。 live.psgi で long/poll の管理と新規番組の情報を扱う。 live.html で Comet 。
live.html
http://github.com/beppu/jquery-ev でCometを実装
live.psgi
eg/chat/app.psgi をほぼそのまま
use strict;
use warnings;
use Tatsumaki;
use Tatsumaki::Error;
use Tatsumaki::Application;
package LivePollHandler;
use base qw(Tatsumaki::Handler);
__PACKAGE__->asynchronous(1);
use Tatsumaki::MessageQueue;
sub get {
my ($self, $channel) = @_;
my $mq = Tatsumaki::MessageQueue->instance($channel);
my $client_id = $self->request->param('client_id')
or Tatsumaki::Error::HTTP->throw(500, "'client_id' needed");
$mq->poll_once($client_id, sub { $self->on_new_event(@_) });
}
sub on_new_event {
my($self, @events) = @_;
$self->write(\@events);
$self->finish;
}
package LivePostHandler;
use base qw(Tatsumaki::Handler);
use Encode;
use AnyEvent::HTTP;
use XML::Simple;
sub get {
my($self, $channel) = @_;
my $v = $self->request->parameters;
my $lv_num = $v->{lv};
http_request(
GET => "http://live.nicovideo.jp/api/getstreaminfo/lv$lv_num",
timeout => 3,
on_body => sub {
my ($body, $hdr) = @_;
return if (!defined $body);
my $res = XMLin(decode_utf8 $body);
$res->{type} = 'message';
my $mq = Tatsumaki::MessageQueue->instance($channel);
$mq->publish($res);
$self->write({ success => 1 });
}
);
}
package LiveHomeHandler;
use base qw(Tatsumaki::Handler);
sub get {
my ($self) = @_;
$self->render('live.html');
}
package main;
use File::Basename;
my $chat_re = 'stream';
my $app = Tatsumaki::Application->new([
"/($chat_re)/poll" => 'LivePollHandler',
"/($chat_re)/post" => 'LivePostHandler',
"/($chat_re)" => 'LiveHomeHandler',
]);
$app->template_path(dirname(__FILE__) . "/templates");
$app->static_path(dirname(__FILE__) . "/static");
$app;
nicoalert.pl
#!/usr/bin/perl
use strict;
use warnings;
use utf8;
use Encode;
use AnyEvent;
use AnyEvent::Socket;
use AnyEvent::HTTP;
use AnyEvent::Handle;
use XML::Simple;
use Data::Dumper;
use LWP::UserAgent;
my $server = get_alertinfo();
my $cv = AE::cv;
my $connection = tcp_connect(
$server->{addr},
$server->{port},
sub { connection_cb(@_); }
);
$cv->recv;
sub socket_read_cb {
my ($handle, $chat_tag) = @_;
my $decoded_chat_tag = decode_utf8 $chat_tag;
if ($decoded_chat_tag =~ />(.*)</) {
my ($lv_num, $co_num, $user_id) = split/,/, $1;
if (defined $lv_num && defined $co_num && defined $user_id) {
http_request(
GET => "http://live.linknode.net:8080/stream/post?lv=$lv_num",
timeout => 3,
on_body => sub { print Dumper \@_; }
);
}
}
$handle->push_read(line => "\0", sub{ socket_read_cb(@_); } );
}
sub connection_cb {
my ($fh) = @_ or die $!;
my $thread_tag = qq/<thread thread="$server->{thread}" version="20061206" res_from="-1" \/>\0/;
my $handle; $handle = new AnyEvent::Handle(
fh => $fh,
on_error => sub {
warn "Error $_[2]\n";
$_[0]->destroy;
},
on_eof => sub {
$handle->destroy;
warn "Done\n";
}
);
$handle->push_write($thread_tag);
$handle->push_read(line => "\0", sub { socket_read_cb(@_); });
}
sub get_alertinfo {
my $url = 'http://live.nicovideo.jp/api/getalertinfo';
my $ua = LWP::UserAgent->new();
my $res = $ua->get($url);
die "$res->status_line" unless ($res->is_success);
my $xml = XMLin($res->decoded_content);
die "Fatal: Server status $xml->{status}" if ($xml->{status} ne 'ok');
return $xml->{ms};
}
起動
plackup -p 8080 -s Twiggy live.psgi > /dev/null 2>&1 & perl nicoalert.pl > /dev/null &
追記
LivePostHandlerのgetをpostに書き換えてnicoalert.plのsocket_read_cb()のhttp_requestの部分をTatsumaki::HTTPClientで処理するように変更しました
追記2
http://blog.plackperl.org/2010/03/comet-applcation-with-tatsumaki-and-twiggy.html
こちらで紹介していただきました。