2009年10月29日

Ajax アプリ等の為の JavaScript コードのテストツール JSTAPd を作ってるよ

Data::ObjectDriverをDISってる人の話題が三周目に突入した今日この頃ですが皆さんは何をDISってますか?Yappoです。

JavaScript にもテストツールが色々とあると思うんですが、 Ajax アプリの XHR リクエストとかも含めてラクチンにテストできるツールが見つからなかったので JSTAPd というツールを作りました。

http://github.com/yappo/JSTAPd

名前の通りテスト結果はTAPで出力してるのでproveコマンドとかを使ってPerlの作法でテストできます。
ブラウザの連携の設定をすれば prove -v foo/hoge.t とかをコマンドで打ち込めば勝手にブラウザ立ち上げてテストコード実行してブラウザ閉じて結果をコンソールに吐いてくれます。
もちろんデーモンとして常駐化できるので複数のOS&ブラウザを使って自動的にテストするなんて構成も取れるでしょう。

1テスト1ファイルにまとめて書く事が出来て、実際にテストに関係するJavaScript, HTML, Server APIのコードを書くだけですむようになっています。
JSコードのテストを細かくサクサク量産できる事を目標にしてます。

文章で説明を呼んでもいまいちピンと来ないと思いますので、実際にJSTAPdを使っている所のデモンストレーション動画を撮ったのでご覧下さい。

あと、日本語でチュートリアルも作ったので細かい内容についてはそっちを参照してください。
http://yappo.github.com/JSTAPd/tutorial/ja.html

文法エラーとか補足しきれなかったりまだまだ細かい所がかゆいんですが徐々にまともにしてくつもりなので乞うご期待。

Posted by Yappo at 17:02 | Comments (0) | TrackBack

2009年10月20日

Makefile polyglot (make And perl Makefile)

Careless you doing "perl Makefile", then you take many error messages.
Your trouble will be solved if the polyglot technology is used.

# Example Makefile
irst_labe: length
X;$Y=Z;
dummy_label: length
        0;
        print "perl world\n";
        $x = <<'END_OF_MAKEFILE='
length:
        echo "make world"

END_OF_MAKEFILE=
actually, this code is works on perl and make command.
$ perl Makefile 
perl world
$ make
echo "make world"
make world

Makefile is processible into Makefile.PL polyglot by perl code shown below.

perl Makefile.PLをperl Makefileと打ち込まんでエラーメッセージを出しちゃうドジっ娘さんは沢山いらっしゃるとおもいます。
そんな悩みを解決すべくperl Makefileと打ち込んでもexec $^X, 'Makefile.PL'を実行してくれるMakefileのsnipetを作りました。

