2006年12月5日火曜日

mysqldumpで文字化けしないためのメモ

ローカルの開発環境で使っていたMySQLなんですが、何も考えずデフォルトの設定で使っていたら、mysqldumpした際に、データが文字化けして、ちょっとは待ったのでメモ。

MySQLのバージョンは4.1.20。
文字化けする原因は、mysqldumpがデフォルトでは、文字コードをUTF-8で出力するようになっていて、フィールドの型がUTF-8でない場合は、自動でUTF-8に変換するためのようです。

僕が使っていたDBは文字コードについて特に何も設定していなかったので、デフォルトの文字コードであるlatin1になっていました。

なので、これもmysqldumpするとlain1 -> UTF-8な変換が自動で行われ文字化けしたということのようです。納得。

この自動変換を行わないようにすれば解決するはず。ということで調べてみると、--default-character-setというオプションを使うとよいことがわかりました。これを使ってデフォルトの文字コードをDBの文字コードとあわせてやることで、自動変換が行われなくなり、文字化けしないようです。
で、結局以下のコマンドで文字化けせずdumpすることができました。

>

mysqldump --default-character-set=latin1 -uroot --all-databses > db.dump
<

これでデータ自体は文字化けしなくなりますが、これをそのまま、UTF-8なDBに取り込むと、取り込んだデータが文字化けしてしました。
ダンプしたデータを見てみると、所々にSET NAMES latin1とかDEFAULT CHARSET=latin1のように「latin1]の文字が。。
これが原因だったようで、ワンライナーでlatin1をutf8に変更してからインポートしたところ、文字化けせずに取り込むことができました。

>

perl -pi -e 's/latin1/utf8/' db.dump
<




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年10月23日月曜日

Akamaiで認証付きコンテンツを配信する方法

IPAに脆弱性として提出されていた、ミクシィにアップロードされた画像がURLを直接たたけばログインしていなくても閲覧できる件が技術的には改修せず、ヘルプにその旨を記載することで決着したという話題について、その理由のひとつに画像の配信は一部、CDN(akamai)を使っているため、そこに認証をかけるのが難しいのではというものを見かけました。

このakamaiなのですが、実は、僕が開発運用している動画共有サイトFlipClipでも、日ごとに増え続けるサーバへの負荷、トラフィックに対応すべく、動画の配信にこれを使えないかと検討してまして、先日akamaiの人にきていただいて話を聞いてみました。

このとき一番聞きたかったのがまさに今回のミクシィの件で話にでてきた「認証のかかったコンテンツをakamaiで配信できるのか?」という点でした。
というのもFlipClipでは動画・サムネールの配信はすべてmod_perlアプリケーションから動的に行っていて、動画に設定されたプライバシーからユーザのアクセス権を判定し、OKならば動画・サムネールを吐き出すという処理をおこなっているからです。

この質問に対してのakamaiの方からの回答は、3つの方法があるというものでした。

+ akamai-FlipClip間でルールを決めて作成したCookieを使ってアクセス権を制御する方法
+ akamai-FlipClpi間でルールを決めて作成したクエリパラメータを使ってアクセス件を制御する方法
+ akamaiはIf-Modifiedヘッダ付きのリクエストを毎回FlipClipに送りつけ、認証はFlipClipに任せる方法

1のCookieを使う方法と2のクエリパラメータを使う方法は、どちらもあらかじめakamaiとFlipClipの間で認証OKかNGかをakamaiが理解できるクッキー、クエリパラメータ生成ルールを決めておき、それを動画・サムネールのリクエストと一緒に送りつけると、それを元にakamaiが認証を行い、キャッシュがあればakamaiからコンテンツを返すという方法だそうです。
この方法の利点としては、

- akamaiが認証を行うので、動画のリクエストの際に、FlipClipまでリクエストが飛んでこず、FlipClipのサーバへの負荷はかなり減る

という点があげられるんですが、欠点として、

- 認証箇所が2箇所(akamaiでの認証だけでなく、FlipClipでもCookieやクエリパラメータを生成する際に認証が必要)になってしまうため、セキュリティ的にリスクが大きくなってしまう
- akamaiのためにそれ用の実装をしないといけない
- 仮にクエリパラメータがばれたら誰でもアクセスできることになる
- そもそもクエリパラメータをつけるなんてかっこ悪い

という点があり、ちょっと微妙な感じだなーと感じました。

3の毎回FlipClip側に認証を求める方法ですが、これは、akamaiは認証は行わず、akamaiに動画・サムネールのリクエストが送られてきたら、そのリクエストにIf-Modified-SinceヘッダをつけてFlipClipのサーバに転送してくれるという方法だそうです。

これの利点は、

- アクセス制御はFlipClip側1箇所で行うので、ここだけを考えればいい。
- 同じ動画・サムネールへのリクエストが多いという傾向があれば、キャッシュのヒット率があがり、FlipClipサーバからのトラフィックをぐんと軽減することが期待できる

欠点としては、

- FlipClip側がIf-Modified-Sinceヘッダを理解できるようにしないといけない
- 動画・サムネールへのアクセスがほどよく分散されているような傾向の場合は、キャッシュヒット率があまりあがらず、本サーバ側の負荷軽減は小さくなる
- リクエストごとにFlipClip側にリクエストが飛ぶので、クッキーやクエリパラメータを使った方法よりは、サーバの負荷がかかる

という点があげられますが、
1つ目の欠点は、すでにIf-Modified-Sinceは理解するようになっているので、問題なし。
2つ目の欠点は、FlipClipの動画の配信傾向を見ていると、パレートの法則にほぼ従っていて、1日に配信される動画のうち20%の動画の再生数が全体の再生数の約90%を占めているという状況なので、キャッシュのヒット率はかなり高くなりそうなので、問題なし。
3つ目の欠点はリクエストは毎回FlipClipのサーバまで飛んできますが、上記のとおりキャッシュがうまく働いてくれそうなので、FlipClipサーバはほとんど、302をレスポンスとして返すだけで済むはずで、負荷、トラフィックはかなり減らすことが期待できそうなので、まあ問題なし。

