ラベル Sledge の投稿を表示しています。 すべての投稿を表示
ラベル Sledge の投稿を表示しています。 すべての投稿を表示

2006年11月25日土曜日

SledgeでもRESTfulなアプリケーションを書きたい!

今日参加した第9回XML開発者の日の川村さんによる「Ruby on RailsにみるRESTfulアプリケーションの方向性」の話を聞いて、SledgeでもRESTfulなコードを簡単に書きたいと思いたち、ちょっとパッチを書いてみました。

>


--- Sledge/Pages/Base.pm.orig 2006-11-25 00:40:59.000000000 +0900
+++ Sledge/Pages/Base.pm 2006-11-25 09:27:50.000000000 +0900
@@ -8,6 +8,9 @@
use strict;
use base qw(Class::Accessor Class::Data::Inheritable);

+use vars qw($MethodQueryKey);
+$MethodQueryKey = '_method';
+
__PACKAGE__->mk_accessors(
'r', # Apache::Request or Sledge::Request::CGI
'session', # Sledge::Session
@@ -81,10 +84,16 @@
eval {
$self->init_dispatch($page);
$self->invoke_hook('BEFORE_DISPATCH') unless $self->finished;
- if ($self->is_post_request && ! $self->finished) {
+ if ( $self->is_put_request && ! $self->finished) {
+ my $putmeth = 'put_dispatch_' . $page;
+ $self->$putmeth() if $self->can($putmeth);
+ } elsif ( $self->is_delete_request && ! $self->finished) {
+ my $deletemeth = 'delete_dispatch_' . $page;
+ $self->$deletemeth() if $self->can($deletemeth);
+ } elsif ($self->is_post_request && ! $self->finished) {
my $postmeth = 'post_dispatch_' . $page;
$self->$postmeth() if $self->can($postmeth);
- }
+ }
unless ($self->finished) {
my $method = 'dispatch_' . $page;
$self->$method();
@@ -188,6 +197,16 @@
return $self->r->method eq 'POST';
}

+sub is_put_request {
+ my $self = shift;
+ return ($self->r->method eq 'PUT' || ($self->r->method eq 'POST' && lc($self->r->param($MethodQueryKey)) eq 'put'));
+}
+
+sub is_delete_request {
+ my $self = shift;
+ return ($self->r->method eq 'DELETE' || ($self->r->method eq 'POST' && lc($self->r->param($MethodQueryKey)) eq 'delete'));
+}
+
sub make_content {
my $self = shift;
# template output, then fillin forms

<

これを使って書いたPagesクラスのサンプルはこんな感じです。

>

package MyProj::Pages::Items;
use strict;
use base qw(MyPfoj::Pages);

sub dispatch_index {
my $self = shift;
my $item_id = int $self->r->param('id');
if ( $item_id ){
# アイテム単体を返すコードを記述
} else {
# アイテムリストを返すコードを記述
}
}

sub post_dispatch_index {
my $self = shift;
# アイテムを追加するコードを記述
}

sub put_dispatch_index {
my $self = shift;
# アイテムを更新するコードを記述
}

sub delete_dispatch_index {
my $self = shift;
# アイテムを削除するコードを記述
}

<

MyProj::Pages::Itemsクラスがアイテムをあらわすリソースに対応していて、
各メソッドにあわせて、CRUDの操作を実行するという風に書けてすっきりする気がします。
ブラウザからはPUT,DELETEリクエストはできないので、_method=putまたはdeleteとクエリパラメータを使うことで代用しています。

こんなのいかがでしょうか?



2006年7月31日月曜日

Sledgeのセッション用テーブルはMyISAMにしています。

naoyaさんのエントリーnaoyaのはてなダイアリー - MyISAM vs InnoDBに反応して、久々にエントリを書いてみます。

FlipClipは、すべてMyISAMで運用しているんですが、理由はあんまりなくて、デフォルトでMyISAMだからってのが大きいのと、スナップショットを取るのにmysqlsnapshotに手を加えたものを使っているので、InnoDBにするとそれを使えなくなるというのが主な理由です。それにまだDBがボトルネックだうはーってな状態になったことがないってのもあります。

それでも少し前にサーバのチューニングをそろそろ考えたほうがいいかなーということを考えたことがありまして、
その時にチューニングの候補に考えたものの一つが、Sledgeのセッション用のテーブルをMyISAMからInnoDBに替えてみるということでした。
セッションテーブルはスナップショットを取る必要ないですし、InnoDBにして早くなったらラッキーってのと、Sledgeのセッション用のテーブルは1リクエストで必ず1回書き込みが行われるので、読み込みと書き込みが同程度発生するので、更新系が多い場合はInnoDBがよいというのにあてはまるかなと思ったからです。

そこで実際にSledgeで作ったアプリケーションを使ってセッションテーブルをInnoDBにした場合と、MyISAMにした場合でどれほど性能に差がでるかabを使って簡単にベンチマークしてみました。

以下がabを使って同時10リクエストで合計1000リクエスト実行した際の結果です。

MyISAMの場合


>

Benchmarking rakuda.localhost (be patient)
Server Software: Apache/1.3.36
Server Hostname: rakuda.localhost
Server Port: 80

Document Path: /
Document Length: 14911 bytes

Concurrency Level: 10
Time taken for tests: 110.009 seconds
Complete requests: 1000
Failed requests: 0
Broken pipe errors: 0
Total transferred: 15167000 bytes
HTML transferred: 14911000 bytes
Requests per second: 9.09 [#/sec] (mean)
Time per request: 1100.09 [ms] (mean)
Time per request: 110.01 [ms] (mean, across all concurrent requests)
Transfer rate: 137.87 [Kbytes/sec] received

Connnection Times (ms)
min mean[+/-sd] median max
Connect: 0 0 0.0 0 1
Processing: 292 1096 223.5 1031 1976
Waiting: 291 1096 223.5 1031 1976
Total: 292 1096 223.5 1031 1976
ERROR: The median and mean for the initial connection time are more than twice the standard
deviation apart. These results are NOT reliable.

Percentage of the requests served within a certain time (ms)
50% 1031
66% 1071
75% 1109
80% 1145
90% 1338
95% 1753
98% 1832
99% 1888
100% 1976 (last request)
<

InnoDBの場合


>

Benchmarking rakuda.localhost (be patient)
Server Software: Apache/1.3.36
Server Hostname: rakuda.localhost
Server Port: 80

Document Path: /
Document Length: 14911 bytes

Concurrency Level: 10
Time taken for tests: 110.730 seconds
Complete requests: 1000
Failed requests: 0
Broken pipe errors: 0
Total transferred: 15167000 bytes
HTML transferred: 14911000 bytes
Requests per second: 9.03 [#/sec] (mean)
Time per request: 1107.30 [ms] (mean)
Time per request: 110.73 [ms] (mean, across all concurrent requests)
Transfer rate: 136.97 [Kbytes/sec] received

Connnection Times (ms)
min mean[+/-sd] median max
Connect: 0 0 0.0 0 0
Processing: 765 1102 273.9 1010 2476
Waiting: 765 1102 273.9 1010 2476
Total: 765 1102 273.9 1010 2476

Percentage of the requests served within a certain time (ms)
50% 1010
66% 1059
75% 1120
80% 1180
90% 1466
95% 1787
98% 1973
99% 2198
100% 2476 (last request)
<


結果としてMyISAMもInnoDBも速度的にはほとんど変わらなかった(ちょっとMyISAMのほうが速かった)ので、今まで通りMyISAMでいいかという結論に達しました。

2006年4月15日土曜日

Sledge::Plugin::FormValidator::Simple-0.02

Sledge::Plugin::FormValidator::Simpleを0.02にバージョンアップしました。



Sledge::Plugin::FormValidator::Simple-0.02



0.01ではリダイレクト先やプロファイルをメソッドを定義することで変更できるようにしていましたが、あんまりかっこよくない気がしたので、メンバ変数として定義するようにしよう変更しました。

それと、valid_before_foo, valid_after_fooを定義することができるようにしました。
使い方はこんな感じです。

>

sub valid_before_foo {
my $self = shift;

# change profile by query param.
if ( $self->r->param('step') == 2 ) {
$self->validator->profile_name($self->profile_name . ".step2");
}else {
$self->validator->profile_name($self->profile_name . ".step1");
}

# change redirection when error is found.
$self->validator->redirect_url('/');
}

sub valid_after_foo {
my $self = shift;
my $results = $self->validator->results;
if ( found some error... ) {
$results->set_invalid('param3' => 'MY_ERROR');
}
}

<

JavaScriptでのバリデーションはまた後日対応したいです。

2006年3月24日金曜日

Sledge::Plugin::FormValidator::Simple - Sledgeのバリデータ

Sledge用のバリデータを作ろうと決めてから結構たってしまいましたが、ようやくそれっぽいものができました。



Sledge::Plugin::FormValidator::Simple-0.01



今のところFormValidator::Simple::Extensionが必要です。
Sledge::Plugin::FormValidator::Simpleを使う場合にはあらかじめこれをインストールしておく必要があります。

このバリデータの特徴は以下のとおりです。
- プロファイルをYAMLで定義できる
- 1つのYAMLファイルにすべてのプロファイルを記述でき、バリデーション時には自動で選択される
- 使用するプロファイルを手動で選択することも可能
- エラー時の挙動も定義可能
- エラーメッセージもYAMLで記述可能。テンプレート側でメッセージを選択可能



以下使い方の説明です。


まずPagesクラスでSledge::Plugin::FormValidator::Simpleをuseします。

>


package Your::Proj::Pages;
use Sledge::Plugin::FormValidator::Simple;

<

Sledge::Plugin::FormValidator::Simpleをuseすると、validatorメソッドがPagesクラスに
追加されます。

次にコンフィグにバリデーションに関する設定を追加します。

>


package Your::Config::_common;

$C{VALIDATOR_OPTIONS} = {
profile => '/path/to/profiles.yml',
messages => '/path/to/messages.yml',
messages_decode_parms => 'utf-8',
plugins => [
'FormValidator::Simple::Plugin::Japanese',
'Your::Original::Validation',
],
loader => 'YAML::Syck',
};

<


さらにプロファイル、エラーメッセージを定義したYAMLを用意します。

>

# profiles.yml

"Your::Pages::Root" :
contact :
- name
- [ [NOT_BLANK], [LENGTH, 0, 30] ]
- email
- [ [NOT_BLANK], [EMAIL_LOOSE], [LENGTH, 0, 100] ]
- tel
- [ [NOT_BLANK], [NUMBER_PHONE_JP], [LENGTH, 0, 30] ]
- content
- [ [NOT_BLANK], [LENGTH, 0, 5000] ]
login :
- userid
- [ [NOT_BLANK], [ASCII] ]
- password
- [ [NOT_BLANK] ]
"Your::Pages::Signup" :
input :
step1 :
- userid
- [ [NOT_BLANK], [ASCII] ]
- password
- [ [NOT_BLANK] ]
- name
- [ [NOT_BLANK], [LENGTH, 0, 30] ]
- email
- [ [NOT_BLANK], [EMAIL_LOOSE], [LENGTH, 0, 100] ]
step2 :
- tel
- [ [NOT_BLANK], [NUMBER_PHONE_JP], [LENGTH, 0, 30] ]
- address
- [ [NOT_BLANK], [LENGTH, 0, 200] ]

# messages.yml
DEFAULT:
name:
DEFAULT: name is invalid!!
action1:
name:
NOT_BLANK: input name!
LENGTH: input name(length should be between 0 and 10)!
email:
DEFAULT: input correct email address!
action2:
name:
DEFAULT: hoge.

<

バリデーション実行時、Pagesクラス名とdispatch名に対応したプロファイルが自動で選択されます。
対応したプロファイルがない場合、バリデーションは実行されません。
使用するプロファイルを手動で変更したい場合は、valid_profile_$pageメソッドを定義します。

>


package Your::Pages::Signup;
use strict;


sub valid_profile_foo {
my $self = shift;
if ( $self->r->param('step') == 2 ) {
return ('step2');
}else {
return ('step1');
}
}

sub dispatch_foo {
...
}
sub post_dispatch_foo {
...
}


<

valid_profile_$pageメソッドは使用するプロファイルのキー名を返すようにします。
上記の例ではクエリー内のstepが2の場合はYAML内の定義のうち、 Your::Pages::Signup -> foo -> step2 が使用されます。


バリデーションでエラーがみつかると、セッションにvalid_pageとvalid_resultがセットされます。
それぞれの値はの下記のとおりです。


バリデーション結果をセットした後、デフォルトでは、current_urlで取得されるURLへのリダイレクトを行います。
もしリダイレクト先を手動で設定したい場合は、valid_redirect_url_$pageを定義します。

>


sub valid_redirect_url_foo {
return '/foo/error';
}

<

また、リダイレクト以外の処理をさせたい場合はvalid_error_$pageを定義します。

>


sub valid_error_foo {
my ($self, $result) = @_;
$self->tmpl->param(result => $result);
$self->load_template('/bar/error');
$self->output_content;
}

<

valid_error_$pageを定義すると、リダイレクトする代わりにこのメソッドが呼び出されます。
上記の例はテンプレートに結果をセットしエラーテンプレートをロードしています。
valid_error_$pageはvalid_redirect_url_$pageより優先されます。


Sledge::Plugin::FormValidator::SimpleがuseされたPagesクラスおよびそのサブクラスでは、
dispatch前にセッションからバリデーションエラーの際にセッションにセットされた
valid_result、valid_pageを取得しようと試みます。(取得の際、セッションからこれらのキーは削除されます。)
valid_resultを取得できた場合、valid_result、valid_pageをテンプレートのパラメータとしてセットし、
さらにfillin_formをloadし、バリデーションに通った値をfillinします。



2006年3月10日金曜日

SledgeのValidatorを作ろうと決意するにいたったわけ

Sledgeのバリデータといえば、Sledge::Plugin::Validatorなんですが、このプラグインが出てくる前からSledgeをいじっていたので、バリデータは自前で実装したものを使っていました。

このバリデータの機能はこんな感じのものでした。

- バリデーションのルールはXMLで定義する。
- バリデータをuseするだけで、バリデーションが効くようになり、それを削除してもプログラムは問題なく動く(Pagesクラスにバリデーションに関するコードを書く必要がない)
- バリデーションはPOSTリクエスト時にのみ作動し、BEFORE_DISPATCHのトリガとして動作する
- エラーを見つけるとテンプレートのパラメータにエラーをセットし、post_dispatch_fooをスキップし、dispatch_fooのみ実行する
- バリデータをuseするだけで自動的にバリデーションが効くようにするということをやるために、XMLの設定ファイルでは、Pagesクラスとdispatch名とそれごとのルールを記述できるようになっている
- エラーメッセージはデフォルトのものが用意されており、上記XMLファイルでルールごとに独自のメッセージを定義することもできる。

当時は設定ファイルといえばXMLだ!ってな風潮だったので、それにのっかって設定ファイルをXMLにしたんです。このバリデータ、今も使っているのですが、使っていくうちにいろいろと不満な点も出てきていました。たとえば、

- XMLの可読性が低い!ルールを定義するときも設定ファイルとにらめっこ。
- メッセージの切り替えができない(英語版と日本語版のページを用意する場合にこまる)
- エラーがあった際の動作を独自に定義できない。
- 1つのdispatch内で条件によってルールを変更できない。(できるけどやり方が変態的)

とまぁ結構あったのですが、実際にどうしてもこのバリデータじゃできないってことに直面しなかったので、ちょこちょこ手を加えながらこれを使いつづけてました。
それが、前回の記事、「SledgeでUTF-8なサイトをつくる」の方法で、UTF-8なサイトを作った時のこと、プログラム内で扱うデータがFlagged UTF-8になったため、エラーメッセージが文字化けしてしまうようになってしまったため、重い腰を上げて新たにバリデータを作ることにしました。

まず、Flagged UTF-8を扱えるようにすること、さらに設定ファイルはXMLから可読性が高くて最近流行のYAMLに変更、バリデーションを実行する部分はFormValidator::Simpleを使うことで、FormValidator::Simple用にたくさん用意されたプラグインの恩恵を受けられるようにというような方針で作ろうと思います。

最初、Sledge::Plugin::Validatorを拡張して使おうかとも思ったのですが、設定ファイルにdispatchごとのルールを定義しておくと、自動でバリデーションを実行してくれるという挙動が気に入っているので、自分で作ることにしました。

2006年3月7日火曜日

SledgeでUTF8なサイトを作る。

Sledgeはプログラム内で扱うデータがEUC-JPにエンコードされたバイナリデータを前提としていて、テンプレートもEUC-JPで作成する必要があります。
これだけが原因ではないのですが、UTF-8なサイトを作ろうとすると、結構いろいろなワナがあります。
このことはSledgeのメーリングリストでも、過去にいろいろ議論されています。
http://lists.sourceforge.jp/mailman/archives/sledge-users/2004-March/000281.html
http://lists.sourceforge.jp/mailman/archives/sledge-users/2004-March/000292.html
http://lists.sourceforge.jp/mailman/archives/sledge-users/2004-March/000294.html

これらの議論は2004年の3月に行われているので、もう2年も前。これだけ時間がたっているので、SledgeでUTF-8をガンガン使っている人もたくさんいると思うのですが、最近ふと、SledgeでUTF8なサイトを作ろうと思い、情報集めのためにググッてみた際、「SledgeでUTF-8扱うにはこうすればいい!」というようなそのものずばりな解決法が見つからなかったので、僕がSledgeでUTF8なサイトを作ったその過程と成果物をさらしてみたいと思います。

* 過程

まず、SledgeでUTF-8を扱うにあたって、以下のような暗黙のルールを定義しました。

- プログラム内で扱うデータはFlagged UTF-8
- テンプレートの文字コードはUTF-8
- RequestクラスでインプットデータをFlagged UTF-8にデコード
- アウトプット時にUnflagged UTF-8にエンコード
- mod_perlで運用すること前提。CGIでの動作は無視


次に上記を実現するようなモジュールを書いてみました。

*** Sledge::Pages::Apache::I18N

まずは、インプットデータをFlagged UTF-8にデコードするRequestクラスを作ってみました。これはSledgeに標準でついているSledge::Charset::UTF8を使うとできそうですが、Sledge::Charset::UTF8でインプットデータをFlagged UTF-8にデコードしてもApache::RequestがUnflaggedなデータにしてしまうため、うまくいかないようです。
なので、Apache::Requestをどうにかしないといけないのですが、CPANを眺めていたら、Apache::Request::I18Nというまさにそのものずばりなモジュールがあったのでこいつを使ってみました。
こいつを使って書いたSledge::Pages::Apacheの代替モジュールはこんな感じです。

>

package Sledge::Pages::Apache::I18N;
use strict;
use base qw(Sledge::Pages::Base);

use Apache;
use Apache::Request::I18N;

sub create_request {
my ($self, $r) = @_;
my $req = Apache::Request::I18N->new(
$r || Apache->request,
DECODE_PARMS => 'utf-8',
);
# $req->param; do parse here
return $req;
}

1;
<


*** Sledge::Charset::UTF8::I18N

次に、Sledge::Pages::Apache::I18Nでインプットデータのデコードをやってくれるようになったので、
アウトプットの際のエンコードのみを行うSledge::Charsetサブクラスを作りました。

>

package Sledge::Charset::UTF8::I18N;
use strict;
use base qw(Sledge::Charset::Null);

use vars qw($VERSION);
$VERSION = '0.01';

use Encode;

sub content_type {
return 'text/html; charset=UTF-8';
}

sub output_filter {
my($self, $content) = @_;
return Encode::encode("UTF-8", $content);
}


1;
<

*** Sledge::Template::TT::I18N

最後に、UTF-8なテンプレートを読み込んで内部ではFlagged UTF-8として扱ってくれるSledge::Template::TTの代替モジュールを作成しました。Sledge::Template::TTだとUTF-8なテンプレートを読み込んでも内部では、UnflaggedなUTF-8として扱われるので、Flagged UTF-8な文字列を埋め込むと文字化けを起こしてしまいます。

>

package Sledge::Template::TT::I18N;
use strict;
use base qw(Sledge::Template::TT);

use vars qw($VERSION);
$VERSION = '0.01';

sub output {
my $self = shift;
my %config = %{$self->{_options}};
my $input = delete $config{filename};
$config{LOAD_TEMPLATES} = [Sledge::Template::TT::I18N::Provider->new(\%config)];
my $template = Template->new(\%config);
unless (-e $input) {
Sledge::Exception::TemplateNotFound->throw(
"No template file detected. Check your template path.",
);
}
$template->process($input, $self->{_params}, \my $output)
or Sledge::Exception::TemplateParseError->throw($template->error);
return $output;
}

package Sledge::Template::TT::I18N::Provider;
use strict;
use base qw(Template::Provider);

sub _load {
my $self = shift;

my ($data, $error) = $self->SUPER::_load(@_);

if(defined $data) {
$data->{text} = utf8_upgrade($data->{text});
}

return ($data, $error);
}

sub utf8_upgrade {
my @list = map pack('U*', unpack 'U0U*', $_), @_;
return wantarray ? @list : $list[0];
}

1;
<

*** MyProj::Pagesクラスに組み込む

今まで作成したモジュールはこんな感じでPagesクラスに組み込みました。

>


package YourProj::Pages;
use strict;
use base qw(Sledge::Pages::Apache::I18N);
use Sledge::Template::TT::I18N;
use Sledge::Charset::UTF8::I18N;

....

sub create_charset {
my $self = shift;
Sledge::Charset::UTF8::I18N->new($self);
}

<

これでとりあえず、SledgeでUTF-8を扱うことができました。

* 成果物

今回作成したモジュールたちです。

- Sledge-Pages-Apache-I18N-0.01
- Sledge-Template-TT-I18N-0.01
- Sledge-Charset-UTF8-I18N-0.01

2006年2月3日金曜日

SledgeのPluginをいくつかCPANにアップしました

ちょこちょと作ってこのブログでも紹介していたSledgeのプラグインのうちいくつかをCPANにアップロードしました。

- Sledge::Plugin::Prototype 0.02
- Sledge::Plugin::IfModifiedSince 0.05

Sledge::Plugin::IfModifiedSinceはブログで公開したのからちょっと改良を加えていて、set_last_modifiedメソッドでLast-Modifiedヘッダに更新日をセットすることができるようになったり、if_modified_sincedメソッドにファイルのパスを渡せば、そのファイルの更新日とIf-Modified-Sinceヘッダを比較するようになったりしてます。

2005年11月6日日曜日

Sledgeドキュメント公開!!

Sledgeのドキュメントをにぽたんさんが公開されてます。

Sledge ドキュメント - にぽたん研究所


先日のshibuya.pmテクニカルトーク#6でのCatalystマンセームードに反抗すべく公開ということですが、Sledgeマンセーな僕にとってはうれしい限り。この勢いでドキュメントが整備されて、SledgeをCPANに登録!ってことにはならないでしょうか。。なるといいなぁ。。

そういえば前に社内でSledgeのドキュメント作ろうって話になって、書き始めたけど結局完成しなかった、ってことがあったんですが、あれどうなってるんだろう?





2005年11月2日水曜日

Sledge::Plugin::Prototype 0.02

伏原さんより、いただいたパッチをそのまま適用して、show_prototype_jsメソッドを追加したSledge::Plugin::Prototype-0.02をアップしました。

Sledge::Plugin::Prototype - HTML::Prototype wrapper

いままで、prototype.jsのソースコードは [% prototype.define_javascript_functions %]を使ってテンプレートに直接埋め込む方法しか取れませんでしたが、
今回追加したshow_prototype_jsを使えば、prototype.js自体を吐き出すPagesクラスを作れます。

伏原さんパッチどうもありがとうございました。



2005年10月28日金曜日

Sledge::Plugin::IfModifiedSince

ひさしぶりにSledgeのプラグインを書いてみました。


Sledge::Plugin::IfModifiedSince 0.01
<追記 date="2005/10/30">上のリンク先が404だったのを直しておきました。失礼しました</追記>


If-Modified-Sinceヘッダからコンテンツが更新されたかを判定するif_modified_sinceメソッドと、レスポンスとしてNot Modifiedを返すnot_modifiedメソッドをPagesクラスにインポートします。

各メソッドはこんな感じで使います。

>

package Your:Pages;
use Sledge::Plugin::IfModifiedSince;

sub dispatch_foo {
my $self = shift;

my $last_modified_epoch = ...;

unless ( $self->if_modified_since($last_modified_epoch) ) {
$self->not_modified;
return;
}
}
<

If-Modified-Sinceを投げてくれるやさしいエージェントにはこちらもやさしくしないとってことでちょこっと書いてみました。



2005年9月15日木曜日

Sledge::Dispatcherリリース

Sledge用のmod_perlハンドラSledge::Dispatcherがリリースされたみたいです。

トリガのCGIファイルを作らないですむのはかなり便利ですね。
もうひとつ僕がいいなーとおもったのは、URLからPages内のdispatchメソッドがどこにあるかわかりやすくなるなーという点。

トリガのCGIファイルを使う場合、CGIファイル内に呼び出すPagesクラスを記述するのですが、Pagesクラスならどのクラスも
呼べるので、適当に設計して作っていると、URLとPagesクラスが対応しなくなっちゃうときがあるんです。特に僕はちょっと考えたら、
まず手を動かしちゃう人なので、うまく対応しなくなってCGIファイルを編集しなおしなんてことをよくやっちゃいます。

なので、dispatchメソッドを書いた時点でURLが決定するSledge::Dispatcherはかなりイイ!と思いました。

via: #!shebang.jp:Sledge::Dispatcher





2005年6月9日木曜日

SledgeによるWebアプリケーションフレームワーク入門

[ThinkIT] 第1回:Webアプリケーションフレームワークとは (1/4)
SledgeによるWebアプリケーションフレームワーク入門という連載がThinkITでスタートしたようです。
Sledgeは使いこなせればすごく便利なフレームワークだと思いますが、ドキュメントが少ないので、さぁフレームワークを導入しようとなったときにちょっと手を出しにくいなのかなぁって気がします。
なので、こういうわかりやすい記事が出てくるのはいいですねー。

僕の会社でもフレームワークを導入して結構たちますが、最近思うのはフレームワークだけ導入しても、その本来の目的である「工数の削減」「品質の均一化」「メンテナンス性の向上」を達成するのは難しいということです。フレームワークを利用してもその上でどういうコードを書くのかという規則、つまりその会社のコーディング規約のようなものがきちんとできていないと、結局品質のばらつきはでますし、メンテナンスも容易でなくなるんですよね。
かといってコーディング規約を文章でまとめておいても、それをすべて頭にたたき込むってのは結構しんどい作業だと思うし、結局誰も規約に目を通してなくて、うまくいかないなんてことになりがちです。
そこで登場するのがペアプログラミング。少し前に、はてなの伊藤さんとお話したときに、はてなでは新しく入社したらまずペアプログラミングをして、はてなフレームワークの使い方と、はてな的な実装方法を教え込むのだといっておられました。コーディング規約を頭で覚えるのでなくて、ペアプログラミングによって体で覚える。ペアプログラミングなんてやっている余裕はないなんて思いがちですが長い目で見ると、工数削減、品質、メンテナンス性向上するためには、遠回りのようでこれが一番いい方法なのかもしれませんね。


2005年5月9日月曜日

Sledge::Plugin::Prototype - HTML::Prototype wrapper

最近Catalystをいじりはじめました。僕はWebアプリケーションを作るときはもっぱらSledgeを使っていますが、なにやらCatalystが熱いようなのでSledgeとの比較も含めて試してます。

まず試しにAjaxっぽいアプリを作って見たんですが、そこで使ったCatalyst::Plugin::PrototypeがJavascriptを一行も書かないでAjax出来て素敵だったのでこれをSledgeでも使えるようなプラグインを書いてみました。

Sledge::Plugin::Prototype - HTML::Prototype wrapper

ちなみにPrototypeは何かの原型というわけではなくて、prototypeという名前のオブジェクト指向Javascriptライブラリなんだそうです。


2005年3月31日木曜日

Sledge + MySQL4.1でセッションを保存できない

Sledge+MySQL4.1な環境でセッションを保存できないという状況に陥りました。
現象としては、セッションIDは保存されているのに、セッションデータ(a_session)だけが保存されないという状況です。どうやらTEXT型のフィールドにStorableモジュールでnfreezeしたデータを入れることができないみたいなので、原因を調べてみました。。

MySQL リファレンスマニュアル :: 2.5.1 バージョン 4.0 から 4.1 へのアップグレードによると、
>
すべてのテーブルと文字列カラムがキャラクタセットを持つようになった。 See 章 9. 各国キャラクタセットと Unicode。 キャラクタセット情報は、SHOW CREATE TABLE と mysqldump によって表示される (MySQL バージョン 4.0.6 以降は、新しいダンプファイルを読み取ることはできる。これより前のバージョンは、新しいダンプファイルを読み取ることはできない)。
<
ということで、4.1からはTEXT型がキャラクタセットを持つようになったようです。試しにshow create talbe sessionsを実行すると以下のように、CHARSET=ujisがついていました。
>

CREATE TABLE `sessions` (
`id` varchar(32) NOT NULL default '',
`a_session` mediumtext,
`timestamp` timestamp NOT NULL default CURRENT_TIMESTAMP on update CURRENT_TIMESTAMP,
PRIMARY KEY (`id`)
) ENGINE=MyISAM DEFAULT CHARSET=ujis
<

このことから、ujis(EUC)のキャラクタセットをもつTEXTフィールドにnfreezeしたujis以外の文字コードを含んだデータをインサートしようとしたため、データが空になってしまうのではと予想しました。

そこでSledge::Session::Pgにならって、nfreezeしたものをbase64エンコードして、ujisの文字コード内で表現できるデータにしてからインサートするようにしたところ、うまくデータを保存できるようになりました。実際には以下のようなSledge::Session::MySQLのサブクラスを作って、替わりにcreate_sessionにセットしてあげました。
>

package Sledge::Session::MySQL41;
use strict;
use base qw(Sledge::Session::MySQL);

use MIME::Base64;

sub _serialize {
my($self, $data) = @_;
return encode_base64(Storable::freeze($data));
}

sub _deserialize {
my($self, $data) = @_;
return Storable::thaw(decode_base64($data));
}

1;
<

2005年2月10日木曜日

Sledge::Plugin::JavaScript::DocumentWrite

Sledge::Plugin::JavaScript::DocumentWrite - output content as javascript's document.write

Outputをdocument.writeにして出力するSledgeのプラグインを書いてみました。
フィルターの部分はApache::JavaScript::DocumentWriteのコードを参考にさせてもらいました。
それとContent-typeをtext/plainにするためにオリジナルのoutput_contentにちょっとだけ手を加えたものをAFTER_DISPATCHにHookしてます。ちょっと強引かなー。

>

package Sledge::Plugin::JavaScript::DocumentWrite;

use strict;
use vars qw($VERSION);
$VERSION = '0.02';

sub import {
my $class = shift;
my $pkg = caller(0);
no strict 'refs';
*{"$pkg\::add_documentwrite_filter"} = \&add_documentwrite_filter;
}

sub add_documentwrite_filter {
my $self = shift;
$self->add_filter(\&documentwrite_filter);
$self->register_hook(AFTER_DISPATCH => \&output_content);
}

sub documentwrite_filter {
my ($self, $content) = @_;
return join "\n", map {
s/\x27/'/g; # '
$_ = "document.writeln('$_');";
} split /\n/, $content;
}

sub output_content {
my $self = shift;
$self->r->content_type('text/plain');
my $content = $self->make_content;
$self->set_content_length(length $content);
$self->send_http_header;
$self->r->print($content);
$self->invoke_hook('AFTER_OUTPUT');
$self->finished(1);
}

1;
<



2004年11月30日火曜日

Sledge::Plugin::SaveUploadImage

フォームからアップロードされた画像を指定の形式に保存するためのSledgeプラグインを書いてみました。

Sledge::Plugin::SaveUploadImage

自分のPagesクラスでuseすると、各画像フォーマット保存用に用意したsave_as_jpg, save_as_gif, save_as_pngがアップロードオブジェクトにインポートされます。save_as_xxxメソッドは引数に保存先のディレクトリのみ指定した場合は保存するファイル名を自動的に決定してくれるので、テンポラリーファイルを保存する際にいちいちテンポラリファイル名を考えるのが面倒くさいと思っていた人にとっては便利かなーと思います。
こんな感じに使います。

>

package Your::Pages;
use Sledge::Plugin::SaveUploadImage;

my $upload = $self->r->upload('upload_file');
my $filename = $upload->save_as_jpg( $save_dir );
<


2004年11月11日木曜日

Foo-conf.plの使い方

SledgeでConfigファイルを読み込む際、環境変数SLEDGE_CONFIG_NAMEが設定されていないと、/etc/Foo-conf.pl(Fooはプロジェクト名)というファイルが実行されるんですが、このファイルには接直SLEDGE_CONFIG_NAMEを設定するような記述を書いておくんだそうです。具体的には以下のような記述を書いておきます。
>

$ENV{SLEDGE_CONFIG_NAME} = 'staging';
1;
<

またhttpd.confに以下のような記述を書くことで同様のことを実現できます。

>

PerlSetEnv SLEDGE_CONFIG_NAME production
<

こちらを使う方がなにかと便利なので、実際にはFoo-conf.plを作成することはほとんどないかもしれませんね。

実はFoo-conf.plが何に使われるのかわかっていなくて、miyagawaさんに直接聞いてしまったんですが、Sledge::Doc::Installにこのファイルに関することが書いてあったのに後で気づきました。。Docに書いてあるのに親切に答えて頂いたmiyagawaさん、ありがとうございました。感謝です。


2004年10月17日日曜日

2004年10月16日土曜日

Sledge::Template::TT::Shift_JIS - テンプレートファイルをShift-JISでかけるようにする

Sledgeは出力するコンテンツがShift-JISの場合でもテンプレートはEUC-JPで書く仕様になっています。
僕の会社では基本的にHTMLの文字コードはShift-JISということになっているのでコーダーからあがってくるHTMLは当然Shift-JISです。なのでこれをテンプレート化するにはパラメータの埋め込みの他、文字コードをEUC-JPに変換をしなければなりません。一括変換すればすむ話ですが、それがちょっとめんどくさい(笑)

ということで、テンプレートをShift-JISで書くためのSledge::Template::TTのサブクラス、Sledge::Template::TT::Shift_JISを作ってみました。使い方は簡単でPagesクラスで、

use Sledge::Template::TT;
と書いているところを
use Sledge::Template::TT::Shift_JIS;
に変更するだけです。

Sledge::Template::TT::Shift_JISのコードはこんな感じです。

package Sledge::Template::TT::Shift_JIS;

use strict;
use vars qw($VERSION);
$VERSION = '0.01';

use base qw(Sledge::Template::TT);

use Template;
use Sledge::Exceptions;
use FileHandle;
use Jcode;

sub output {
my $self = shift;
my %config = %{$self->{_options}};
my $input = delete $config{filename};
my $template = Template->new(\%config);
unless (ref($input) || -e $input) {
Sledge::Exception::TemplateNotFound->throw(
"No template file detected: $input",
);
}
my $text = do { local $/; my $fh = FileHandle->new($input); <$fh> };
$text = Jcode->new($text, 'sjis')->euc;
$template->process(\$text, $self->{_params}, \my $output)
or Sledge::Exception::TemplateParseError->throw($template->error);
return $output;
}

1;
最初は下のようなコードを書いたんですが、テンプレートのオプションのfilenameがスカラーリファレンスだとSledge::Tempate::TTのリビジョンが1.3より以前のものでは動かないことに気づいて、Sledge::Tempate::TTのコードをそのまま借りてきて、文字コード変換を差し込んだ結果、上のようなコードに落ち着きました。
package Sledge::Template::TT::Shift_JIS;

use strict;
use vars qw($VERSION);
$VERSION = '0.01';

use base qw(Sledge::Template::TT);

use FileHandle;
use Jcode;

sub output {
my $self = shift;
my %config = %{$self->{_options}};
if ( -e $config{filename} ) {
my $text = do { local $/; my $fh = FileHandle->new($config{filename}); <$fh> };
$text = Jcode->new($text, 'sjis')->euc;
$self->set_option(filename => \$text);
}
$self->SUPER::output;
}

1;



2004年10月14日木曜日

Sledgeでセッションを使わないとき

Sledge::Doc::FAQにも書いてあるようにセッション管理が必要ない場合はconstruct_sessionを空のメソッドでオーバーライドすればいいのですが、さらにcreate_managerも空メソッドでオーバーライドしておけば、SessionManagerもuseしなくてすみます。これでほ~~んのちょっぴりメモリが節約できます。