« 2007年6月 | メイン | 2007年8月 »

2007年7月31日

WEBアプリテストのチェック項目リスト
このエントリーをブックマークに追加 このエントリーをlivedoorクリップに追加

こんにちは!やまもと@テスト番長です。

TestingGeek
という耳障りの良い名前のサイトをご存知でしょうか?
総合的にテストの話を取り扱っており、それでいて読みやすいサイトです。

そこのTemplatesのコーナーにWeb Application Testing Checklist という便利そうなものがありましたので、日本語にしてみました。
ちょっとそのままだと物足りない感がありますが、テストポリシー作成の叩き台に使ってみるのも良さそうですね。
この手のリストを他にもご存知の方がいらっしゃれば、是非ご一報ください。

1. 機能テスト

1.1 リンク

1.1.1 記載された通りの先に遷移するか
1.1.2 どこからもリンクされないページは存在しないか
1.1.3 全ての外部リンク
1.1.4 参照しているサイトおよびメールアドレスはハイパーリンクになっているか?
1.1.5 廃止したページはユーザーにアクセスされないように、404レスポンスを返してHOMEページ(または検索ページ)へリダイレクトするようにしているか
1.1.6 全てのmailtoリンクは届く宛先になっているか確認する

1.2 フォーム

1.2.1 無効な入力を受容しないか
1.2.2 任意項目と必須項目
1.2.3 入力可能な文字列長
1.2.4 ラジオボタンの動作
1.2.5 ページを読み込み/再読み込みした時のデフォルト値(条件・状態は無効になっているべき)
1.2.6 コマンドボタンはハイパーリンクと続行ボタンに割り当てられているか?
1.2.6 コンボ/リストボックスの全ての中身は年代順に整頓されているか?
1.2.7 テーブルやフォームは全て表示されているか?適切なサイズに広げられているか?適切な位置のテキストがフォーカスされているか
1.2.8 必要なスクロールバーが表示されているか?

1.3 データの確認と検証

1.3.1 プライバシーポリシーが制定されており、読みやすい場所にあるか
1.3.2 無効なデータが与えられた時、システムがぎこちなくなる箇所はないか
1.3.3 サイト閲覧中にユーザーがCOOKIEを削除したらどうなるか
1.3.4 サイト訪問後にユーザーがCOOKIEを削除したらどうなるか


2. アプリケーションの固有の機能要件

2.1 データの調整

2.1.1 文字列が断ち切られないように、最大のフィールド長をチェックする
2.1.2 数字フィールドが負の数を受容するなら、データベースに格納することができるか?フィールドに負の数が入る合意が取れているか?
2.1.3 特定のデータがデータベースに保存されるなら、各値がデータベースに完全に保存されるのをチェックする。 すなわち、 文字列のトランケーションと数値の桁落ちに注意する。


2.2 日付フィールドのチェック

2.2.1 うるう年が正しく考慮されていて、誤り/誤算を引き起こさないことを確認する
2.2.2 2月28, 29, 30日が正しく考慮されていて、誤り/誤算を引き起こさないことを確認する
2.2.3 全てのサイトの著作権表示はヤフー相互ブランドサイトの更新を反映しているか

↑思いっきり直訳しましたが、どうやら「co-branded(相互ブランド)」とは
「ユーザーがある企業のサイトを訪れ、リンクを辿って提携企業のページへ進んだときに、そのリンク元の企業のロゴやブランドがそのままブラウザ上に残すことをいう。」らしいので、
フレームで異質なサイトを組み合わせて表示する際、コピーライトの日時がずれないように、ということのようです。


2.3 数字入力フィールドのチェック

2.3.1 最小値、最大値が正しく取り扱われることを確認する
2.3.2 空白が先頭にある数字フィールドは、エラーとして処理または報告されるか確認する
2.3.3 空白が末尾にある数字フィールドは、エラーとして処理または報告されるか確認する
2.3.4 プラス、マイナスの値が正しく処理されることを確認する
2.3.5 ゼロの割り算が起こらないことを確認する
2.3.6 すべての計算にゼロを含めてみる
2.3.7 範囲の上限値、下限値が正しく扱われることを確認する。
#原文には(Using BVA)とあるのですが、BVAって何でしょう?ビットバレー・アソシエーション?w


2.4 英数字入力フィールドのチェック

2.4.1 空白及び空白ではないデータを試す
2.4.2 最大値、最低値を入れる
2.4.3 無効な文字や記号を入れる
2.4.4 有効な文字を入れる
2.4.5 データの最初の文字に空白を入れる
2.4.6 データの最後の文字に空白を入れる


3. インタフェースとエラー処理

3.1 サーバインタフェース

3.1.1 ウェブサーバ・アプリケーションサーバ間、アプリケーションサーバ・データベースサーバー間で正しく交信できることを確認する。逆も同様に確認する。
3.1.2 サーバソフトウェア、ハードウェア、ネットワーク接続の互換性

3.2 外部のインタフェース

3.2.1 推奨するブラウザは全てテストしたか?
3.2.2 関係する外部のアプリケーションやサーバが使用できなくなった場合を考慮してテストしたか?


3.3 内部のインタフェース

3.3.1 サイトがプラグインを使用する場合、それなしでも動作するか?
3.3.2 リンクした文書は全てのプラットフォームで利用可能か?(マイクロソフトのワードファイルはソラリスで閲覧出来るか?)
3.3.3 ダウンロードが失敗した場合、失敗の処理が行われるか?
3.3.4 ユーザーはコピー&ペースト機能を使用出来るか?パスワード/暗証番号/クレジットカード番号フィールドでは許容されるか?
3.3.5 暗号化していないデータを送信するようになっていないか?


3.4 内部のインタフェース

3.4.1 システムが異常終了した場合、リブートと回復はスムーズに行われるか?
3.4.2 操作を途中で止めてサイトを去るなら、それは取り消されるか?
3.4.3 インターネットの接続が切れた場合、処理は取り消されるか?
3.4.4 ブラウザクラッシュの可能性を考慮しているか?
3.4.5 アプリケーションサーバとウェブサイト間でのネットワーク異常があった場合を考慮しているか?
3.4.6 あなたは(クッキー無効などに対して)知的なエラー処理を実装したか?


4. 互換性

4.1 ブラウザ

4.1.1 使用しているHTMLのバージョンは推奨するブラウザバージョンと適合しているか?

4.1.2 画像は対象ブラウザで正しく表示されているか?
4.1.3 フォントはいずれのブラウザでも表示可能か?
4.1.4 スクリプトはテスト対象のブラウザで正しく動作しているか?
4.1.5 アニメーションGIFはどのブラウザでも問題なく表示されるかテストしたか?

4.2 画面表示設定

4.2.1 様々な解像度(1024x768, 600x800, 640x480ピクセルなど)でチェックする(テキストとグラフィックの配置が乱れない、フォントの可読性など)
4.2.2 色数を変えてチェックする(256色、16ビット, 32ビット)

4.3 接続(表示)速度

4.3.1 サイトは閲覧者のブラウザで8秒以内に表示されるくらい軽くなっているか?

4.4 印刷

4.4.1 テキストと画像の並びが乱れないか
4.4.2 テキスト、フォアグランド、およびバックグラウンドの色は適切か
4.4.3 紙サイズに合う大きさになっているか
4.4.4 テーブルと枠線は表示されるか
4.4.5 テキストを分断せずに読みやすく印刷出来るか?

ext3の dir_indexを試す
このエントリーをブックマークに追加 このエントリーをlivedoorクリップに追加

こんにちは、佐藤です。kernel 2.6系のext3からdir_indexというものがサポートされたようなのでどのような状況でパフォーマンスが向上するか実験してみました。

環境は以下です。

uname -a; cat /etc/issue.net
Linux 2.6.18-8.1.3.el5 #1 SMP Mon Apr 30 19:55:44 EDT 2007 i686 i686 i386 GNU/Linux
CentOS release 5 (Final)

まず/dev/sdb1にext3領域をを作ってみます。

sudo mkfs.ext3 -j /dev/sdb1
sudo tune2fs -l /dev/sdb1  # 詳細表示
....
Filesystem features:      has_journal resize_inode dir_index filetype needs_recovery sparse_super large_file
....

dir_indexという項目があるので、このバージョンでのmkfs.ext3はデフォルトでdir_indexが適応されるようです。

比較のためにdir_indexが無い領域も作成します。

sudo mkfs.ext3 -j -O none /dev/sdb2
sudo tune2fs -l /dev/sdb2
...
Filesystem features:      has_journal needs_recovery
...