ということで、この方法はいいかもという感想でした。

このようにakamai経由でも認証付きコンテンツの配信はできそうなのですが、なぜミクシィがそれをできないのかというと単純にこの修正で影響を受ける箇所が多すぎて、直すに直せないってことなんじゃないかなーとか、静的に返していたコンテンツを動的にアプリケーションから吐き出すようにすると、パフォーマンスがでないと考えているのかなーとか考えちゃいますがほんとのところはどうなんでしょうねぇ。


via: スラッシュドット ジャパン | ミクシィ、画像に認可制御なしの欠陥を改修できず、ヘルプで弁解

2006年10月4日水曜日

Firefoxの「選択した部分のソースを表示」はJavaScriptで動的に生成したHTMLも表示される

これ知りませんでした。「選択した部分のソースを表示」しても、普通にその部分の生のソースが出てくるだけかと思い込んでました。

最近はJemplateなんかを使って動的にHTMLを生成するというのをやることが多いんですが、これ、HTMLのメンテがしづらいとデザイナーさんからはすこぶる評判がわるかったんです。
これで少しはメンテが楽になるかなー。

via: subtechグループ - マングローブ - JavaScriptなんかでいじられた後の現在のソースを表示

巨大なFLVを再生中に他のリンクをクリックしてもなかなか移動できない現象を回避する方法

FlipClipでクリップを見ていると、再生の途中で画面内のリンクをクリックして他のページに移動しようとしても、なかなか移動できなくてイライラすることがあったので、これを回避する方法がないものかと考えていたのですが、今日試した方法が有効だったので紹介します。

この現象は、再生している動画のサイズが大きい場合によく起こる現象で、リンクをクリックしてもブラウザはこの大きな動画の再生に忙しいのか、なかなか画面を切り替えてくれません。

そこで考えたのが、クリックした時に、再生している動画をけしてしまうという方法です。
試した方法は簡単で、リンクなどをクリックしてページが切り替わるタイミングで、再生中のフレームを含むdiv要素のinnnerHTMLを空にしてしまうというものです。
コードのイメージはこんな感じです。

>

<script type="text/javascript"><:!--
Event.observe(window, 'beforeunload', function(){
$('clipPlayer').innerHTML='';
});
--></script>
<

最初はbeforeunloadではなくunloadで試してみましたが、タイミングが遅いらしく、効果がありませんでした。
beforeunloadはブラウザによっては動かないといった情報をどこかで見たような気がしたのですが、IE, FireFox, Safariでうまく動いたので、互換性に問題なしと判断し、FlipClipでもこの方法を早速採用しました。

画面の移動がスムースになって、いい感じです。



特定のネットワークからは無制限に、外からのアクセスはパスワードを要求

今までapacheでアクセス制限かけるときはIP制限ならIP制限だけ、標準認証なら標準認証だけしか設定したことがなかったし、それで事足りていたのでなにも困ることはなかったんですが、社内のネットワークやサーバ間で通信する相手からのアクセスは無制限に行いたいけど、その他のネットワークからのアクセスはパスワードで制限したいという状況になったので、ちょっと調べてみたら、結構簡単にできました。

>

Require valid-user
Allow from 192.168.1
Satisfy Any
<

Apacheのドキュメントに普通に書いてありました。マニュアルはちゃんとよんどかないとだめですね。

それにしてもSatisfy Anyってわかりやすい書き方ですね。どっちか満たせばOKって、覚えやすい。


2006年10月1日日曜日

フリップ・クリップも絶賛社員募集中です!

naoyaさんのバイト募集に便乗して、弊社の社員募集を出してみます。

弊社はFlipClipという動画共有サービスを運営している会社です。

http://www.flipclip.net/

動画共有サービスというとYouTubeがまっさきに思いつくと思いますが、
YouTubeをイメージしてもらって、そこからEvilさを引いて、かわいらしさをプラスするとFlipClipのイメージに近くなるかなーと思います。
ただ、YouTubeの真似をして、日本版YouTubeを作る気はありません。
ブログによって、誰でも自分の意見を世界に発信できるようになったように、
FlipClipを使うと、誰でも動画を簡単に世界に発信できるようになるといわれるようなサービスに
したいと思っています。そのために動画を公開する人が使いやすかったり、便利だったりする機能を
どんどん強化していきたいと考えています。

フリップ・クリップでは、上記のようなサービスの開発に興味があり、
以下のようなことができる方を募集しています。

- PerlでWebアプリを開発したことのあるエンジニア(Sledge経験者大歓迎。Linux,Apache,MySQLに詳しい方さらに大歓迎)
- ActionScriptがバリバリかけるFlashエンジニア
- XHTML+CSSなコーディングがバリバリできるデザイナー、コーダー
- GFSのような世界中の動画を管理できるような巨大ストレージシステムを構築したいと考えている方
- 巨大なトラフィックをさばけるようなサーバの構築・運用をしたいと考えている方

弊社の会社概要は以下のとおりです。
http://www.flipclip.net/company

現在、Perlエンジニアが僕を含めて2名、デザイン・コーディング・広報まで担当している、デザイナーが1名、営業・マーケティング担当が1名の4名がメインで活動しています。
そして、関連会社のアトムにFlash、デザイン、サーバ構築運用の部分を手伝ってもらっています。

社員はみんな、動画を使ってあんなことできないか、こんなことできないか、あーでもないこーでもないと議論しながら、面白いアイデアはどんどん実装していこうというスタンスで開発を進めています。

