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
こちらで紹介していただきました。