適当な場所にマウントします

 sudo mount /dev/sda /home/unoh/sdb1
 sudo mount /dev/sda /home/unoh/sdb2

テストのため計測プログラムを書きました。

g++ -o disktest disktest.cpp
./disktest 
-c N: create files
-a N: random access files
-d N: delete files
オプション -c で N個のファイルを作成します
オプション -a でN個のファイルの中からランダムに 2000個アクセスします
オプション -d でN個のファイルを削除します
操作はすべてカレントディレクトリに対して行います。

結果に影響ができないようにテストの度に以下のコマンドで ページ,dentry,i-nodeキャッシュを解放します。

sudo sysctl -w vm.drop_caches=3; sudo sysctl -w vm.drop_caches=0;

結果

ファイル作成

ファイル数 15000 30000 60000 120000
dir_index無し 19364 msec 43919 msec 120990 msec 337845 msec
dir_index有り 2443 msec 7006 msec 15320 msec 31793 msec

ファイルアクセス

ファイル数 15000 30000 60000 120000
dir_index無し 1219 msec 4746 msec 18579 msec 27791 msec
dir_index有り 43 msec 77 msec 1683 msec 5467 msec

ということで、ファイルの作成についてはdir_indexが無い場合ファイル数が増えるにつれて指数的にアクセスが遅くなっているのがわかります。

dir_indexがある場合は、ファイル数が増えても時間は比例的に増えます。

ファイルのアクセスについても同じような結果になっています。

これを見る限り dir_indexは非常に有効そうです。

ちなみに、bonnie++ というソフトウェアでも同じようなテストができるようです。

2007年7月30日

dir_walk_recursive?
このエントリーをブックマークに追加 このエントリーをlivedoorクリップに追加

こんにちは。先々週、入社したbokkoです。

普段はbokkoとかcubicdaiyaとか名乗っています。近々、「就職祝い」として、フルボッコの会が開かれるそうなのですが、大学時代の先輩がたくさん来るそうなので、期待と不安が半々といったところです。

それはそれとして、アプリケーション内で、指定したディレクトリ以下のファイル全部に対して、何らかの処理を行いたい時がたまにあると思います。こういう場合、ディレクトリかファイルかで処理を分け、ループと再帰でぶん回したりしますが、去年、これを何回も書く機会があって面倒になったことがあったので、ディレクトリの規模があまり大きくない場合は、

function divGeneric($dir, $f, $arg){
    if(!is_dir($dir)) return false;
    if($hd = opendir($dir)){
        while(false !== $file = readdir($hd)){
            if($file != "." && $file != ".."){
                $f($dir,$file,$arg,$f);
            }
        }
        closedir($hd);
    }
    return $arg;
}

↑のような関数を用意して、実行したい処理を引数で渡すだけで済むようにしています。例えば、あるディレクトリ以下の全ファイルを1つの配列に入れるような場合は、以下のように書きます。


$f = create_function('$dir,$file,&$array,$f', 
                     'if(is_dir($dir."/".$file)){
                          $array = divGeneric($dir."/".$file, $f, $array);
                      }
                      else{
                          $array[] = $dir."/".$file;
                      }
                     ');
$ret = divGeneric($root_dir, $f, array());

こんな感じで、array_walk_recursiveならぬ, dir_walk_recursiveっぽいことができます。関数の名前が全然違いますが、気にしないでください。ただ、このやり方はうまくいかなかったときのデバッグが面倒なので、create_function内に書く処理はあんまり長くならないようにした方がいいかもしれません。

■ほかの言語の場合

RubyとPythonの例は前に私が書いたのがこちらにあります。ただ、Pythonでは、lambdaに単一の式しか渡せない(それがいいか悪いかどうかはまた別のお話)ので、少し強引に書いてます。


■余談

PHP5ではscandirがあるので、もう少しスマートに書けます。

追記(7/30)

コードにちょっと間違いがあったので、修正しました。
手元のコードをよく見たらdieじゃなくて、return false;でした。

2007年7月27日

ベンチャー流Webサービスの作り方(開発チーム編)
このエントリーをブックマークに追加 このエントリーをlivedoorクリップに追加

尾藤正人(a.k.a BTO)です

前回はWebサービスを作るときの企画の部分について書きました (ベンチャー流Webサービスの作り方(企画編))。 今回はWebサービスを作るときの組織作りについて書いてみたいと思います。

僕がウノウに入って始めたのがフォト蔵の開発でした。 当初は開発が僕、ディレクションが代表の山田という二人体制でやってましたが、 組織が大きくなるにつれてだんだんと人数が増えていきました。 現在は僕も山田もフォト蔵からは離れて新しいチームで開発を行っています。

二人体制から始めて、少しずつ人数を増やしていって、 立ち上げメンバーが開発から離れるまでいろいろ経験しながら 自分が感じた事を簡単にまとめたいと思います。

・最終決断は一人で

何をするのか、戦略はどうするのか、方向性は何なのか、最終的な決断はリーダーが一人で行います。 個人の主張を尊重しすぎて、各々が好きな事を始めると体制がめちゃくちゃになります。 2:8の法則は、どんなに優秀な人、やる気のある人を集めても必ず成り立ちます。 最終的なビジョンの部分まで考えて行動できるのはリーダーだけです。

最終決断をリーダーがくださない状態になると以下のような事柄が発生します。

方向性がブレる - 大きなことやるにはチームが一丸となって取り組む必要があるのです が、 一人一人が自分のやりたい事しかやらなくなるので、何がやりたいのかがさっぱり分からなくなります。

全体の事を考えなくなる - サービスは一つの事柄だけとらえてはダメで全体として考え ないといけないのですが、 自分の範囲でしか物事を思考しなくなります。 過去の例としては、 「富豪的に機能を追加・変更する -> サーバ負荷が高くなる -> 誰も異議を唱えない」 「重大な不具合が発生 -> リーダは知らんぷり -> 他の開発者は自分の案件だけ -> 緊急対応にも関わらず対応してるのは担当者だけ」 というのが実際に起こりました。

・リーダーに必要な要素

では、リーダーはどういうことをやればいいのかをまとめてみます。

ビジョナリーである - 企業としてサービスの開発を行うなら大きなビジョンを持ちまし ょう。 小さな前進しか考えられないのであればリーダーの資格はありません。

決断力がある - 大きな事を実現するには大きな決断が必要です。 大きな決断をくだすのは、言うのは簡単ですが実行するのはなかなか難しいです。 失敗を恐れずに実行しましょう。決断をくだすことができる精神力を鍛えましょう。

こだわりを捨てられる - 実際にやってみるとうまく行かない場合がほとんどです。 そういう時に事実を客観的に受け止めて、考え方を変えるのは実はなかなかできません。

技術が分かる - 一流のプログラマである必要はありませんが、 プログラムを書けるようにはなっておきましょう。 技術が分からなければ実現可能性があるかどうかの判断もできません。 どうしても技術が分からないというのであれば、信頼の置ける参謀が必要です。

技術にこだわらない - 技術ができる人に多いのですが、 技術にこだわりすぎる人がいます。 最終目標はサービスの成功であって、高度な技術を活用することではないはずです。 あくまでも技術は目的を実現するための手段であることを心得ましょう。

雑用を積極的に引き受ける - 良いシステムを作るには現場の開発者の力が必要です。 リーダーは積極的に雑用をこなして、開発者が開発に集中できる体制を整えましょう。

開発者の希望をできるだけ尊重する - 開発者がシステムを作る原動力はモチベーション です。 どんなに優秀な人でもモチベーションのある人とない人とでは、 自ずと結果が違ってきます。 最終決断をくだすのはリーダーですが、 その中でもできるだけ開発者の意向を尊重するようにしましょう。

・コア開発者を捕まえる

自社でシステムを作るのですから、コアはしっかりと握っておきたいところです。 できるだけ自社のエンジニアに開発してもらいましょう。 外注するなら協業にしましょう。 いいサービスを作るのにはモチベーションが必要です。 単なる受託開発では真剣にやってもらえないので、協業にして密にやりとりしましょう。最悪なのが外注先のたらい回しです。 システムがどんどん汚くなって収集がつかなくなります。

・無駄なミーティングを増やさない

人数が増えてくるとだんだんミーティングの数が増えてきます。 何も考えずにやってると、いつの間にかミーティングの時間だけが増えて、 一生懸命やってるのに全然進まないという事態に陥ります。 定期的に見直して、ミーティングの数を減らす、効率よく行う努力を怠らないようにしましょう。

・ミーティングに参加する人数を増やさない