FlipClipはすべてPerlで書かれていて、フレームワークにSledgeを採用しています。
OSは、CentOS4.3で、WebサーバはApache+mod_perl, DBにMySQL, SMTPサーバにPostfixを採用しています。
動画の変換部分もffmpegやQuickTimeなど様々なソフトウェアを組み合わせて自前で構築しています。
ソースコードの管理にはSubversionを使っています。

簡単に弊社を紹介させていただきましたが、ちょっとでも興味をもたれた方は、
horiyasu at gmail.com までご連絡ください。


via: naoyaのはてなダイアリー - バイト募集してます。

2006年9月30日土曜日

MT3.3でBerkeley DBからPostgreSQLへの移行する際の注意点

MTのエントリが多くなりすぎて、再構築ができなくなったと友達から連絡をもらい、確認したところ、DBにBerkeleyDBを使っていたので、それを改善すべくDBをBerkeleyDBからPostgreSQLに移行してみました。
その際、MTのマニュアルにしたがってやってみたところ、そのままではうまくいかなかったので、行った作業をメモ的に残しておきます。

* PostgreSQLの設定

まず、マニュアルをみてPostgreSQLの設定をしました。

基本マニュアルどおりに進めて、mt-config.cgiの設定を変更し、
データベースの作成をおこないました。
DBの文字コードはUTF-8だったので、DB作成の際、文字コードを指定しました。
>

createdb -E UNICODE --owner xxxxx
<

* 移行スクリプトを実行

次にマニュアルどおりMT付属の移行スクリプトを実行したところ、DBの文字コードと違う文字コードのデータを入れようとした旨のエラーメッセージが出て、移行スクリプトを完了できませんでした。
文字コードの違うデータというのは、トラックバックPingのデータで、トラックバック元のブログの文字コードがUTF-8ではないものがエラーではじかれているようでした。
これは、文字コードが違うPingを探し出して削除するのは面倒くさかったので、移行スクリプトのほうを修正して、インサートに失敗しても処理を続行するようにして、回避しました。
修正点は以下のとおり。saveに失敗してもdieしないようにしただけです。MTの方で、DBへのアクセスが高度に抽象化されているので、修正は非常に簡単です。
>

--- ./mt-db2sql.cgi.org Sat Sep 30 00:54:15 2006
+++ ./mt-db2sql.cgi Sat Sep 9 20:09:45 2006
@@ -141,8 +141,8 @@
$obj->allow_comments(0)
if defined $obj->allow_comments && $obj->allow_comments eq '';
}
- $obj->save
- or die $obj->errstr;
+ $obj->save;
+ # or die $obj->errstr;
}

# fix up the category parents
<

この修正版のスクリプトを再度実行したところ、今度は問題なく、データの移行が完了しました。

* シーケンスの作成、更新

これで万事OKかと思ったんですが、この状態でエントリを作成しようとすると、以下のようなエラーがでて、作成できなくなってしまいました。

>

Saving entry failed: ERROR: ExecInsert: Fail to add null value in not null attribute entry_id
<

このエラーでググッてみると、どうやらDB内にシーケンスがないためのようです。
先ほどの移行用スクリプトだと、テーブルは作成されてもシーケンスは作成してくれないみたいです。
そうとわかれば、後はシーケンスを手動で作成するだけです。
以下のSQLを実行し、シーケンスを作成し、格納されている値を現在の最大値にセットしたところ、問題なく動くようになりました。

>

CREATE SEQUENCE mt_author_id;
CREATE SEQUENCE mt_blog_id;
CREATE SEQUENCE mt_category_id;
CREATE SEQUENCE mt_comment_id;
CREATE SEQUENCE mt_config_id;
CREATE SEQUENCE mt_entry_id;
CREATE SEQUENCE mt_fileinfo_id;
CREATE SEQUENCE mt_ipbanlist_id;
CREATE SEQUENCE mt_log_id;
CREATE SEQUENCE mt_notification_id;
CREATE SEQUENCE mt_objecttag_id;
CREATE SEQUENCE mt_permission_id;
CREATE SEQUENCE mt_placement_id;
CREATE SEQUENCE mt_plugindata_id;
CREATE SEQUENCE mt_tag_id;
CREATE SEQUENCE mt_tbping_id;
CREATE SEQUENCE mt_template_id;
CREATE SEQUENCE mt_templatemap_id;
CREATE SEQUENCE mt_trackback_id;

select setval('mt_author_id',(select max(author_id) from mt_author));
select setval('mt_blog_id',(select max(blog_id) from mt_blog));
select setval('mt_category_id',(select max(category_id) from mt_category));
select setval('mt_comment_id',(select max(comment_id) from mt_comment));
select setval('mt_config_id',(select max(config_id) from mt_config));
select setval('mt_entry_id',(select max(entry_id) from mt_entry));
select setval('mt_fileinfo_id',(select max(fileinfo_id) from mt_fileinfo));
select setval('mt_ipbanlist_id',(select max(ipbanlist_id) from mt_ipbanlist));
select setval('mt_log_id',(select max(log_id) from mt_log));
select setval('mt_notification_id',(select max(notification_id) from mt_notification));
select setval('mt_objecttag_id',(select max(objecttag_id) from mt_objecttag));
select setval('mt_permission_id',(select max(permission_id) from mt_permission));
select setval('mt_placement_id',(select max(placement_id) from mt_placement));
select setval('mt_plugindata_id',(select max(plugindata_id) from mt_plugindata));
select setval('mt_tag_id',(select max(tag_id) from mt_tag));
select setval('mt_tbping_id',(select max(tbping_id) from mt_tbping));
select setval('mt_template_id',(select max(template_id) from mt_template));
select setval('mt_templatemap_id',(select max(templatemap_id) from mt_templatemap));
select setval('mt_trackback_id',(select max(trackback_id) from mt_trackback));
<

