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

こちらで紹介していただきました。