人数が増えてくるとミーティングに参加する人数がどんどん増えてきます。 人数が増えてくると時間がかかって議論がまとまらなくなるので全然進まなくなってきます。 細かい単位に分けたり、メリハリをつけるなどして工夫しましょう。

・フルコミットする

片手間でやるのはやめましょう。 受託開発をして日銭稼ぎつつ、 あるいは本業を持ちつつ空いた時間で片手間でサービスの開発を行うのは想像以上に厳しいです。 パートナーが別に仕事持っているならいさぎよく辞めてもらいましょう。 今の仕事を捨てられない程度の覚悟しかないのなら、 最初からやってもらわない方がマシです。

収益の不透明なサービス開発にフルコミットしてもらうのは、 組織的にも資金的にも苦しいと思います。 なんとかしてフルコミットしてもらえる体制作りをしましょう。 それは経営者の仕事であり、リーダーの仕事でもあります。

・まとめ

Webサービスの開発は戦略から要件定義、開発まで全て自分達で行います。 身内だけでやっていると、結果に対する評価がどうしても甘くなってしまって緊張がなくなりがちです。 適度な緊張感とモチベーションを維持しつつ、開発を続けていくにはしっかりとしたチーム作りが必要不可欠です。

2007年7月24日

PHP標準の機能(だけ)でウェブサイトのサムネイルを作る
このエントリーをブックマークに追加 このエントリーをlivedoorクリップに追加

こんばんわ、isogawaです。

PHP 5.2.2以降には、Windows版限定で一部のスキモノには注目の機能が追加されています。GDライブラリの開発者Pierre-A. Joyeによって実装された、imageGrabScreenとimageGrabWindowのふたつの関数がそれで、それぞれ画面全体と個々のウィンドウをキャプチャーするものです。

これを使えば、例えばInternet Explorerの画面をキャプチャーして、ウェブページのスクリーンショットやサムネイルを簡単に作成できたりします。ウェブページのキャプチャーはすでに色々な方法が知られていますが、標準機能として用意されたことで、(既存の手法と比べて性能面での優劣はさておき)実にお手軽に作成できるようになったわけです。

てなことで、これで遊んでみようと思うのですが。まずもちろん、Windows版PHPの5.2.2以降は必須ですが、これを利用するには、Windowsのサービスとして動作しているウェブサーバに、“デスクトップとの対話をサービスに許可”してやる必要があります。これを設定するには、Windowsコントロールパネルの管理ツールから「サービス」を選び、ウェブサーバがApacheなら、サービスの一覧からApacheを選択して表示される画面の「ログオン」タブで、「デスクトップとの対話をサービスに許可」をチェックして、Apacheを再起動してください。

ではまず試しにInternet Explorerのウィンドウをキャプチャーして、そのサムネイル(ここでは160×120ピクセル)を表示してみましょう。PHPからIEを操作するにはCOMを使用します。

<?php

$url = 'お好みのURLをどーぞ';

if (!extension_loaded('gd')) {
  exit('GD library is not loaded.');
} elseif (!function_exists('imagegrabwindow')) {
  exit('Function imageGrabWindow is not exist.');
}
try {
  $ie = new COM('InternetExplorer.Application');
  $handle = $ie->HWND;
  $ie->Width = 800;
  $ie->Height = 600;
  $ie->Visible = true;
  $ie->Navigate($url);
  while ($ie->Busy) {
    com_message_pump(4000);
  }
  $image = imagegrabwindow($handle);
  $ie->Quit();
} catch (Exception $e) {
  exit($e->getMessage());
}
if (!$image) {
  exit('Failed to grab image.');
}
$thumb = imagecreatetruecolor(160, 120);
imagecopyresampled(
  $thumb, $image,
  0, 0, 0, 0, 160, 120, 800, 600
);
header('Content-Type: image/jpeg');
imagejpeg($thumb);

ここではIEの起動時のウィンドウサイズを800×600に変更していますが、このようにIE(IWebBrowser2オブジェクト)のプロパティ/メソッドを操作することで、PHPでその動作や見た目を変更できます。IWebBrowser2オブジェクトで利用できるプロパティ/メソッドについてはIWebBrowser2 Interfaceを参照ください。

なお、途中のwhile文では、ページを読み込んでいる最中の画面をキャプチャーしないように、IEがページの読み込みを完了するまで待機しています。

さて、imageGrabScreen/imageGrabWindowが返すのはGDリソースなので、上の例のように、GDを使ってキャプチャー画像をリサイズしたり、あるいは文字や図形を重ねたりと、自由に変形できますが、さてそれではなにをしようかと思ったところで、ここで画像のリサイズコードをだらだらと書いてもしょうがないので、フォト蔵APIを利用して、作成したスクリーンショットをフォト蔵にアップロードしてみることにしました。