規則名をlabelとして扱わせて、その次にperlの構文のlengthをぶち込み、セミコロン必要な所は何故かGNU makeでX;$Y=Z;が通ったので、それでごまかしてlengthの規則名を定義してmakeで実行させる規則を作りつつ。perlで実行された時にはlengthの規則の定義部分は$xにぶち込まれるようにしてあるという具合です。(説明めんどう

ExtUtils::MakeMakerにパッチあてれば良い感じになりますか?

GNU Make 3.81 で確認済み

thanks to tokuhirom and takesako

Posted by Yappo at 14:27 | Comments (0) | TrackBack

2009年10月16日

AUTOLOAD is so crazy

it is useful to what? i don't know.

my $path = 'foo/bar/baz'; Class->$path
とかAUTOLOADで普通に受け取れたから、オブジェクトも送れるんじゃないかと思って書いてみた。反省しない。

Posted by Yappo at 17:31 | Comments (0) | TrackBack

2009年10月15日

HTTP::Engine 0.03001 has streaming response now / multipart/mixedなストリームをHTTP::Engine/Plackでpushする

thanks many ideas from yusukebe, mattn, miyagawa.
I created a streaming response support for HTTP::Engine. it was a too easy hack.
example here

[ゆ]: multipart/mixedなストリームをPlack/PSGIでpushする みて、HTTP::Engineの上で動くかと思ったけど全然動かなかったので、HTTP::Engine側で対応して動くようにしました。
http://github.com/yappo/fast-twitter-stream

IO::Handle::Utilで作った$fhをHTTP::Engine::Response->new( body => io_from_getline sub {} )しただけだと、Content-Lengthが無いとエラーになってしまってbackend interfaceに渡せなかったんですね。
なんで、PSGIみたくバックエンド側でストリーミング的なコンテンツが扱えるinterfaceの時はContent-Length無しでも通るようにしました。

で、出来たのが以下のコードです。オリジナルと殆ど同じです。

use strict;
use warnings;

package FastTwitterStream;
use Coro;
use Coro::Channel;
use Coro::AnyEvent;
use AnyEvent::Twitter::Stream;
use HTTP::Engine::Response;
use IO::Handle::Util qw(io_from_getline);
use Encode;

my $username = $ENV{TWITTER_USERNAME};
my $password = $ENV{TWITTER_PASSWORD};
my $boundary = '|||';

my $streamer;
my %queue;
my $count = 0;
sub request_handler {
    my $req = shift;

    if ( $req->path eq '/push' ) {
        my $now = ++$count;
        $queue{$count} = Coro::Channel->new;
        $streamer ||= AnyEvent::Twitter::Stream->new(
            username => $username,
            password => $password,
            method   => 'filter',
            track => 'twitter',
            on_tweet => sub {
                $_->put(@_) for values %queue;
            },
        );
        my $body = io_from_getline sub {
            my $tweet = $queue{$now}->get;
            if( $tweet->{text} ){
                return "--$boundary¥nContent-Type: text/html¥n" .
                    Encode::encode_utf8( $tweet->{text} );
            }else{
                return '';
            }
        };

        return HTTP::Engine::Response->new(
            headers => {
                'Content-Type' => qq{multipart/mixed; boundary="$boundary"},
            },
            body => $body,
        );
    }
    if ( $req->path eq '/' ) {
        return HTTP::Engine::Response->new( body => html() );
    }
};

sub html {
    my $html = <<'HTML';
<html><head>
<title>Server Push</title>
<script src="http://ajax.googleapis.com/ajax/libs/jquery/1.3.1/jquery.min.js"></script>
<script type="text/javascript" src="/js/DUI.js"></script>
<script type="text/javascript" src="/js/Stream.js"></script>
<script type="text/javascript">
$(function() {
var s = new DUI.Stream();
s.listen('text/html', function(payload) {
$('#content').prepend('<p>' + payload + '</p>');
});
s.load('/push');
});
</script>
</head>
<body>
<h1>Server Push</h1>
<div id="content"></div>
</body>
</html>
HTML
    return $html;
}

package main;
use HTTP::Engine;
use Plack::Builder;

my $engine; $engine = HTTP::Engine->new(
    interface => {
        module => 'PSGI',
        request_handler => ¥&FastTwitterStream::request_handler,
    },
);

builder {
    enable "Plack::Middleware::Static",
        path => qr{¥.(?:png|jpg|gif|css|txt|js)$},
            root => './static/';
    sub { $engine->run(@_) };
};

ブラウザから"http://localhost:8080/"にアクセスすると、もの凄い勢いでつぶやきが追加されていくのが分かるかと思います。しかも切断していないのでかなり高速です。

お試しの効果には個人差があります。

multipart/mixedとこのDiggライブラリの組み合わせ、いいですね。というわけで、元ネタになったyusukebeさん、Plackでpush配信の仕方を教えてくれたmiyagawaさんありがとうございました。
Enjoy!

Posted by Yappo at 13:20 | Comments (0) | TrackBack

2009年10月13日

HTTP::Engine meets PSGI

I shipped HTTP::Engine 0.03. Interface::PSGI is enclosed from this time.
async server became easy by PSGI.

use strict;
use warnings;

use HTTP::Engine;
use Plack::Loader;

my $he1    = HTTP::Engine->new(
    interface => {
        module => 'PSGI',
        request_handler => sub {
            HTTP::Engine::Response->new( body => 'plack 1' );
        },
    },
);
my $he2    = HTTP::Engine->new(
    interface => {
        module => 'PSGI',
        request_handler => sub {
            HTTP::Engine::Response->new( body => 'plach 2' );
        },
    },
);

Plack::Loader->load('AnyEvent', port => 18081)->register_service(sub { $he1->run(@_) });
Plack::Loader->load('AnyEvent', port => 18082)->register_service(sub { $he2->run(@_) });
AnyEvent->condvar->recv;


Enjoy!

Interface::PSGI を同梱した HTTP::Engine 0.03 をリリースしました。
思う所があって、PSGI対応周り以外にももすこし機能追加しようかなという気分になってきました。

Posted by Yappo at 18:44 | Comments (0) | TrackBack

2009年10月09日

HTTP::Request::StreamingUpload - 省メモリでrequest bodyをupload

Today I created a good wrapper for request body upload HTTP::Request.
this module is use few memory on file upload. also too big file.

http://search.cpan.org/dist/HTTP-Request-StreamingUpload/
http://github.com/yappo/p5-HTTP-Request-StreamingUpload

DESCRIPTION

HTTP::Request::StreamingUpload is streaming upload wrapper for HTTP::Request. It could be alike when $DYNAMIC_FILE_UPLOAD of HTTP::Request::Common was used. However, it is works only for POST method with form-data. HTTP::Request::StreamingUpload works on the all HTTP methods. Of course, you can big file upload using few memory by this wrapper.
SYNOPSIS
  my $req = HTTP::Request::StreamingUpload->new(
      PUT     => 'http://example.com/foo.cgi',
      path    => '/your/upload.jpg',
      headers => HTTP::Headers->new(
          'Content-Type'   => 'image/jpeg',
          'Content-Length' => -s '/your/upload.jpg',
      ),
  );
  my $res = LWP::UserAgent->new->request($req);

LWPとか使って大きなファイルアップロードするときは$HTTP::Request::Common::DYNAMIC_FILE_UPLOAD使ってアップロードするとメモリに優しくアップロードできるってのは有名ですが、あれってform-dataの時しかやってくれないので、例えばPUTメソッドでデッカいファイル送りたい時には使えません。

HTTP::Request->new( PUT => $uri, $headers, sub { read $fh, my $buf, 1978; $buf } )
とか書けば出来るんですが、毎回書くのもうっとおしいので、同じ事を
HTTP::Request::StreamingUpload->new( PUT => $uri, headers => $headers, fh => $fh )
で出来るようにしたんです。

Content-Length を入れない場合は LWP::UserAgent の中で使ってる LWP::Protocol::http が chunked で送ってくれるので、予め送りたいサイズが解らない場合でも安心して使えます。

Posted by Yappo at 12:45 | Comments (0) | TrackBack

2009年10月07日

PSGI Implのsendfileについて

PSGIなServerはsendfileを扱う時にどうするかのメモ。

  • $env->{'psgix.sendfile'}がセットされてれば、そこに書いてあるファイルパスを使う
  • $res->[2]がGLOBだったら fileno($res->[2])して sendfile に fd を渡す
  • $res->[2]->can('fileno') が生えてたら、$res->[2]->filenoからfdを取って使う
  • $res->[2]->can('path') が生えてたら、$res->[2]->pathからファイルパスを取って使う

以上が、基本的なsendfileを使を行うときのパターンになる。
また、response headerにX-Sendfileなどの任意のヘッダが入っていて、その中にファイルパスが入っていれば、そのファイルパスを元にしてsendfileするのはServer実装者の自由である。
が、アプリの設定したヘッダはむやみに弄るべきではないので、後述するMiddleware等が設定したpsgix.sendfileを使うべき。だし、そもそも->pathとかをduck typeして取った方が奇麗だ。
というか、好き勝手にやればよい。と言うのがだいたいのsendfile扱うときの定石かな。

多分psgix.sendfileを最優先に使えば良いと思うけど。
たぶんMiddlewareがX-Sendfileなどをpsgix.sendfileに突っ込む等の方向性になるんじゃないか、まだ良くわかんない。

nginx embed perl は、今のところ $r->sendfile($path) しか提供されてなんだけど$r->sendfile_by_fd($res->[2]->fileno)とか出来るようにするつもりです。

Posted by Yappo at 12:31 | Comments (0) | TrackBack