WEB+DB PRESS Vol.89に「Perlによる内部DSLの作り方」という記事を書いた

10/24発売のWEB+DB PRESS Vol.89に「Perlによる内部DSLの作り方」という記事を書いた。

gihyo.jp

タイトルの通り、Plack::Builderのコードを例に挙げながら、PerlによるDSLの作り方を紹介した記事である。僕自身、Plack::BuilderがPerl5のコードの中で一番カッコイイと思っているので、そのカッコよさが伝われば望外の喜びである。

さて、この記事を読んだならばおそらく次の疑問が湧くだろう。そう、Perl6ではどうなってるのよと。

これについては、すでに Crust::Builder, Frinfon などでその片鱗をうかがえる。

結論として、Perl5もPerl6もあつい!

perlでサイズが大きいファイルをhttp postする

perlでサイズが大きいファイルをhttp post or putするには下記のようにするとよさそう。

LWP, HTTP::Tinyともrequest contentとしてcallbackを指定できるのでそれを使う。 ただし、デフォルトだとTransfer-Encoding: chunkedになるので明示的にContent-Lengthを指定するとよさげ。

use strict;
use warnings;
use HTTP::Tiny;
use LWP::UserAgent;

my $read_callback = sub {
    my $fh = shift;
    sub {
        my $len = read $fh, my $buf, 32768;
        if (!defined $len) {
            die "read error: $!";
        } elsif ($len == 0) {
            undef; # EOF, finish
        } else {
            $buf;
        }
    };
};

my $url = "http://example.com";
my $file = "bigfile.data";
open my $fh, "<", $file or die;
binmode $fh;

# HTTP::Tiny
my $tiny = HTTP::Tiny->new;
my $res_tiny = $tiny->post($url, {
    headers => {
        'Content-Length' => -s $fh,
        'Content-Type'   => "application/octet-stream"
    },
    content => $read_callback->($fh),
});

seek $fh, 0, 0; # rewind

# LWP
my $lwp = LWP::UserAgent->new;
my $req = HTTP::Request->new(
    POST => $url,
    [ 'Content-Length' => -s $fh, 'Content-Type' => "application/octet-stream" ],
    $read_callback->($fh),
);
my $res_lwp = $lwp->request($req);

前記事を真似れば、upload speed制限(limit 5Mbpsとか) もできそうではある。

perlでdownload speedに制限をかける

なぜかhttp getのときdownload speedに制限をかける必要が度々発生するので、そのやり方を考えてみた。

dataをgetして、data取りすぎのときテキトーにsleepを入れれば一応達成できた。

LWP::UserAgent, HTTP::Tiny でのやり方は以下。

use strict;
use warnings;
use LWP::UserAgent;
use HTTP::Tiny;
use Time::HiRes ();

my $LIMIT_UNIT_SECOND = 0.001;

sub limit_data_callback {
    my ($fh, $limit_bps) = @_;
    my $previous = [ [Time::HiRes::gettimeofday], 0 ];
    sub {
        print {$fh} $_[0];
        my $elapsed = Time::HiRes::tv_interval($previous->[0]);
        return 1 if $elapsed < $LIMIT_UNIT_SECOND;
        my $sleep = 8 * (tell($fh) - $previous->[1]) / $limit_bps - $elapsed;
        if ($sleep > 0) {
            select undef, undef, undef, $sleep;
            $previous->[0] = [Time::HiRes::gettimeofday];
            $previous->[1] = tell($fh);
        }
    };
}

my $url = "http://www.cpan.org/src/5.0/perl-5.22.0.tar.gz";
my $limit_bps = 10 * (1024**2); # 10Mbps

# LWP::UserAgent
open my $fh_lwp, ">", "lwp-perl-5.22.0.tar.gz" or die;
binmode $fh_lwp;
my $res_lwp = LWP::UserAgent->new
    ->get($url, ':content_cb' => limit_data_callback($fh_lwp, $limit_bps));
close $fh_lwp;

# HTTP::Tiny
open my $fh_tiny, ">", "tiny-perl-5.22.0.tar.gz" or die;
binmode $fh_tiny;
my $res_tiny = HTTP::Tiny->new
    ->get($url, {data_callback => limit_data_callback($fh_tiny, $limit_bps)});
close $fh_tiny;

これでだいたい10Mbpsに制限できる。

HTTP::Tiny->mirrorについてはmoduleにまとめてみた。 https://github.com/shoichikaji/HTTP-Tiny-Bandwidth

もっといいやり方ありましたら教えてください。

Let's start Perl6 with Crust!

Crust https://github.com/tokuhirom/p6-Crust is Plack for Perl6, developed by tokuhirom. Did you try it? If not, let's try it now!

> panda install Crust

The simplest example is below, which shows "Hello World!" when you access curl http://localhost:5000.

> crustup -e '-> $env { 200, [], ["Hello World!"] }'

How easy it is!

Crust already has some stuff. If you want to serve files in your current directory, type:

> crustup -MCrust::App::Directory -e 'Crust::App::Directory.new'

Again how easy it is!

Yes, Crust is written in Perl6, so I want to write code which cannot be written in Perl5.

Let's consider Plack/Crust Middleware. Middleware wraps your PSGI application which is thought of a plugin.

In Perl5, we create Plack::Middleware::Hoge class, and apply it to PSGI applications.

In Perl6, because sub {} is actually a Routine object, we can write an in-line Middleware like this:

my $app = sub ($env) {
    say $env<PATH_INFO>;
    [200, [], ["ok"]];
};

# middleware
$app.wrap: -> $env {
    $env<PATH_INFO> ~= "before-wrap";
    my @res = callwith($env);
    @res[2;0] ~= " after wrap";
    @res;
};

$app;

Wow!

Actually I'm just starting learning Perl6. So I definitely miss a lot of Perl6 features now.

I'm looking forward to learning awesome Perl6 features as writing Crust stuff!

Let's start Perl6 with Crust!

slack perl6 evalbotを作った

Larry Wallが今年のクリスマスにリリースすると言って以来、 一部において異常な盛り上がりを見せるPerl6であるが、 この度Slack Perl6 Evalbotが完成した。 f:id:ks0608:20151004151714p:plain https://github.com/shoichikaji/slack-perl6-evalbot

Perl6は現在、リリースに向けてdeprecatedな関数の削除は当たり前として、 Great List Refactor(リスト周りの大規模なリファクタ)、 Array.pushの挙動変更などちょー攻撃的な変更の嵐である。

そんな中をサバイブするためにはcronでPerl6を毎日buildするのは至極当然である。

またチャットにおいては、円滑なコミュニケーションをとるためにperl6 codeをその場でevalできることが必須であると言えよう。

Slack Perl6 Evalbotはhttp://soozy.fushihara.net/にてすでに動いており、ご興味ある方は早速joinの上

perl6: "hello world!".say;

とつぶやいていただきたい。

またSlack Perl6 EvalbotはDockerさえあれば立てられるので任意のslack termにも簡単に導入可能である。

余談

  • evalbotはperl5で作ったが、perl6はperl5の実用性を再認識させてくれる言語として極めて優秀であった。
  • AnyEvent-SlackRTM, AnySan-Provider-Slackを使えばとても簡単にperl5でslack botが作れた。
  • 会社の社内チャットには一足早くperl6 evalbotが導入されており、おそらく日本で最初にチャットでperl6をevalできる会社になったと自負している(無駄w)