以下のスクリプトの実行にはフォト蔵アカウントが必要です。もしまだアカウントをお持ちでなければ是非、登録ページでユーザ登録してください!

  • フォト蔵では、写真や動画のアップロードはアルバム単位で行います。以下のスクリプト中で指定するアルバムのIDは、アルバムを表示したページのURL(http://photozou.jp/photo/list/数字/数字)の、末尾のほうの数字になります。例えばこのアルバムの場合、IDは「1771」です。
  • 以下ではAPIへのアクセスはPEARのHTTP_Requestで行っています。これがインストールされていない場合は、コマンドプロンプトで「pear install --alldeps HTTP_Request」と叩いてサクっとインストールしてやってください(pearコマンド自体がインストールされていない場合は、まずは「go-pear」を叩いてくださいね)。
<?php

/*
 * Photozou username (your email address), password
 * and the ID number of the album to which you want to post the image.
 */
$username = 'フォト蔵のユーザ名(メールアドレス)';
$password = 'フォト蔵のパスワード';
$album_id = アップロード先のアルバムのID(数字);

/*
 * URL to be displayed in IE.
 */
$url = 'お好みのURLをどーぞ';

/*
 * Check if required libraries are available.
 */
require_once 'HTTP/Request.php';
if (!extension_loaded('gd')) {
  exit('GD library is not loaded.');
} elseif (!function_exists('imagegrabwindow')) {
  exit('Function imageGrabWindow is not exist.');
}

/*
 * Launch Internet Explorer and capture the window image.
 * Exit if COM throws an exception.
 */
try {
  $ie = new COM('InternetExplorer.Application');
  $handle = $ie->HWND;
  $ie->Width = 800;
  $ie->Height = 600;
  $ie->Visible = true;
  $ie->Navigate($url);
  while ($ie->Busy) {
    com_message_pump(4000);
  }
  $image = imagegrabwindow($handle);
  $ie->Quit();
} catch (Exception $e) {
  exit($e->getMessage());
}

/*
 * Save the captured image as a temporary file.
 */
$filename = tempnam(sys_get_temp_dir(), 'tmp');
if (!$image || !$filename || !imagejpeg($image, $filename) || !file_exists($filename)) {
  exit('Failed to save image.');
}
imagedestroy($image);

/*
 * Post the temporary file to Photozou via API.
 */
$request = new HTTP_Request;
$request->setURL('http://api.photozou.jp/rest/photo_add');
$request->addHeader('User-Agent', 'Hogebot/1.0 (Bogus)');
$request->setBasicAuth($username, $password);
$request->setMethod(HTTP_REQUEST_METHOD_POST);
$request->addPostData('album_id', $album_id);
$request->addFile('photo', $filename, 'image/jpeg');
$result = $request->sendRequest();

/*
 * Exit if the API request was failed.
 */

$error_message = '';
$code = $request->getResponseCode();
if (PEAR::isError($result)) {
  $error_message = $result->getMessage();
} elseif (!in_array($code, array(200, 401))) {
  $error_message = "Responded status code was $code.";
} elseif ('' == $body = $request->getResponseBody()) {
  $error_message = 'Responded body was empty.';
} else {
  try {
    $rsp = new SimpleXMLElement($body);
  } catch (Exception $e) {
    $error_message = $e->getMessage();
  }
  if (isset($rsp) and (string) $rsp['stat'] != 'ok') {
    $api_error_messages = array();
    foreach ($rsp->err as $error) {
      $api_error_messages[] = (string) $error['msg'];
    }
    $error_message = implode("\n", $api_error_messages);
  }
}
unlink($filename);
if (!empty($error_message)) {
  header('Content-Type: text/plain;charset=UTF-8');
  exit($error_message);
}

/*
 * Respond HTML if everything went well.
 */
echo "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n";
?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="ja" lang="ja">
  <head>
    <meta http-equiv="Content-Type" content="text/html;charset=UTF-8" />
    <meta http-equiv="Content-Style-Type" content="text/css" />
    <title>Photozou</title>
  </head>
  <body>
    <h1>Image was uploaded.</h1>
    <div><?php echo $rsp->large_tag ?></div>
  </body>
</html>

2007年7月20日

PEAR::Net_URL_MapperでURLルーティングを制御する
このエントリーをブックマークに追加 このエントリーをlivedoorクリップに追加

miyakeです。

php4のサポート打ち切りが発表されて様々な物議を醸している今日この頃、皆様いかがお過ごしでしょうか。

今日はphpでURLルーティングをしてくれるPEARライブラリ、Net_URL_Mapperをご紹介します。

このNet_URL_Mapperはphp5専用となっており、残念ながらphp4では動作しません。また、公式ドキュメントが英語版すら用意されておらず、Web上にもほとんど資料がなく手探りで使うような状況です。

そんなNet_URL_Mapperですが、個人的にはなかなか重宝しているので、少しでも使う人が増えてくれればいいな、ということで基本的な使い方をまとめてみました。

では、早速コードを見てみましょう。

// $path = 'blog/view/123';
$router = Net_URL_Mapper::getInstance();
$router->connect('blog/:action/:id');
var_dump($router->match($path));

$pathにコメントのような、URLから受け取った文字列が入っているという前提です。このコードを実行すると、

array(2) {
  ["action"]=>
  string(4) "view"
  ["id"]=>
  string(3) "123"
}

という出力が得られます。これだけで、「blog/(アクション名)/(id)」というURLに対するマッピングができます。お手軽ですね。

$pathが指定のルールにマッチしない場合はmatch()はnullを返します。ルールは $router->connect(~); を書き連ねることで複数設定が可能で、最初にマッチした物が優先されます。

このように、URL文字列からパラメータを取得するというのが、Net_URL_Mapperの一番メジャーな用途になると思います。URLマッピングを定義しているconnect()メソッドについて、もう少し詳しく見てみましょう。connect()は以下のような引数をとります。

connect(パス, [初期値の配列], [値のルール]);

パスは先程のサンプルのように、/で区切った文字列を指定します。:~ もしくは :(~) と書くことで、match()に投げる文字列の値をパラメータとして取得することができます。

初期値の配列と値のルールは省略可能です。それぞれパスで指定したパラメータ名をキーとする連想配列で指定します。

と言っても分かりにくいので、実際に書いてみましょう。

■初期値を指定した例

$router->connect('shopping/:action', array('controller' => 'Store', 'action' => 'index'));
var_dump($router->match('shopping'));
array(2) {
  ["controller"]=>
  string(5) "Store"
  ["action"]=>
  string(5) "index"
}

ここでmatch()に投げられたURLを表す文字列は:actionにあたる部分が省略されていますが、出力結果にはactionには初期値で設定した「index」が入っています。これは、$router->match('shopping/index'); が実行されたのと同じことになります。

もう一つ設定しているcontrollerはURLのルールにはない値です。このように初期値だけを指定すると、そのルールがマッチした時にmatch()の実行結果に固定値を渡すことができます。

このように、初期値を設定するとその値が省略可能になるので、

$router->connect('blog/:action/:id', array('controller' => 'blog', 'action' => null, 'id' => null));

のようにすれば、blog/view/3 も blog/view も blog/ もマッチさせることができます。

■ルールの指定

先程の例では、パスだけを指定した最小限の設定でしたが、ここに「idは数値だけに限定する」というルールを加えてみましょう。connectを呼んでいる行を以下のように変更します。

$router->connect(':controller/:action/:id', array(), array('id' => '\d+'));

ルールは正規表現で指定します。この状態で blog/view/abc を評価すると、Net_URL_Mapper_InvalidException例外が発生します。ルールにマッチしなかった場合はnullが返りますが、「マッチはしたけどルールに合致しなかった」場合は例外を投げるようになっていますので注意してください。

try {
    $router->match($path);
} catch (Net_URL_Mapper_InvalidException $e) {
    header('Location: http://www.example.com/', 302);
}

のように例外をキャッチしてリダイレクトする、といったパターンがありがちでしょうか。

初期値とルールを組み合わせると、

$router->connect('blog/archive/:year/:mon/:day', array('mon' => null, 'day' => null), array('year' => '\d{4}', 'mon' => '\d{1,2}', 'day' => '\d{1,2}'))

で、

  • blog/archive/2007
  • blog/archive/2007/7
  • blog/archive/2007/12/31

にそれぞれ対応するようなルールが書けます。基本的な使い方はざっとこんな感じです。

■ワイルドカードによるパラメータ指定

「connect()に設定するパス内でパラメータを設定するには、:~で書く」と解説しましたが、実は *params のように最初の文字をアスタリスクで指定することもできます。両者の違いはパラメータの内容に/を含められるかどうかです。

$router->connect(':controller/:action/*params', array('action' => 'index', 'params' => null));
var_dump($router->match('user/edit/hoge/foo/bar'));

このコードを実行すると、

array(3) {
  ["params"]=>
  string(12) "hoge/foo/bar"
  ["controller"]=>
  string(4) "user"
  ["action"]=>
  string(4) "edit"
}

となります。このように、パスの最後を*によるワイルドカード指定にして、まとめるという使い方が最も一般的ではないかと思います。

また、若干変速的ですが、

$router->connect('user/*name/login');
var_dump($router->match('user/foo/bar/login'));

とすれば、nameにfoo/barが入った状態でマッチします。

他にも、

$router->connect(':params');
$router->match('');

はnullになるけど、

$router->connect('*params');
$router->match('');

はマッチしてarray()が返ります。使いどころがあるかは分かりませんが…

■まとめ

Net_URL_Mapperはドキュメントが整備されていませんが、テストコードはかなり分かりやすいので、PEARのサイトからアーカイブをダウンロードしてtests/以下のコードを読んでみることをお勧めします。

また、一部を除けば追いかけやすいコードになっていますので、今までphp4を使っていてphp5はこれからだという人は、(その一部を除いて)実際にソースを追ってみるのも参考になるかも知れません。

他にも、Net_URL_Mapperには今回の内容とは逆方向のパラメータからURLを作成する機能もあります。そちらも今回ご紹介した内容を踏まえた上でテストコードを読めば、理解するのは難しくないと思います。

「php5専用のライブラリ」というと今までは肩身の狭い立場にあり、敬遠されがちな印象がありましたが、いよいよ目を向けていく時期に入ったように感じられます。

これから先、phpとphpを使うエンジニアにとっては苦しい時期を迎えるかもしれませんが、移行できてしまえば「ちょっと洗練された開発環境がありますよ」と言えるかも知れません。

2007年7月19日

名刺作りで学ぶレイアウト(+α)基礎講座 その2
このエントリーをブックマークに追加 このエントリーをlivedoorクリップに追加

こんにちは! yamazakiです。
前回のレイアウト講座、いかがだったでしょうか。
今回は、前回の続きとして、さらにもう少しあれこれと、名刺・書類などを作るときに役に立ちそうなレイアウト回りのコツなどを、やや補足的に書いてみようと思います。
なお、今回も作例はフォントを「HGP創英角ゴシックUB」「HGPゴシックE」、色は黒のみ、でやってみることにしておきます。

安定/不安定

「安定した見た目」「不安定な見た目」というのがあります。実際にいくつかやってみましょう。

上のほうに大きなもの、太いものがあると、どことなく不安定な印象になります。
meishi1
meishi1 posted by (C)フォト蔵

下のほうに大きなもの、太いものがあると、安定感が出てきます。
meishi2
meishi2 posted by (C)フォト蔵

左右/上下が線対称、もしくは、点対称であれば安定感が出ます。
meishi3
meishi3 posted by (C)フォト蔵

左右/上下が非対称だと、不安定な印象になります。ついでにやや傾けたりすると余計に不安定感が増します。
meishi4
meishi4 posted by (C)フォト蔵

で、この「安定」「不安定」がどういった印象につながるかというと

安定したものは、
「落ち着き」や「安定感」、「信頼感」といった印象が出てきます。
一方で「没個性的」「つまらない」「あたりさわりのない」といったややネガティブな印象を与えることもあります。

不安定さというのは
「個性的」「洗練された」「若者的」「都会的」な雰囲気を出すのに適していると思います。
一方で「粗野」「不安定」「信頼性のない」「落ち着きのない」などの印象につながることもあります。

整然/乱雑

整然としたものには「美しさ」や「上品さ」「丁寧さ」などを感じます。
整然としたもの、をイメージしてみてください。大抵のものは「列がそろっている」とか「等間隔」とか「左右対称」とかいったものが浮かぶと思います。
つまり人は「規則性のある」ものには「整然としている」という印象を受けます。そして、そういった「整然とした」様子に、「美」「整った印象」「信頼感」「安定」といったものを感じます。
meishi5
meishi5 posted by (C)フォト蔵


逆にその「規則性」を壊してしまえば「乱雑」になります。「乱雑」になると、「粗野」「適当」「乱雑」「いい加減」「勢いがある」「暴力的」など、あまりビジネス方面向けではよくなさそうな印象が強く出てきます
meishi6
meishi6 posted by (C)フォト蔵

が、これはこれで、たとえばロックミュージシャンの名刺、などであればかなりカッコイイ印象になるかもしれません。

グリッドレイアウト

グリッドレイアウト、について書き始めると、だいぶ長くなりそうなのでここではさわりだけ。
前項の「整然」を意識して行う場合には「グリッドレイアウト」というのを意識してやると「整った」見た目を作りやすくなるかもしれません。
グリッドというのは、「格子」のことで、たとえば名刺の中にこんな線があると想定してみましょう。
meishi7
meishi7 posted by (C)フォト蔵

で、この格子のラインにそろうように要素を当てはめて、かつバランスのよさそうな位置に置くように気を使って名刺作ってみると…
meishi8
meishi8 posted by (C)フォト蔵

meishi9
meishi9 posted by (C)フォト蔵

線はどこにも引いていないのに、文字の境界によって線が浮かび上がって、整った印象になります。

また、グリッドを傾けたり、グリッドの間隔をダイナミックに変えることで、ただ「整然とした」というだけでなく、「整っていて美しい上に個性的」「整然としているけどアヴァンギャルド」など、さらなる味付けへと発展させることもできます。
meishi10
meishi10 posted by (C)フォト蔵

meishi11
meishi11 posted by (C)フォト蔵

この「グリッドレイアウト」という手法は、Webデザインの現場でもかなり多用されているやり方ですので、Web方面のデザインをやっている方は知っておくとかなり便利です。興味のある方は「グリッドレイアウト」をキーワードにあれこれ調べてみると、非常に有用なリソースや本などが色々みつかりますのでぜひ調べてみてください。

今回はだいぶ急ぎ足で書いてしまいましたが、いかがでしたでしょうか。
名刺ひとつ、文字の並びひとつとっても色々とやり方、考えるポイントがあるものです。
大事なのは、自分が一体この名刺で「どういう人だと思われたいのか」「どんな印象を与えたいのか」を少し考えてみる事、です。そこがはっきりしさえすれば、おのずと選ぶべきレイアウトなどが絞られてくると思います。
あとは実際に、最終的にどういう形に仕上げるか。そこはそれぞれのセンスで。もちろんこれまでのセオリーを破るのもひとつの方法です。クリエイティブにいきましょう!

2007年7月18日

Rails風フレームワークPylonsで簡易Wikiを作ってみる
このエントリーをブックマークに追加 このエントリーをlivedoorクリップに追加

Yet Another Sakatokuです。今回はPyhonで書かれたRuby on Rails風のフレームワークPylonsの使い方を簡単に紹介したいと思います。

PylonsはPythonのWebフレームワークとしては、現在のところ、Django, TurboGearsにつぐ三番手(以下)と見なされていますが、TurboGears 2がPylonsと合流して、Pylons上に旧TurboGearsのAPIを提供していくことが表明されましたので、今後大きな勢力になっていくと思われます。

サンプル・アプリケーション

サンプルとして、簡易Wiki(CoCoWiki)を作ってみました。以下のURLからダウンロードできるので参考にしてみてください。

cocowiki.tar.gz

Pylonsの開発サーバ上で動作させるには、ダウンロードしたtar.gzを解凍し、cocowikiディレクトリで"paster serve"コマンドで実行します。

$ tar xzf cocowiki.tar.gz
$ cd cocowiki
$ paster serve development.ini

上手く動作していれば、http://localhost:5000/にアクセスすると次のような画面が表示されると思います。

cocowiki
cocowiki posted by (C)フォト蔵

動作確認はWindows版Python2.5.1, Linux版Python2.4.3で行いました。後述の通り、あらかじめPylonsとSAContextをインストールしている必要があります。また、Python2.4以前のバージョンの場合は、pysqlite2ライブラリも必要です。

Pylonsのインストール

例によってeasy_installを使ってインストールします。ただ、僕の環境ではsetuptoolsのバージョンが古いというエラーが発生したので、最初に以下のURLから最新版のsetuptoolsをダウンロードし、インストールしました。

http://cheeseshop.python.org/pypi/setuptools

次に、Pylonsをインストールします。現在配布されているバージョンは0.9.6RC1です。

$ easy_install -U Pylons

これで、Pylonsの依存するライブラリがすべてインストールされるはずです。

Pylons 0.9.6の大きな変更点として、データベース接続にSAContextというライブラリの使用が推奨されるようになったことがあります。今回作成するアプリケーションではこのSAContextを使うので、これもインストールしておきます。

$ easy_install -U SAContext

プロジェクトを作成する

新しくPylonsプロジェクトの作成するには"paster create"コマンドを使います。

プロジェクト名は、"cocowiki"としました。

$ paster create --template=pylons cocowiki
$ cd cocowiki

開発用のサーバを起動するには、"paster serve"コマンドを使います。

$ paster serve development.ini

アプリケーションの設定

まずdevelopment.inにアプリケーションの設定を記述します。

今回はSessionを使わないので、とりあえずはデータベースの設定のみです。

sqliteを使うことにしましたが、SQLAlchemyがサポートするデータベースならば何でも構いません。

sqlalchemy.default.uri = sqlite:///%(here)s/data/cocowiki.sqlite.db
sqlalchemy.default.echo = true
sqlalchemy.default.echo_pool = false
sqlalchemy.default.pool_recycle = 3600

SQLAlchemyのサポートするデータベースとURIの記述の仕方については、こちらのドキュメント(英語)を参照してみてください。

モデル定義とデータベースの初期化

次にモデルの定義をcocowiki/model/__init__.pyに記述します。

# -*- coding: utf-8 -*-
# cocowiki/model/__init__.py
from sqlalchemy import *
from sqlalchemy.ext.assignmapper import assign_mapper

from urllib import quote
from datetime import datetime

from sacontext import PylonsSAContext

# Pylons 0.9.6から以前のpylons.databasesがdeprecated扱いになりました。
# 代わりにSAContextのPylonsSAContextを使うことが推奨されています。
# 詳しくはhttp://sluggo.scrapping.cc/python/sacontext/を参照してください。
sac = PylonsSAContext()
sac.add_engine_from_config(None)

# テーブル定義
pages = Table('pages', sac.metadata,
              Column('id', Integer, primary_key=True),
              Column('word', Unicode(250), nullable=False, unique=True),
              Column('content', Unicode, nullable=False),
              Column('created_at', DateTime, default=datetime.now()),
              Column('updated_at', DateTime, default=datetime.now(), onupdate=datetime.now()),
              mysql_engine='InnoDB',
              )

# Wikiのページに相当するドメインオブジェクト
class Page(object):
    def __init__(self, word=None, content=None):
        self.word = word
        self.content = content

    def __repr__(self):
        return '<cocowiki.model.Page "%s">' % self.word

    def get_absolute_url(self):
        """
        このオブジェクトのURLを取得します
        これはPylonsの流儀ではないかもしれませんが便利です!
        """
        return '/%s' % quote(self.word.encode('utf-8'))

# AssignMapperを使ってテーブルとドメインオブジェクトを
# マッピングします
assign_mapper(sac.session_context,
              Page,
              pages)

モデルの設計は、あっさりとしたもので済ませることにしました。

いくつか注釈を付け加えるならば、

  • このような単純な例ならば、ElixirというSQLAlchemyのラッパーライブラリを使う手もあります。このライブラリを使うと、モデル定義をよりRails的に(ActiveRecord的に)行うことができます。ただ、個人的にはあまり好みではないので使っていません。
  • Pylonsのサンプルでは、しばしば、"assign_mapper"ではなく"mapper"が使われていますが、assign_mapperの方がコードが簡潔になるのでお勧めです。
  • もっと大きなアプリケーションを書く場合、project/model/__init__.pyにドメインオブジェクトの定義も、テーブルの定義も一緒に書いてしまうと収拾が付かなくなります。本来ならば、project/model/tables.py, project/model/domains.py...のように細かくモジュールを分けるのがお勧めです。このアプローチはSQLAlchemyのサンプル(test/zblog)が参考になります。

データベースの初期化のコードは、cocowiki/websetup.pyに記述しておきます。

# -*- coding: utf-8 -*-
"""Setup the cocowiki application"""
from paste.deploy import appconfig
from pylons import config

from cocowiki.config.environment import load_environment

def setup_config(command, filename, section, vars):
    """Place any commands to setup cocowiki here"""
    conf = appconfig('config:' + filename)
    load_environment(conf.global_conf, conf.local_conf)

    from sqlalchemy import exceptions
    from cocowiki.model import sac, Page
    sac.metadata.create_all(checkfirst=True)

    try:
        start_page = Page('StartPage', u'ここうぃきの世界にようこそ')
        sac.session_context.current.flush()
    except exceptions.SQLError, e:
        pass

コマンドラインから、"paster setup-app"コマンドを使うと、このコードを実行することができます。

$ paster setup-app development.ini

コントローラの作成, ルーティングの設定

コントローラを作成します。

$ paster controller page

これで、cocowiki/controllers/pages.pyにPagesControllerというコントローラクラスが生成されます。このコントローラのメソッドに各処理(アクション)を実装していきます。

それらのアクションとURLを対応付けているのが、cocowiki/config/routing.pyです。ルーティングについて簡潔にまとまっているページがなかなか見つからないので、Routeのドキュメントにあたるのが一番早そうです。今回は、ドキュメントを参考に次のようなURL設計にしてみました。

/list
登録済みのページ一覧の表示
/WikiWord
WikiWordというページの表示
/WikiWord;edit
WikiWordというページの編集(新規登録)フォームの表示

"/list"で一覧表示というのが、あまりエレガントではないかもしれません。また、リソースの新規作成と更新をURL上でも、HTTPメソッドの上でも区別しないことにしています。"/WikiWord"というURLにPOSTすると、ページがすでにある場合は更新し、無い場合は新規作成します。

このような動作に対応するrouting.pyは、以下のようになります。

# cocowiki/config/routing.py

def make_map():	
	# ...略

    # 登録されている単語を表示するページです。
    map.connect('list', controller='pages', action='index')

    # Wikiの編集フォームのページ
    map.connect('*(word);edit', controller='pages', action='edit')

    # Wiki更新のアクションのマッピングです。
    # フォームの内容にエラーがある場合は、再度上のeditページを表示します。
    map.connect('*word', controller='pages', action='update', conditions={'method':['POST']})

    # Wikiの個々のページです。
    # 上記のform, edit, list以外の単語がWikiワードになります。
    map.connect('*word', controller='pages', action='show')

    return map

Wikiとは違って、リソースをデータベースのinteger型のキーで識別するようなアプリケーションでは、Rails風(REST風)のURLマッピングが使えます。この場合は、コントローラを"paster restcontroller"で作成します。

$ paster restcontroller object objects

"paster restcontroller"に関しては、別の場所でメモを残したことがあるので、興味がある方は参照してみてください。Pylons 0.9.6になっても、この部分は変わっていないと思います。

コントローラの実装

いよいよコントローラにロジックを書いていくわけですが、すべてのアクションについて解説していくと長くなるので、ページの取得の部分(showメソッド)を例にとって、Pylonsの基本を説明していきます。

class PagesController(BaseController):
    def show(self, word=None, format='html'):
        """
        Wikiワード"Word"を表示します。
        WordがNoneの場合、つまりURLが"/"の場合は、/StartPage
        にリダイレクトさせます。
        """
        if not word:
            redirect_to('/StartPage')

        c.page = Page.get_by(word=word)
        if c.page is None:
            # wordでページが登録されていない場合は、
            # 新規登録のフォームを表示します。
            return self.edit(word)

        # 更新順に10件のページを取得します。
        # limit=11で最大11件表示しているのは、「もっと見る」の
        # リンクの表示する条件を簡略化するためです。
        c.words = Page.select(order_by=desc(Page.c.updated_at),
                              limit=11)
        return render('show')
  • ビュー(テンプレート)に値を渡すには、"c"を使います。上記の例では、"c.page = ...", "c.words = ...."がそれに当たります。
  • リダイレクトさせる時にはredirect_toを使います。これは例外として処理されるので、return redirect_to('/path')のようにする必要はありません。
  • HTTPエラー(例えば404 Not Found)を返したい場合は、abort(404)を使います。
  • 以前のバージョンのPylonsではテンプレートをレンダリングするのに、render_responseというメソッドを使っていましたが、今はrenderを使うようです。

他のメソッドは、サンプルアプリケーションのコードを参照してください。Pylonsの流儀に慣れてしまえば、難しいことは何もしていないことがお分かりになると思います。

テンプレートの作成

テンプレートは、cocowiki/templatesに配置します。現在のPylonsの標準テンプレートエンジンはMakoで、上述のrenderメソッドを使った場合、render('show')ならばcocowiki/templates/show.makが使用されます。

Makoの構文は以前簡単に紹介したとおりですが、今回は継承機能を使ってみました。Makoの継承機能についてはこちらのメモもご覧下さい。

実際のcocowiki/templates/show.makは次のようになっています。

<%inherit file="base.mak" />
<%def name="title()">${c.page.word | h}</%def>
<h2>${title()}</h2>
${ h.cocowiki_markup(c.page.content) }
<h3>${ _('Menu') }</h3>
<ul>
  <li>${ h.link_to(_('Edit this page'), h.url_for(word=c.page.word, action='edit')) }</li>
</ul>

<h3>${ _('Recent changes') }</h3>
% if c.words:
<ul>
% for word in c.words[:10]:
  <li><a href="${word.get_absolute_url()}">${word.word | h}</a></li>
% endfor
</ul>
  % if len(c.words) > 10:
    <span>» ${ h.link_to(_('See more'), h.url_for(word=None, action='index')) }</span>
  % endif
% else:
  <p>${_('No pages')}</p>
% endif

Pylonsのテンプレートについてまず知っておくべきことは、次のようなことです。

  • コントローラで割り当てた変数は、テンプレートからも"c"で参照できます。
  • "h"を使うと、cocowiki/lib/helpers.pyに定義されている関数、変数を参照できます。上記の例で言うならば、h.cocowiki_markup, h.link_to, h.url_for等がそれに当たります。
  • 今回は使いませんでしたが、"g"を使って、cocowiki/lib/app_globals.pyに定義されている値を参照することができます。これはアプリケーション共通の設定を参照するのに便利です。
  • "request"を使って、そのリクエストの根底にあるWSGIRequestオブジェクトを参照することができます。これは、リクエストのPATH_INFOを調べたりする際に使用できます。

独自のビュー・ヘルパー関数を定義する

今回作成したアプリケーションでは、cocowiki/lib/helpers.pyに簡易Wiki記法をHTMLに変換するcocowiki_markupという関数を定義しています。

# -*- coding: utf-8 -*-
from webhelpers import *
from urllib import quote
import re
from cgi import escape

WIKI_WORD_RE = re.compile(r'\[\[(.+?)\]\]')

def cocowiki_markup(value):
    """

    WIKIのページデータをHTMLに変換します。
    """
    def repl(matcher):
        word = matcher.group(1)
        return '<a href="%s">%s</a>' % (quote(word.encode('utf-8')),
                                        escape(word))

    # markdown記法で変換
    value = markdown(value)

    # http://example.com, mail@example.comをリンクに変換
    value = auto_link(value)

    # [[word]]という表記をWikiワードとして扱う
    value = WIKI_WORD_RE.sub(repl, value)

    return value

静的なファイルの配信

cocowiki/publicに置いたファイルは静的なファイルとして配信することができますので、ここにスタイルシートや画像をおきました。

まとめと謝辞

以上、Pylonsの概要を駆け足で紹介しました。サンプルアプリケーションと合わせて参照していただくことを念頭に執筆しましたので、かなり説明を省略しているところもあるかと思います。

分かりにくいところや、間違いがあった場合は、お気軽にコメントやトラックバックをいただけましたら幸いです。

また、サンプルアプリケーションのテンプレートには、stansさんが配布されているテンプレートを利用させて頂きました。ありがとうございました。

2007年7月17日

MySQL最適化のミニtips
このエントリーをブックマークに追加 このエントリーをlivedoorクリップに追加

yukiです。
今回はWebサイトを製作する上で欠かせないデータベース(DB)のお話です。Linux、Apache,MySQL,PHPを組み合わせたLAMPという言葉が登場して久しいですが、Webサービスを構築する上で欠かせないのがDBの存在ですね。
運用後Webサイトが順調に拡大し規模も大きくなってきた頃、パフォーマンスに悩むことも出てくるものです。
ハードウェアや構成に問題がある場合、ロジックに問題がある場合など様々ですが、DBを見直してみるのも手かもしれません。
銀行の預金残高などのようにミッションクリティカルである場合や、ともかくパフォーマンス性を求められるなど様々あり、一概に言えるものでもありませんが、 Webサービスにおいては有名な8秒ルールも、最近では6秒、3秒、1秒と求められるパフォーマンスはどんどん短くなって来ています。
パフォーマンスだけでなく、メンテナンスコストやスケーラビリティなどすべてに大きく関わり、システム的な面においてはDBが大部分を握っていると言っても過言ではありません。
設計時点で悩むDBですが、ただ正規化すればよいというものでもなく、どのような場面で利用され、また最も多くアクセスが発生するかなどを考慮する必要が出てきます。
今回はMySQLを例に取り上げますが、それぞれの性質を考慮した上で判断していくことが重要になってきます。
とはいえ、MySQL自体の設定を変更できない場合などもあるので、一般的な注目・改善点をご紹介します。

MyISAM、InnoDBなどテーブルタイプ
トランザクションやロック処理などが必要ない場合など、MyISAM形式にも良いところはあるので検討してみる価値はあるかもしれません。
更新(update)・削除(delete)のフラグ化
一概には言えませんが、レコード数が大きくなるとコストが上がってきます。整合性などの注意点もあるので慎重に行いましょう。
レコード数などの参照は非正規化してみる
都度レコード数を集計するのではなく、一部非正規化して持つなど。問い合わせ発行数を減らすのも改善案のひとつです。
indexが適切かどうか
無駄なindexはないかどうかを調査してみましょう。かえって遅くなったりしているかもしれません。
利用範囲を考慮する
データが利用される場面を想定し、リアルタイム性を求められなかったり、冗長性がないかどうかを調べてみましょう。場合によってはログテーブルなどを利用するのもいいかもしれません。
サマリテーブルの導入を検討する
ランキングなど集計処理はコストが高くつきがちなので、リクエスト毎に生成する必要があるかを検討する。
explain で参照処理を検証する
適切なindexが利用されているか、filesortが発生していないか、オーバーヘッドが大きくないかなどの情報を見ることが出来ます。
そもそも必要な処理・問い合わせなのかを見極める
別テーブルや静的ファイルで代用可能かどうかなど、意外な盲点があるかもしれません。

このtipsはもちろんすべてに応用できるものではなく、個々の場合に応じて最適解は異なって来ます。(特に更新・削除フラグなど)
自サービスに合うかどうか、そもそも設計時点で無理はないかなど、多角的に見る事が重要です。

2007年7月14日

Mash up Award 3rd 開催中です
このエントリーをブックマークに追加 このエントリーをlivedoorクリップに追加

こんにちは、matsuda です。
先月末よりサン・マイクロシステムズ株式会社さんと株式会社リクルートさん共催のMash up Award 3rd の作品募集が始まりました。

そのMash up Award 3rd にウノウも協力企業としてフォト蔵APIを提供しています。

開催概要や参加申込み方法はこちらのページをご覧ください。
↓↓↓↓
MA3_468x60_w1

最優秀賞金100万円だそうです!腕に覚えのある方、これからAPIで遊ぼうとしている方などなど、フォト蔵APIも利用して応募してみてはいかがでしょうか。新たな写真・動画を利用した面白いサービスが応募される事を楽しみにしています。

また、フォト蔵APIを利用したサービスは、フォト蔵開発日誌でも紹介させていただきますので、こちらのフォト蔵APIコミュニティまで是非ご連絡くださいませ。

2007年7月12日

水平方向のナビゲーションの作り方
このエントリーをブックマークに追加 このエントリーをlivedoorクリップに追加

yamaokaです。

水平方向に並んだナビゲーションを作る場合、 皆さんはどのようにマークアップされているでしょうか。 とりあえず必要な項目を羅列してみましょう。 それぞれの項目には矢印の画像を付加するものとします。

== HTML ==
<p id="navigation">
  <img src="arrow.gif">編集
  <img src="arrow.gif">削除
  <img src="arrow.gif">追加
</p>

horizontal_nav_1
horizontal_nav_1 posted by (C)フォト蔵

一見よさそうです。 ただ、HTMLの論理的な構造としてふさわしいものでしょうか。 ナビゲーションと言えども論理構造としてはリストの一種なので、リストとしてマークアップするのが適当と言えます。

リストにしてみましょう。

== HTML ==
<ul id="navigation">
  <li>編集</li>
  <li>削除</li>
  <li>追加</li>
</ul>

horizontal_nav_2
horizontal_nav_2 posted by (C)フォト蔵

リストの先頭に表示される記号が邪魔な感じですね。 先程使用した矢印の画像に置き換えてみましょう。

=== CSS ==
#navigation {
  list-style-image: url(arrow.gif);
}
== HTML ==
<ul id="navigation">
  <li>編集</li>
  <li>削除</li>
  <li>追加</li>
