Monday, August 6, 2012

LWP Chunked Way

在讀取 || 傳送大量資料的時候,可以利用 IO::* 這類模組去一次讀取其中一段資料 (Chunk) 就寫入暫存檔 || 送出,來避免對記憶體造成負擔的手法可說相當常用。也是 HTTP Server 用來做 Streaming 或是 Long Polling / Comet 的基礎。

Client 端的用法就沒有那麼絢麗的名字了(開發 Client 的人沒有必要用絢麗的名字震懾老闆?:p)。LWP 對 Chunk 手法也有支援,不過在 GET 和 POST 兩種 HTTP 動做的支援方式分別使用了接受 callback 和利用全域變數啟用的方式,有點不一致,特此紀錄一筆。

GET 使用 Chunk 的方式在 lwpcook 有詳盡記載。在 LWP::UserAgent 物件呼叫 request 方法時多傳入一個 callback 即可。

$ua->request(
HTTP::Request->new(GET => $URL),
sub {
my($chunk, $res) = @_;
# do something on $chunk
}
);
view raw get.pl hosted with ❤ by GitHub

POST 動作其實也應該可以提供 callback。想像起來應該是這樣:

$req = HTTP::Request->new(POST => $URL);
$req->header(KEY => VALUE);
$req->content($bytes);
$ua->request(
$req,
sub {
my($chunk, $socket, $res) = @_;
$socket->write($chunk);
}
);
view raw post.pl hosted with ❤ by GitHub

大概是反正多數時候只能寫 $socket->write($chunk),所以乾脆就給個全域變數當作開關?
$HTTP::Request::Common::DYNAMIC_FILE_UPLOAD = 1;
雖然有 callback 的話應該還是有點用途的。

Saturday, March 31, 2012

class variables in Moose

Moose attribute didn't allow the reference rather than a CODE reference as a default value. That prevents the objects shared a attribute when initializing. Actually this is a good design. But How should I do when needing a shared attribute between objects? I may refer this as the concept of class variable.

The state variable in CODE reference may be competent with this role.

A Counter as an example here. We define a Counter class here.

use Modern::Perl;
use MooseX::Declare;
class Counter {
has value => (
is => "rw",
isa => "Num",
traits => ['Counter'],
default => 0,
handles => {
inc => "inc",
},
);
};
view raw Counter.pm hosted with ❤ by GitHub


A counter has a attribute "value" and a method "inc" to increment it.

Then We define a Person class.

use Modern::Perl;
use MooseX::Declare;
use Counter;
class Person {
has name => (
is => "ro",
isa => "Str",
default => "nobody",
required => 1,
);
has count => (
is => "rw",
isa => "Counter",
default => sub {
state $count = Counter->new;
$count;
},
);
sub BUILD {
my ($self) = shift;
$self->count->inc;
}
};
view raw Person.pm hosted with ❤ by GitHub


The Person class has a attribute "name" and a shared attribute "count". The Counter is a state variable return from a anonymous CODE reference. That's the trick.

The BUILD hook increment the counter everytime a new Person is constructed.

Then we examinate the shared variable.

#!/usr/bin/env perl
#-*- mode: cperl -*-
use Modern::Perl;
use Person;
my $shelling = Person->new(name => "shelling");
my $count = $shelling->count;
say $count->value; #=> 1
my $sherry = Person->new(name => "sherry");
say $count->value; #=> 2
my $appollo = Person->new(name => "appollo");
say $count->value; #=> 3


A global variable in package name may still work. But this way uses 'has' syntax sugar. :p

If you use builder to replace the default option. the subroutine used by the builder is just the class variable accessor from Class name, returning value as $person->count().

Saturday, March 3, 2012

Mysterious Timestamp

時間轉換大概是最惱人的「小」問題之一,每個語言的標準處理方式不盡相同。Perl 的 localtime() 是月份從 0 開始到 11 結束,Ruby 的 DateTime 是月份是從 1 開始的,不過 Perl 的 DateTime 月份也從 1 開始 (LOL)。背誦這些東西大概會發瘋。

不過 Epoch 沒有這個問題,任何語言上的 Epoch 應該都是一致的,有的只是時差問題,使用 Epoch 來初始時間物件大概是避開這問題的好方法之一,特別在不同語言間交換時間資料時,用 Epoch 交換後怎麼 format 都不會出問題。所以有些時間模組大概會像 Perl 的 DateTime 一樣實作 from_epoch() 這種 constructor。

就這點設計而言,Badger::Timestamp 大概是最好用的時間模組,其 constructor 支援 DateTime 模組的方式初始化外,還可以餵食 Epoch 和 ISO 格式的時間字串。一個簡單的 Epoch/ISO converter 只要八行。
#!/usr/bin/env perl
use 5.010;
use Badger::Timestamp;
my $ts = Badger::Timestamp->new($ARGV[0]);
say $ts->timestamp;
say $ts->epoch_time;
view raw timestamp.pl hosted with ❤ by GitHub

什麼,你問說這八行程式有什麼用,有些跑了好幾年的 unix 系統上有著 crontab 執行著需要 timestamp 來分辨執行區段的分析器,有些要當天的零分零秒,有些要隔天的零分零秒。壞掉時有人會要你一定要「人工」處理,這八行就就是這樣來的。

附帶一提, Javascript 的 Date.UTC 和 Perl 的 localtime() 一樣,都是月份從 0 開始。


和 Badger::Timestamp 很像的 moment.js這時候就很好用了,其 constructor 用陣列初始化時雖然還是沒有改用 1 作為月份起始著實也讓我驚訝了一下。好險,這 constructor 支援用 Epoch 初始化(不過承襲 javascript 的慣例,是 millisecond),還可以用 Date.parse() 支援的字串初始化。這兩招可以避開月份的問題。