以上BerkeleyDBからPostgreSQLへの移行の際の注意点をまとめると、

- 文字コードの問題でエラーがでるため、移行スクリプトを修正して、エラーを無視するようにする。
- シーケンスは作成されないので手動で作成する。

の2つを注意しないといけないようです。

2006年7月31日月曜日

コネクションプーリング都市伝説は正しそう

またちょっと古いねたですが、WEB+DB PRESS vol.33でnipotanさんが書いてたコネクションプーリング都市伝説を読んだ時、ほんとのところどっちが速いのかってのをabでベンチマークをとってみました。

ベンチマークとったときにエントリを書こうと思ってたんですが、モチベーションがあがらず今になっちゃいました。。

以下がベンチマークの結果です。

プーリングを有効にした場合


>

Server Hostname: rakuda.localhost
Server Port: 80

Document Path: /
Document Length: 12323 bytes

Concurrency Level: 20
Time taken for tests: 107.840 seconds
Complete requests: 1000
Failed requests: 0
Broken pipe errors: 0
Total transferred: 12579000 bytes
HTML transferred: 12323000 bytes
Requests per second: 9.27 [#/sec] (mean)
Time per request: 2156.80 [ms] (mean)
Time per request: 107.84 [ms] (mean, across all concurrent requests)
Transfer rate: 116.65 [Kbytes/sec] received

Connnection Times (ms)
min mean[+/-sd] median max
Connect: 0 0 0.0 0 1
Processing: 640 2140 486.9 1999 4674
Waiting: 639 2139 486.9 1999 4673
Total: 640 2140 486.9 1999 4674
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% 1999
66% 2066
75% 2141
80% 2267
90% 2650
95% 3388
98% 3774
99% 3941
100% 4674 (last request)
<

コネクションプーリングを無効にした場合


>

Server Hostname: rakuda.localhost
Server Port: 80

Document Path: /
Document Length: 12323 bytes

Concurrency Level: 20
Time taken for tests: 104.939 seconds
Complete requests: 1000
Failed requests: 0
Broken pipe errors: 0
Total transferred: 12579000 bytes
HTML transferred: 12323000 bytes
Requests per second: 9.53 [#/sec] (mean)
Time per request: 2098.78 [ms] (mean)
Time per request: 104.94 [ms] (mean, across all concurrent requests)
Transfer rate: 119.87 [Kbytes/sec] received

Connnection Times (ms)
min mean[+/-sd] median max
Connect: 0 0 0.1 0 3
Processing: 353 2081 502.0 1969 5219
Waiting: 351 2081 502.0 1969 5219
Total: 353 2081 502.0 1969 5219

Percentage of the requests served within a certain time (ms)
50% 1969
66% 2033
75% 2117
80% 2192
90% 2503
95% 3011
98% 4032
99% 4571
100% 5219 (last request)
<

これ以外にもいくつかパスを替えてベンチマークをとったところ、いずれも若干ですがプーリングしないほうが早かったので、現在はプーリングしないほうで運用しています。

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年6月25日日曜日

タグを頻繁に切るようなプロジェクトでのsvkの使い方。これでいいのか?

ここ最近はsvkを使ってバージョン管理をしているんですが、svk help introで見ることができるイントロで紹介されているように 、

>

# リポジトリをミラー
svk mirror svn://svn.example.com/project_x //mirror/project_x

# リポジトリを同期
svk sync //mirror/project_x

# ローカルリポジトリを作成
svk copy //mirror/project_x //project_x

<

として、同期したproject_xのリポジトリをそのままローカルリポジトリにするという方法でしばらく使っていました。最初はこれで問題ないように思えたんですが、元のリポジトリでブランチやタグを切った後、svk pull //project_xして、リポジトリの同期をとると、ブランチやタグがファイルの実体としてコピーされているようだということに気づきました。

僕の会社でのsubversionの使いかたは、trunkで開発を進めて、リリースの準備ができたら、ブランチを切り(例えば branches/RB-2.3)、実際にリリースする時にはタグを切る(例えば tags/REL-2.3.0)という形をとっているので、結構タグを切ることが多いです。

これだとタグを切るたびにsvk pullするとそのプロジェクト分のファイル容量が消費されてしまい、すごくディスクを無駄にしているような気がしていました。

そこで考えたのが以下の方法。

>

# リポジトリをミラー。ここは同じ
svk mirror svn://svn.example.com/project_x //mirror/project_x

# リポジトリを同期。ここも同じ
svk sync //mirror/project_x

# trunk,brancheのローカルリポジトリをそれぞれ作成。ここを変更
svk copy //mirror/project_x/trunk //project_x-trunk
svk copy //mirror/project_x/branches/RB-2.3 //project_x-rb2.3

<

ローカルリポジトリを作成する際にリポジトリそのままではなく、トランクとブランチをそれぞれローカルレポジトリとして作成してみました。
今はこの方法で開発をはじめてますが、pull, push,なんかも問題なくできますし、トランクの作業コピー上で、svk smerge //project_x-rb2.3として、リリースブランチの修正をトランクにマージすることもできているので、問題はなさそうです。

もうひとつ、考えられる方法として、mirrorする際に、trunkをミラーするという方法もできそうですが、なんかこの方法だと管理が複雑になりそうだったので上記の方法をとりました。

svkは結構柔軟にいろいろできるっぽいので、他のひとがどういう風に使っているのかちょっとしりたいなーと思いました。

2006年6月13日火曜日

MacOSX に IMAPサーバ構築 (Dovecot)

会社のネットワークセキュリティが厳しくて、ローカルのPostfixからメールを外部に送信できないので、メールのやりとりもローカルで完結するよう、ローカルにIMAPサーバをたてることにしました。
以前にIMAPサーバをたてた時はcourier-imapを使ったんですが、ググってみると、Dovecotというのがよさげだったので、そちらをいれてみることにしました。

構築はMac mini/3.メールサーバを参考に行ないました。というかほとんどここに書いてあるとおり実行しただけです。

インストールはDarwinPortsにパッケージがあるのでそれをいれました。簡単!
>

sudo port install dovecot
<

インストールが完了すると自動起動するには以下のコマンドを実行しろというようなメッセージがでてきたので実行しました。

>

sudo launchctl load -w /Library/LaunchDaemons/org.darwinports.Dovecot.plist
<

これをやると /etc/hostconfigにMAILSERVER=-YES-が追加されたりするみたいです。

参考にしたサイトだとここでDovecotユーザを作成すると書いてありますが、今回試したかぎりでは、
この時点ですでにDovecotユーザが作成されていました。

次にPAM認証の設定をしました。 sshdの設定を流用しました。

>

sudo cp /etc/pam.d/sshd /etc/pam.d/dovecot
<

/opt/local/var/runが必要ということで作成しました。

>

sudo mkdir -p /opt/local/var/run
<

サンプルをコピーして設定ファイルを作成しました。

>

sudo cp /opt/local/etc/dovecot/dovecot-example.conf /opt/local/etc/dovecot/dovecot.conf
<

設定ファイルは以下のように修正しました。
利用プロトコルをimapだけにして、SSLは無効にしました。

>

--- /opt/local/etc/dovecot/dovecot-example.conf 2006-06-12 10:43:19.000000000 +0900
+++ /opt/local/etc/dovecot/dovecot.conf 2006-06-12 10:59:57.000000000 +0900
@@ -18,6 +18,7 @@
# Protocols we want to be serving:
# imap imaps pop3 pop3s
#protocols = imap imaps
+protocols = imap

# IP or host address where to listen in for connections. It's not currently
# possible to specify multiple addresses. "*" listens in all IPv4 interfaces.
@@ -44,6 +45,7 @@

# Disable SSL/TLS support.
#ssl_disable = no
+ssl_disable = yes

# PEM encoded X.509 SSL/TLS certificate and private key. They're opened before
# dropping root privileges, so keep the key file unreadable by anyone but
<

ここまできたら、OSを再起動すると、起動時にDovecotがたちあがるようになりました。

手動でdovecot 起動、停止、再起動するには以下のコマンドをたたけばいいようです。

>

sudo SystemStarter start Dovecot
sudo SystemStarter stop Dovecot
sudo SystemStarter restart Dovecot
<

MacBookでさくさく開発できるようになりました

どうしても欲しくて、いてもたってもいられず、会社近くのさくらやでMacBookを購入してから1週間くらいたちましたが、環境構築もだいたいおちついて、快適に開発できるようになってきました。

Windows+VMWareな開発環境と比べるとすべてがさくさく動いて涙がでそうです。
以前は svk pull と打ったあとにトイレいって帰ってきてもまだ終了してないってことがざらにあったんですが、
今はすぐに終了するのでトイレに行く暇もありません。

はじめてのMacだったんですがとなりの席のyoshidaさんにいろいろ教えてもらえたので、スムースに移行できたとおもいます。yoshidaさんがいなかったらアプリのインストールの仕方もわかりませんでしたw ありがとうございます。そしてこれからも色々教えてください。


2006年5月23日火曜日

JSON::Syckで改行を含むデータをダンプすると改行の後にスペースが2個入る

Ajaxる時のサーバとのデータ交換フォーマットとして、JSONを使う時、perlでサーバ側を実装する際にはJSONとかJSON::Syckというモジュールを使うとperlのデータ構造をJSONフォーマットに変換してくれるので便利です。

昨日これを使って、サーバからデータを取得し、textareaに取得したデータを入れるということをしたところ、改行を含んだテキストだと、改行の後にスペースが2個入ってしまうという現象に遭遇しました。
要はこうなってほしいところが、

><

こうなってしまうんです。

><

調べてみると、クライアントで受け取ったJSONデータをevalした段階ですでにスペースを含んでいたので、サーバ側でJSONデータを作成するところに問題がありそうだということがわかりました。
そこで以下のようなスクリプトを書いて、JSON,JSON::SyckがどんなJSONデータを吐き出すか見てみました。
>

#!/usr/bin/env perl
use strict;
use JSON::Syck;
use JSON;

my $data = {
key => "foo\nbar\nbaz\n"
};

print "# Dumped by JSON::Syck $JSON::Syck::VERSION:\n";
print JSON::Syck::Dump($data), "\n";

print "# Dumped by JSON $JSON::VERSION:\n";
print objToJson($data), "\n";

<

結果は以下のとおり。

>

# Dumped by JSON::Syck 0.12:
{"key":"foo\n\
bar\n\
baz\n"}
# Dumped by JSON 1.05:
{"key":"foo\nbar\nbaz\n"}

<

どうやらJSON::Syckを使ってダンプしたデータは、整形のため、改行の後に、スペースが2個入っているようです。それでこれをJavaScriptでevalするとこのスペースもデータとして含まれてしまうため、上記のような現象が起きたみたいです。
そもそも改行後にスペースが入ってしまうのが問題なのか、スペースが入るのは問題なくて、クライアント側のJavaScriptでのパースに問題があるのかわかってませんが、試した限りだと、IEとFirefoxでスペースつきのデータの扱い方が異なるようで、挙動が変わってしまい、困りました。

とりあえず、サーバ側で改行コードを[BR]とかに変換しておいて、クライアント側でそれを改行コードとか、<br />に変換するという風にして対処しましたが、ちょっと付け焼刃的ですかねー。

2006年5月21日日曜日

OOなJavaScriptの勉強がてら、ボタンの2度押し禁止機能を簡単に実現するライブラリを作ってみました

ちょっと時代に乗り遅れている気もしますが、最近prototype.jsをつかったOOなJavaScriptを書くのにはまっています。

今日はbindとbindAsEbentListenerの勉強がてら、これまたかなり昔話題になった「2度押し禁止ボタン」を実現するコードを書いてみました。

input.js

使い方はこんな感じです。

>

 // prototype.jsとinput.jsをロードします。
 <script type="text/javascript" src="/path/to/prototype.js"></script>
 <script type="text/javascript" src="/path/to/input.js"></script>
 <script type="text/javascript"><!--
 // windoのロード時に2度押し禁止機能を有効にします。
 Event.observe(window, 'load', function(){
  // ボタンのIDか要素のオブジェクトを渡せばクリックすると押せなくなります。
  new Input.DisableAfterClick('myButton');
 }, false);
  --></script>
<

ボタンのテキストを変えたい場合は2番目の引数に代替テキストを渡します。
画像の場合は代替画像のURLを渡します。
>

new Input.DisableAfterClick('mySubmitButton2', '送信中です');
new Input.DisableAfterClick('myImageButton2', '/path/to/image2.gif');
<

一定時間経過後にボタンを再度有効にしたい場合は、3番目の引数に有効にするまでの時間をミリ秒単位で渡します。
>

new Input.DisableAfterClick('myButton', '1秒後にまた有効になります', 1000);
<

サンプルページにサンプルがあります。

また、input.jsの中身はこんな感じです。画像ボタンの扱いはもっといい方法があるのかなーとも思うんですが、おかしなところがあったら突っ込んでください。

>

var Input = new Object();
Input.DisableAfterClick = Class.create();
Input.DisableAfterClick.prototype = {
initialize : function(input, alt, timer){
this.input = $(input);
this.alt = alt;
this.timer = timer;
this.org = this.input.type == 'image'
? this.input.src
this.stopEvent = this._stopEvent.bindAsEventListener(this);

Event.observe(this.input, 'click', this.disable.bind(this));
},
disable : function() {
this.input.blur();
if (this.input.type == 'image'){
if ( this.alt )
this.input.src = this.alt;
Event.observe(this.input, 'click', this.stopEvent);
} else {
if ( this.alt )
this.input.value = this.alt;
this.input.disabled = true;
}
if (this.timer)
setTimeout(this.enable.bind(this), this.timer);
},
enable : function() {
if (this.input.type == 'image'){
if ( this.alt )
this.input.src = this.org;
Event.stopObserving(this.input, 'click', this.stopEvent);
} else {
if ( this.alt )
this.input.value = this.org;
this.input.disabled = '';
}
},
_stopEvent : function (evt) {
evt.stop();
return false;
}
};

<

2006年5月18日木曜日

CentOS4.3なローカル開発環境がでけた

もう2週間も前になりますが、なかなか時間がなくて作れなかったローカルな開発環境をゴールデンウィークを利用して構築しました。Vmware Player上でCentOS4.3を動かしてます。
CentOSのデフォルトのロケールがja_JP.UTF-8なので、UTF-8な環境の構築も行いました。
さらに、今までbashを使ってたんですが、zshに変更しました。

それにしても環境構築まで長かった。。

最初、colinuxにCentOS4.3を入れようと思って「coLinux 用 インストーラ」を使って、はじめたんですが、OSのインストールに時間がかかるかかる。。最初はスムースに行くんですが、ちょっとすると、真っ青な画面になって、まったく動かなくなりました。固まっちゃったのかなと、何度かインストールをやり直してみましたが、やっぱり、青い画面でとまるので、そのまま放置して寝てみることに。
8時間くらい寝て、起きた後、画面を見てみると、ちょっとインストールが進んでいました。とまっているわけではないのだなと、さらに放置しておいたのですが、なかなか終わる気配がありません。。結局インストール完了するまでに、2日くらいかかりました。。ここまで我慢してがんばってきたんですが、起動してみるとネットワークカードを認識しない。。いろいろ試してみたんですが、うまくいかなかったので、Vmwareでも試してみるかとなりました。

Vmwareへのインストールはすんなりいって、今までの苦労はナンだったんだという感じですが現在が快適なのでよしとします。



2006年4月29日土曜日

Apache::RequestからPOSTデータがとれない!?

うおーーーーー、Apache::RequestからPOSTデータがとれない!!なぜだーと小一時間悩んでいたら、こんなコード書いてました。

>

sub handler {
my $r = Apache::Request->new(shift)
my @keys = qw(foo bar baz);
for my $key ( @keys) {
my $val = $r->param($_);
warn "$val\n";
}
}
<

原因に気づいて愕然としました。。。

2006年4月20日木曜日

livedoor Reader いい!

先日ベータ版がリリースされたlivedoor Readerを使ってみました。

恐ろしく快適です。
記事をバックグラウンドで先読みしてくれていて、動作が軽快です。
さらに、キーボードショートカットが超便利。
s,a,j,k,spaceを使えば記事を次々読んでいけるし、気になる記事はpでピンをうっておいて、oで別ウィンドウで表示できる機能は恐ろしく便利です。
まら、oで別ウィンドウを開く際、ブラウザのポップアップブロックで別ウィンドウが開けないと、その旨をメッセージ欄で教えてくれたり、IMEがオンになっている状態でショートカットキーを使おうとすると、ショートカットを使うにはIMEをOFFにしてくださいと親切に教えてくれます。すごく細かいところまで作りこまれているなーと感動しました。

なんてことをやっていたら、午前中が終わってしまった。。



LWPのバージョンあげたらファイルアップロードが遅くなった

ここ最近AtomPPを使ったサービスを作っているんですが、そのためにXML::Atomをインストールしたら、LWPを使って10MBくらいの大きいファイルをアップロードすると異常に時間がかかるようになってしまいました。
アップロード時のサーバの様子を見ているとメモリとCPUをやたらと消費しているようです。

LWPのドキュメントを読んでいるとHTTP::Request::Commonのドキュメントに$DYNAMIC_FILE_UPLOADをTRUEにセットすれば大きなファイルをメモリをあまり使わずにアップロードできるよと書いてあったので、これをTRUEにしたところ、劇的にアップロードの時間が短縮されました。

めでたしめでたし。





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年4月13日木曜日

WSSE認証で使用するパスワードにハッシュ化した文字列を使うのはどうか?

ここ最近APIの認証について、考えてます。

AtomAPIの認証として事実上標準的に使われているWSSE認証ですが、APIを提供しているサービスにログインするためのパスワードとAPIの認証用パスワードを共通にした場合、APIを利用するクライアント側に生のパスワードを持たせる必要があるので、APIを利用したサービスでパスワードがもれた場合、そのパスワードを使って、提供側のサービスにもログインできてしまうということになりえてしまいます。

かといってサービスログイン用のパスワードと、API認証用のパスワードを別々に設定するようにすると、ユーザはパスワードを二つ覚えておく必要があるので、ちょっと、めんどくさーとか、わすれたーとなってしまいそうです。

そこで、パスワードは1つで済んで、APIを利用する側のアプリでパスワードがもれたりした場合も、本サービスではそのパスワードを使ってログインできないようにする方法として、APIで使用するパスワードは生のパスワードをハッシュ化したものを使うというのはどうだろうかと考えています。

こうしておけば、APIを利用するアプリ側も生パスワードを持たなくて済むので、なんとなく気持ちが楽になるのかなと。結局ハッシュ化した文字列がパスワードのかわりになるだけなので、これがもれるとAPIの認証はできてしまうんですが、APIで触れる範囲はログインしてできる範囲より小さいと考えられるので、被害は小さくなるのかなと思いました。

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月21日火曜日

Template::Plugin::Encode

内部エンコーディングをFlagged UTF-8にしたところ、テンプレート内の日本語を含んだURLにuriフィルタを適用すると、空になるという現象が発生してしまいました。
uriフィルタをハックして、、とも思いましたが、テンプレート内でEncodeモジュールが使えたら便利かなと思い、プラグインを書いてみました。


Template::Plugin::Encode-0.01


使い方はこんな感じ。メソッドの実装は、Template::Plugin::Jcodeを参考に、virtual methodとして扱えるようにしてみました。

>

[% USE Encode %]

# encode
<a href="/foo/bar?jword=[% ja_str.encode.encode('utf-8')|html|uri %]">click here</a>

# decode
[% foo.encode.decode('euc-jp') %]

# from_to
[% num = bar.encode.from_to('sjis','euc-jp') %]
[% bar %]

<

こういうプラグインはすでにCPANにあるだろうと思ったんですが、ミストを感じなかったので、作成してみました。

2006年3月15日水曜日

_blankを使わないで別ウィンドウを開くにはrel="external"を使うのが美しいと思う。

はてなブックマークをみていたら、気になるエントリーを発見。


[戯] target="_blank" を使わないで新しいウィンドウでリンクを開く方法


target="_blank"という書き方がXHTML 1.1 や XHTML Basicに準拠していないので、これらに準拠するようにしつつ、別ウィンドウで開くにはどうすればよいかという話です。

別ウィンドウで開くにはJavaScriptを使えってのが推奨される方法なんですが、onclickを使って定義するのはめんどうということで、この記事では、aタグにclass="popup"という属性を与えておけば、JavaScriptで別ウィンドウを開くということをしています。

この件については友人のHTML、CSSマスターなkawachi君と話したことがあって、そのときは、下のエントリで紹介されているrel="external"という方法を使うのがいいんじゃないかという結論に達しました。


Opening a link in a new window - the valid way


僕は上のエントリを参考に以下のようなjsファイルを用意して使っています。


>

//external.js
function externalLinks() {
if (!document.getElementsByTagName) return;
var anchors = document.getElementsByTagName("a");
for (var i=0; i<anchors.length; i++) {
var anchor = anchors[i];
if (anchor.getAttribute("href") &&
anchor.getAttribute("rel") == "external")
anchor.target = "_blank";
}
}

Event.observe(window,'load', externalLinks, false);

<


使い方は簡単でこのjsファイルをインクルードするだけです。


>


<script type="text/javascript" src="/path/to/prototype.js"></script<
<script type="text/javascript" src="/path/to/external.js"></script>

<


load時にこのファンクションを実行するためにprototype.jsのEvent.observeを使っているため、
prototype.jsもインクルードしています。ここはwindow.onload=externalLinks;に書き換えれば、
prototype.jsは必要ありません。
このファイルをインクルードすると、rel="external"と指定したaタグをクリックすると別ウィンドウで開くようになります。


>


<a rel="external" href="http://www.google.com/">別ウィンドウで開きます。</a>

<


rel="external"というこのリンク先は外部のサイトだよという関連性を示す記述に対して、JavaScriptで別ウィンドウで開くんだよと定義してあげるというアプローチが美しいなと思っています。




FormValidator::Simple::Extension - アップロードファイルのバリデーションなど

前回のエントリでFormValidator::Simpleでアップロードされたファイルのバリデーションができるように拡張したモジュールを書いているといいましたが、それっぽいものができました。

FormValidator-Simpe-Extension-0.01.tar.gz

SimpleのExtensionてどっちやねんという感じですが、アップロードファイルのバリデーション以外に、メッセージをFlaggedなUTF-8にデコードした形でgetできるような機能もつけてみたので、名前をなんにするか悩んだ挙句、とりあえずExtensionにしておきました。使い方はこんな感じです。

>


use FormValidator::Simple::Extension;

## アップロードファイルのバリデーション

FormValiator::Simple::Extension->check( $q, [
thumbnail => [
'UPLOAD_NOT_BLANK', # ファイルがアップされたかどうか
[UPLOAD_FILESIZE,0, 1024], # ファイルサイズは1kBまで
[UPLOAD_FILENAME_REGEX, 'jpe?g|gif|png$'] #画像っぽいファイル名ならOK
],
]);

## メッセージをFlagged UTF-8でうけとる

FormValidator::Simple::Extension->set_messages('messages.yml');
# どんな文字コードからデコードするか文字コードをセット
FormValidator::Simple::Extension->set_message_decode_parms('utf-8');
my $result = FormValidator::Simple::Extension->check($q, $prof);
# メッセージはデコードされる
my $messages = $result->messages;

<

これらの拡張が本家のFormValidator::Simpleに取り込まれたらうれしいのですが、アップロードファイルを取得する方法がApache::Requestのuploadメソッドしか想定してないので、ちょっとマイナー過ぎるきもします。。
CGI.pmのuploadメソッドも考慮したものにすれば、取り込んでもらえるかなー。



2006年3月14日火曜日

MIME::Lite::TT::Japanese 0.08

TokuLog!な人にSubjectだけじゃなくて、FromもMIMEエンコードしないと、携帯で返信できないyo!というメールをパッチとともにいただいたので、早速適用して、CPANにアップロードしておきました。


MIME::Lite::TT::Japanese 0.08


どうもありがとうございました。

ところで、SledgeのValidatorのほうですが、FormValidator::Simpleだとアップロードされたファイルについてのバリデーションができないことに気づいて、ちょっとなやんでます。とりあえず、アップロードできるように拡張したモジュールを書いてみているんですが、いまいちしっくりきません。。もうちょい時間がかかりそう。

2006年3月11日土曜日

FormValidator::Simple::ProfileManager::YAML

SledgeのValidatorを作るにあたって、FormValidator::Simple用のプロファイルをYAMLで定義するためのモジュールを書いてみました。

FormValidator::Simple::ProfileManager::YAML

まずは以下のようなYAMLファイルを用意します。

>

# sample yaml profile
group1 :
- name
- [ [NOT_BLANK] ]
- email
- [ [NOT_BLANK], [EMAIL_LOOSE] ]
- tel
- [ [NOT_BLANK], [NUMBER_PHONE_JP] ]
- content
- [ [NOT_BLANK] ]

group2 :
subgroup1 :
- userid
- [ [NOT_BLANK]]
- password
- [ [NOT_BLANK]]
- name
- [ [NOT_BLANK] ]
- email
- [ [NOT_BLANK], [EMAIL_LOOSE] ]
subgroup2 :
- tel
- [ [NOT_BLANK], [NUMBER_PHONE_JP] ]
- { zip : [zip1, zip2] }
- [ [ZIP_JP] ]
- address
- [ [NOT_BLANK] ]
<

group1とかsubgroup1というのはプロファイルをグルーピングするのに使います。

使い方は簡単です。まずオブジェクトを作成します。

>

use FormValidator::Simple::ProfileManager::YAML;
my $manager = FormValidator::Simple::ProfileManager::YAML->new('/path/to/profile.yml');
<

デフォルトではYAMLをロードするのにYAMLモジュールを使いますが以下のようにloaderをオプションとして渡すと、YAML::Syckを使うこともできます。

>

my $manager = FormValidator::Simple::ProfileManager::YAML->new(
'/path/to/profile.yml',
{
loader => 'YAML::Syck',
}
);
<

プロファイルを取得するには、get_profileメソッドを使います。パラメータとして、グループ名を渡せば、そのグループに属するプロファイルを取得することができます。
取得したプロファイルはそのままFormValidato::Simpleに渡すせます。

>

# group1のプロファイルを取得
my $profile1 = $manager->get_profile('group1');

# subgroup2のプロファイルを取得
my $profile2 = $manager->get_profile( 'group2', 'subgroup2' );

#取得したgroup1のプロファイルをそのままわたす。
my $result = FormValidator::Simple->check($q , $profile1);

<

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ヘッダを比較するようになったりしてます。

Template::Plugin::AutoLink

テキスト中のURLやメールアドレスを自動でハイパーリンクに置換してくれるTTフィルターを書いてみました。

Template::Plugin::AutoLink 0.03

こんな感じで使います。

>

# テンプレート内

[% use AutoLink %]

[% FILTER auto_link target='_blank' %]

hori-uchi.com
http://hori-uchi.com/

[% END %]

# アウトプットはこんな感じ
Search here
http://hori-uchi.com/

<

こういう機能をもったプラグインて絶対すでにあるだろうと思っていたんですが、CPANで探しても見つからなかったので、書いたんですが、実はあったりするのかな。

追記
miyagawaさんが作ったTemplate::Plugin::Clickableというまったく同じことができるモジュールがすでにありました。やっぱないわけないですよね。。



2006年1月21日土曜日

映画「シムソンズ」完成披露試写会にいってきました。

月曜日のことになりますが、シムソンズムービーブログで協力させていただいた映画「シムソンズ」の完成披露試写会に行ってきました。
生加藤ローサはかわいかった!でも個人的には「妖怪大戦争」にも出ていた高橋真唯が一番でした。

下の動画はマスコミ用の撮影タイムに一緒に撮影したものです。


映画の内容も期待以上によかったです。
「ウォーターボーイズ」とか「スウィングガールズ」が好きな方はぜひ見てください。
感動することまちがいなしです。

http://www.sim-sons.com/