</ul>

horizontal_nav_3
horizontal_nav_3 posted by (C)フォト蔵

これで矢印の画像が表示されるようになりました。 垂直方向に並んでいるのを水平方向に並ぶようにして、 それぞれの項目の間のスペースを調整します。 ここでは、それぞれの項目をfloatさせることにします。

== CSS ==
#navigation {
  list-style-image: url(arrow.gif);
  list-style-position: inside;
}
#navigation li {
  float: left;
  width: 3em;
  margin: 0 20px 0 0;
  padding: 0;
}
== HTML ==
<ul id="navigation">
  <li>編集</li>
  <li>削除</li>
  <li>追加</li>
</ul>

horizontal_nav_4
horizontal_nav_4 posted by (C)フォト蔵

大体よさそうですが、よく見ると文字の垂直方向の中央と矢印の画像の位置が揃っていません。 これは画像やフォントの大きさによるのですが、できれば揃えてしまいたいところです。

リストの記号として画像を指定した場合、その画像の位置をCSSで指定することはできません。 あえて揃えるなら、画像の大きさを空白部分を設けるなどして調整する必要があります。 また、そうした場合でも、フォントの大きさを変更した場合にレイアウトが崩れてしまうのを避けることはできません。

しかし、背景画像なら位置を指定することができます。 そこで、li要素の背景画像に矢印の画像を配置して、横に並ぶようにしてみます。 もうリストの記号は必要ないので、消してしまいましょう。

