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つを注意しないといけないようです。