== CSS ==
#navigation {
  list-style: none;
}
#navigation li {
  float: left;
  width: 3em;
  background: transparent url(arrow.gif) no-repeat scroll left center;
  padding-left: 12px;
  margin-right: 15px;
}
== HTML ==
<ul id="navigation">
  <li>編集</li>
  <li>削除</li>
  <li>追加</li>
</ul>

horizontal_nav_5
horizontal_nav_5 posted by (C)フォト蔵

いかがでしょうか、最初に思ったとおりの表示結果が得られたでしょうか。 上記のコードのままだとfloatの指定が残ってしまうので、 実際には#navigationでclearfixハックをする必要があるかと思います。 clearfixハックに関しては以下の記事が詳しいので、参考にどうぞ。

注意する点としては、ナビゲーションが複数行になってしまった場合に、 矢印の画像がその複数行分の高さの中央に配置されてしまうという問題があります。 そうなってしまいそうな場合は、矢印の画像の位置を調整するなどしていろいろ試してみてください。

また、上記の例ではli要素をfloatさせて水平方向に並べましたが、 display: inline; を指定することでも水平方向に並べることができます。 しかし、その場合は上下のマージンが指定できないこと、 また、widthが指定できないのでボタンのような表現がしづらいことに留意する必要があります。

常に論理的なマークアップを心がけるようにしておくと、 自然にシンプルな構成のHTML(XHTML)になるのではないかと思います。 頭の片隅で常に意識しておきたいですね。

追記

floatを指定した要素に対して、widthを指定していなかったのを修正しました。CSS2の仕様では、floatを指定したliに対しては必ずwidthを指定する必要があります。ご指摘いただきましてありがとうございました。

TilePlex脆弱性問題の追跡調査と再開につきまして
このエントリーをブックマークに追加 このエントリーをlivedoorクリップに追加

このたびはTilePlexの脆弱性問題につきましてご心配とご迷惑をおかけし大変申し訳ありません。

追跡調査と再開につきましてウノウ・ホームページの方に掲載いたしましたので、ご覧ください。

2007年7月11日

3G携帯のみに限定したサイトを作る場合
このエントリーをブックマークに追加 このエントリーをlivedoorクリップに追加

harukiです。

最近では3Gのみ対応というサイトも増えてきています。 そこで、3Gに限定した場合の端末の対応具合をまとめてみました。

以下を該当の機種として進めます。

DoCoMoFOMA(※)
auWAP2.0
SoftBank3GC型

※XHTML対応機種(FOMA 2001,2002,2101V以外)

●記述言語

HTMLが必須ではなくなり、XHTML「のみ」という選択も可能になります。

●文字コード

Shift_JISに加えてUTF-8、EUC-JPも使えるようです。

●ページサイズ

DoCoMo100KB
auテキスト9KB程度以内
SoftBank300KB

テキストと画像のサイズ合計で1.2KB, 5KB, 7.5KB, 10KBということをあまり気にしなくてもよくなります。

●画像

DoCoMoGIF / JPEG
auGIF / JPEG / PNG
SoftBankGIF / JPEG / PNG

よほどのことがない限り、GIFとJPEGで事足ります。

●メール

端末が受信可能な文字コードは以下になります。

DoCoMoShift_JIS / ISO-2022-JP
auShift_JIS / JIS / ISO-2022-JP
SoftBankUTF-8 / ISO-2022-JP

3キャリアすべてで、太字になっている文字コードでは、メールに絵文字を含めることが可能です。

また、サイズは以下になります。

DoCoMo全角5,000文字
au全角5,000文字
SoftBank300Kbyte

500Byteや384Byteという制限もありません。

デコメールやデコメ絵文字の受信にも対応しているものも多いです。

●その他

3Gの場合に、比較的対応している機能もあります。

・Flash

それぞれのキャリアでの対応するFlash Liteのバージョンは以下のとおりです。

DoCoMo1.0 / 1.1
900iシリーズのみ1.0、以降は1.1
au1.1 / 2.0
WIN端末では比較的対応しています。
SoftBank1.1 / 2.0
50%ぐらい対応しているようです。

・動画

対応機種はMPEG-4に対応しています。

DoCoMo3GPP
auAMC / 3GPP2
SoftBank3GPP

MNG,Nancyなどはありません。

と、3Gに限定することにより、かなり差異が少なく、敷居も低くなっています。 携帯用のサイトを作成したことがない方は、ここから手をつけてみてはいかがでしょうか。

2007年7月10日

TilePlex脆弱性発見と対応につきまして
このエントリーをブックマークに追加 このエントリーをlivedoorクリップに追加

ウノウ株式会社代表取締役社長/山田進太郎です。

このたびベータ版としてウノウラボで公開しておりましたTilePlexにおきまして、ユーザー登録されましたユーザー様のメールアドレスなどが取得できる状態になっていましたことをご報告し、深くお詫び申し上げます。大変申し訳ありませんでした。

詳細な経緯と今後の対応などをウノウ・ホームページの方に掲載いたしましたので、ご覧ください。

2007年7月 6日

メンテナンス画面を簡単に出してみる
このエントリーをブックマークに追加 このエントリーをlivedoorクリップに追加

カレーはあまり好きじゃないKeitaです。
映画サイトの映画生活のデザインリニューアルをして、いくつかデザイン以外の修正もあり、サーバ停止時間が発生するため、メンテナンス画面を作ることにしました。今日はその簡単なトピックスを書いてみたいと思います。
まず最初に、メンテナンス画面は次のような形の要件があるかなと思っています。
  • ドメイン以下すべてがメンテナンス画面になる
  • クローラー対策でヘッダで503を出力する
  • 癒される
  • 特にクローラー対策は、クローラーがきておかしいものをキャッシュされると結構痛いかなと思うので、503が的確かはともかく、そこらへんのエラーを出すことにしました。

    最初、ここら辺のすべての処理をmod_rewriteだけで実現できるかなと思ったのですが、残念ながら、mod_rewriteでは300番系のエラーを出すことができますが、503のエラーは出せないようなのでさくっと簡単なものを作るの「には」便利とかいわれてるPHPで、さくっと、503を出すことにしました。(もともと、映画生活がPHPなので深く考える必要なかったというのが理由です。)

    まず、いろいろ考えるのも面倒なので、mod_rewriteをこんな感じに設定します。
    RewriteEngine on
    RewriteRule ^503/ - [L]
    RewriteRule ^.*$ 503/503.php
    
    これで、設定されているディレクトリ以下が503ディレクトリ以外全部 /503/503.phpに飛ばされます。

    503.phpには次のように書きました。
    <?php
        header ('HTTP/1.0 503 Service Temporarily Unavailable');
        include(dirname(__FILE__) . '/maintenance.html');
    

    これで、503を出力しつつmaintenance.htmlを表示します。
    これで、すべてのアクセスでメンテナンス画面を出力することができます。

    maintenance.htmlにPHPコードがかいてあると、それが実行されてしましまうので、file_get_contetnsfile_get_contentsとか使うほうがいいかもしれません。

    あと、maintenance.html内に、画像など外部リソースのリンクを張るときは、503ディレクトリに入れるのとそれをフルパス記載することわすれないように注意してください。

    それでは実際に使われたメンテナンス画像をごらんください。
    maintenance
    maintenance posted by (C)フォト蔵
    なんらかのご参考になれば幸いとおもいつつ、もっとうまい方法があるような気がするのでもしよろしければ、フィードバックをいだければと思います。

    2007年7月 5日

    Cactiによるサーバ稼働状況の監視と異常通知
    このエントリーをブックマークに追加 このエントリーをlivedoorクリップに追加

    こんにちわ。
    7月2日に、エイガでつながるクチコミサイト「映画生活」のリニューアルが無事に終わってほっとしているhideです。近々、映画APIなども公開していきたいと思っていますので、どうぞよろしくお願い致します。

    さて、今日は、Cactiを使ったサーバ稼働状況の監視について説明したいと思います。サーバの稼動状況を監視するツールとしてはMRTGが有名ですが、ウノウではCacti を使っています。MRTGと比べて、Webブラウザ上から設定を行うことができる、データはMySQLに保存されるので過去の任意の時点を詳しく見ることができるなどの点で優れています。

    Cacti
    Cacti posted by (C)フォト蔵

     

    Cactiは普通にインストールしただけだとサーバから取得したデータをグラフ表示するだけなのですが、http://cactiusers.org/にて公開されている Cacti Plugin Architecture というパッチを当てるとプラグイン機構が有効になります。これにより、監視している値が閾値を超えた時に管理者にメール通知するなどの機能を追加することができます。

    インストール方法は、パッチを当てる方法とプログラムを入れ替える方法がありますが、後者のほうが簡単かと思います。プラグインのインストールは、cactiのpluginディレクトリにダウンロードしたファイルを解凍します。そして、include/config.phpを編集してプラグインを有効にします(下記参照)。

    $plugins = array();
    $plugins[] = 'thold';
    $plugins[] = 'monitor';

    現時点で利用可能なプラグインはそんなに多くないのですが、Tholdはかなり使えると思います。以下にいくつかプラグインを紹介します。

    Thold 監視している値が閾値を超えた時に管理者にメール通知
    Monitor 機器のUp/Downステータスを監視
    Reports 定期的にグラフレポートを送信
    Tools snmpwalk, HTTPやPOP3などのサービス稼働チェック
    Update Cactiおよびプラグインのアップデート情報を取得
    Discovery ネットワーク内のノード自動検出
    NTop ntopコマンドの結果を表示
    Syslog syslog監視機能

    また、CactiはSNMP以外にもテンプレートを自分で用意することで、様々なデータを監視することができます。Cacti Scripts
    and Templates
    にいろいろなテンプレートが紹介されているので参考にされると良いかと思います。
    それでは、皆さん良い監視ライフを!


    参考リンク:

    2007年7月 4日

    商品を”つなげる”サイト TilePlexβオープン
    このエントリーをブックマークに追加 このエントリーをlivedoorクリップに追加

    komagataです。

    本日、TilePlexという新しいサイトをオープン(β版)しました。

    TilePlexはある商品と関連する商品をひたすらつなげていく不思議なサービスです。ユーザー登録不要で追加していけますので是非試してみてください。

    Screenshot-TilePlex%20-%20Mozilla%20Firefox.png

    また、Web APIも同時に公開されていますので面白いアイデアを思いついて何か作ってしまった方は100%見に行くのでトラックバック等いただければ幸いです。
    (トップページのFlashもこのAPIのみを使って作られています。)

    TilePlex:
    http://tileplex.jp

    TilePlex API:
    http://tileplex.jp/top/api