Skip to content

Instantly share code, notes, and snippets.

@gardejo
Created May 12, 2010 14:18
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save gardejo/398635 to your computer and use it in GitHub Desktop.
Save gardejo/398635 to your computer and use it in GitHub Desktop.
PSGI-Doc-JA (trial)

README For PSGI-Doc-JA

(English follows Japanese)

PSGI-Doc-JAはウェブサーバーとPerlベースのウェブアプリケーションとのインターフェースであるPSGI仕様に関して日本語訳を提供するためのプロジェクトです。

I. 翻訳の入手

PSGI-Doc-JAはgitで管理されているので、誰でもチェックアウト可能です。 現在はgithub上にて公開されています。

<del>http://github.com/jpa/PSGI-Doc-JA</del>

註: 現在はJPA(Japan Perl Association)のリポジトリではなく、翻訳協力者の個人リポジトリにて公開されています。

http://github.com/gardejo/PSGI-Doc-JA

II. 追加修正

誤訳等による修正や、新バージョンによる追加はgithubのpull request等を通して連絡してください。

META.ymlファイルにバージョンを指定するのを忘れないでください。このファイルにバージョン情報がないと正しくデータが出力されません。Perl組み込み(コアモジュール・ドキュメント)に関しては perl-X.XXXのように同梱されているPerlのバージョンを指定してください。

また、文章はすべてUTF-8で統一してください。

III. 翻訳協力者(敬称略)

守屋雅樹 (gardejo)

IV. PSGIディストリビューションのREADME.md

これはPythonのWSGIやRubyのRack風のPSGI(Perl Web Server Gateway Interface)です。

PSGIプロトコル仕様(英語)は現在草案の段階にあります。私達はこれを最終版にする前にフィードバックを審査しているところです。FAQも参照してください。

さらなるリンクはPSGI/Plackウェブサイト(英語)を参照してください。

README For PSGI-Doc-JA

PSGI-Doc-JA is a project to translate PSGI documents in Japanese.

I. Where to get the source

PSGI-Doc-JA is currently hosted on github. Anybody can clone it:

<del>http://github.com/jpa/PSGI-Doc-JA</del>

NOTE: PSGI-Doc-JA is not currently hosted on JPA (Japan Perl Association)'s repository, but instead on translator's personal repository.

http://github.com/gardejo/PSGI-Doc-JA

II. Updating the documentation

Whether by mistake, omission, or new updates, you can make your changes and ping us via github's pull request feature

You must specify the module's version number in a META.yml file.

When you do so, please use UTF-8 only, please.

III. Translators

Masaki Moriya (gardejo)

IV. README.md in PSGI distribution

This is PSGI, Perl Web Server Gateway Interface -- ala Python's WSGI and Ruby's Rack.

PSGI protocol specification is currently in draft. We're reviewing feedbacks before making it final. See also FAQ

See PSGI/Plack website for more links.

NAME / 名前

PSGI::FAQ - Frequently Asked Questions and answers / 頻出問答集

QUESTIONS / 質問

一般

PSGIはどのように発音するのですか?

私達はこれを単純にピー・エス・ジー・アイ(P-S-G-I)と読んでいます。

それで、これは何なのですか?

PSGIとは、ウェブサーバーと、perlに基づくウェブアプリケーションとの間のインターフェースであり、CGIがウェブサーバーとCGIスクリプトで行っていることと同種のものです。

どうしてこれが必要なのですか?

PerlにはCGIやmod_perlやFastCGIの差異を幾分か抽象化するコアモジュールとしてCGIがあります。しかし、多くのウェブアプリケーションフレームワーク(例えばCatalystやJifty)の開発者は、性能を最大化したり、低水準のAPIにアクセスするために、このモジュールを使うことを避けています。結局その人達は、それらの異なる環境の全てに対応させるためのアダプターを書いていますが、あるものは十分にテストされている一方で、そうでないものもあります。

PSGIによって、ウェブアプリケーションフレームワークの開発者は、PSGIのためのアダプターを書くだけで済むようになります。エンドユーザーは、PSGIインターフェースに対応する全てのバックエンドを選ぶことができます。

PSGIはとCGIと同類だそうですね。PSGIインターフェースはCGIとどのように違うのですか?

PSGIインターフェースは、CGIがとても簡単になることに加え、PSGIに対応するために、CGIにとてもよく似るように意図的に設計されています。CGIとPSGIの主な違いの見所は、以下の通りです:

  • CGIにおけるサーバーとは、何かの言語(ただし大抵はC)によって記述された実際のウェブサーバーであり、スクリプトとは、CやPerlやシェルスクリプトやRubyやPythonのような何かの言語で記述されたものです。

    PSGIにおけるサーバーもウェブサーバーではありますが、それらはperlのプロセスであり、普通は(mod_perlのように)ウェブサーバーに組み込まれているものか、(FastCGIのように)ウェブサーバーから呼ばれるperlのデーモンプロセスであるか、完全にperlに基づくウェブサーバーです。そして、PSGIアプリケーションとは、perlのコードリファレンスです。

  • CGIにおいては、パラメーターとHTTPリクエストボディーを読み取り、アプリケーションからのエラーを送信するために、標準出力と標準エラー出力と環境変数を使います。

    PSGIにおいては、サーバーとアプリケーションとの間でデータを受け渡すために、$envハッシュリファレンスとpsgi.inputストリームとpsgi.errorストリームを使います。

  • CGIにおけるアプリケーションとは、HTTPヘッダーとボディーを標準出力へ印字し、それをウェブサーバーに対して受け戻すものだとされています。

    PSGIにおけるアプリケーションとは、HTTPステータスコードと、ヘッダーと、(配列リファレンスまたはファイルハンドルのようなオブジェクトとしての)ボディーを、配列リファレンスとしてアプリケーションへ返すものだとされています。

私のフレームワークは既にCGIとFSGIとmod_perlで動かせます。どうしてPSGIへ対応させたがるのでしょうか?

もしあなたのウェブアプリケーションフレームワークが、既に多くのサーバー環境に対応していて、性能が良くて、バックエンドがよくテストされているならば、PSGIバックエンドと重複している何かのコードを削除することによってPSGIに直ちに対応しても、直接的な利益はないでしょう。しかし現在CGI環境のみ対応しているならば、とても簡単にPSGIに対応することで、アプリケーションをPSGI互換サーバーで動かせるようになり、plackupPlack::Testや多くのPlack::Middlewareコンポーネントのようなユーティリティーを使えるようになるので、あなたとあなたのフレームワークのユーザーのためになるでしょう。

私はウェブアプリケーションを書いています。PSGIは私にとってどのような利益がありますか?

あなたがお使いのフレームワークがPSGIに対応しているということは、あなたのアプリケーションが既存およびこれから生まれるPSGI実装のどこでも動かせることを意味しています。あなたは、PSGIアプリケーションを返す.psgiファイルを提供することができ、それによって、あなたのアプリケーションのエンドユーザーは、あなたのアプリケーションを色々な別の方法によって構成・実行できるはずです。

でも、私はCGIで動くウェブアプリケーションを書いていますが、それは満足に動いていますよ。PSGIに切り替える必要があるのですか?

もしあなたが生のCGI.pmを使って、いずれのウェブフレームワークも使わずにウェブアプリケーションを書いているならば、あなたのアプリケーションは生のCGI環境や、mod_perlや、FastCGIなどでの稼働に制限されます。あなた一人があなたのアプリケーションの開発者でありユーザーであるならば、たぶんそれでもいいでしょう。

しかしいつか、あなたのアプリケーションを顧客のために共有ホスティング環境に配備したいと思ったとき、またはCGIスクリプトとしてではなくスタンドアローンモードのサーバーで動かしたいと思ったとき、またはオープンソースソフトウェアとして配布したいと思うことがあるかも知れません。CGI.pmを使っているあなたのアプリケーションがCGI環境のみに制限されていると、痛い目を見るでしょう。

あなたはPSGIを意識した将来性のあるアプリケーションを作るために、PSGI互換のフレームワーク(フルスタックのものでも、小規模のものでも)の一つか、フレームワークがお嫌いであればPlack::Requestを使い始めることができます。

たとえ現在PSGIを知らなくて、生のCGIで動くアプリケーションを書いていたとしても、CGI::PSGIラッパーを使うことで、今後いつでもPSGIに切り替えることができます。

PSGIに対応するためにはどうすればいいのですか?

あなたがウェブサーバーの開発者ならば、PSGIアプリケーションを呼ぶPSGI実装を書いてください。または、PSGIツールキットおよびユーティリティーであるPlackの開発に参画し、あなたのウェブサーバーのためのサーバーアダプターを加えてください。

あなたがウェブアプリケーションフレームワークの開発者ならば、PSGI用のアダプターを書いてください。これで、サーバー環境の全ての差異に対応させる必要がなくなります。

あなたがウェブアプリケーションの開発者(またはウェブアプリケーションフレームワークのユーザー)であれば、PSGIに対応しているフレームワークを選ぶか、またはPSGIへ対応してくれないかとフレームワークの作者に訊ねてください :) あなたのアプリケーションが大規模なアプリケーション(例えばWebGUIやMovable Type)であるならば、あなたはPSGIの観点ではフレームワーク開発者であるものとして考えられるので、既存のフレームワークを使わないでください。つまり、あなたのアプリケーションでPSGI向けのアダプターを書く方が理に適っています。

PSGIは(私のフレームワークよりも)高速ですか?

繰り返しますが、PSGIは実装ではありません。ただ、とても高速なPSGI実装が生まれる可能性があります。全てを事前ロードしていて、XSパーザーによって事前フォークされていて、sendfile(2)カーネルコールを使うような、十分に最適化されたコードを実行するスタンドアローン。Cで書かれ、PSGIに対応するperlが組み込まれた、イベント駆動型の極小ウェブサーバー。どのモジュールも全くロードせず、多量のメモリーを消費せずに、CGI環境下で相当早く動く、旧来のCGI.pmに基づくバックエンドなどです。

HTTP::Server::PSGI::PreforkやCoroに基づく、とても高速なPSGIサーバーが既に存在していますし、それらにはPlack用のアダプターが含まれているので、plackupユーティリティーを使って実行することもできます。

あなたのフレームワークのユーザーは、必要に応じて最適なバックエンドを選べます。ウェブアプリケーションフレームワーク開発者としてのあなたは、それぞれ異なる要求を持っている様々なユーザーのことを考える必要がありません。

Plack

Plackとは何ですか?PSGIとPlackの違いは何ですか?

PSGIは仕様であり、ソフトウェアやモジュールからPSGIが呼ばれるわけではありません。エンドユーザーは、PSGIアプリケーションを動かすために、PSGIサーバー実装のどれかを選ぶでしょう。Plackとは、PSGIのユーティリティーの集合体であり、PSGIのリファレンスサーバー実装であるHTTP::Server::PSGIに加え、CGIとFastCGIとmod_perl向けのウェブサーバーアダプターも含んでいます。

Plackには、PSGI上の便利なAPIとヘルパーもあります。リクエストオブジェクトにおける行儀のよいオブジェクト指向APIを提供するためのPlack::Requestのようなものや、app.psgiを使ってコマンドラインからPSGIアプリケーションを実行したり環境設定するplackup(RackのRackupのようなものです)や、標準的なHTTP::RequestHTTP::Responseの組を使ってモックのHTTPまたはHTTPサーバーによるアプリケーションをテストできるPlack::Testなどです。詳細はPlackを参照してください。

どのような種類のサーバーバックエンドがありますか?

Plackの場合、私達はApache2などの多くのウェブサーバーへ既に対応していて、標準的なCGIやFastCGIに対応しているものもありますが、さらにPerlbalやnginxのようなperlを組み込める特別なウェブサーバーへの対応も試みているところです。Apacheモジュールであるmod_perliteやGoogle AppEngineまでもがPSGIに対応してくれて、PSGI/Plackに基づくアプリケーションをクラウドで動かせるようになれば、とてもいいと思っています。

RubyにはRackがあり、JavaScriptにはJackがあります。これをPackと呼ばないのはなぜですか?

Packは確かに申し分なく魅力的な名前ですが、Perlには組み込み関数としてpackがあるので、とりわけ書き物ではなく会話の際に若干の混乱を引き起こします。

PSGIに対応する実装の名前空間は何にすべきですか?

PSGIバックエンドやアダプターを実装するために、PSGI::名前空間を使わないでください

PSGI名前空間は、PSGI仕様と、実装者が合格するためのリファレンス単体テストのために予約されています。それは特定の実装によって使われるべきではありません。

もしCamperという(架空の)ウェブアプリケーションフレームワークのためにPSGIへ対応させるためのプラグインか拡張を書く場合、そのコードの名前はCamper::Engine::PSGIのようになるでしょう。

もしPSGIインターフェースに対応するウェブサーバーを書く場合、あなたが望む名前を何でも付けてください。あなたはPlack::Handlerの抽象インターフェースへ任意に対応するか、それ用のアダプターを以下のように書くこともできます:

my $server = Plack::Handler::FooBar->new(%opt);
$server->run($app);

あなたのサーバーでこのnewrunのメソッドに対応すると、それはplackup互換になり、ユーザーがあなたのアプリケーションを<plackup>コマンドで実行できるようになります。このAPIに従うことは推奨されますが、PSGIアプリケーションのローンチャーを自分自身で提供する場合には、必須ではありません。

PSGI/Plackで動かしたい、CGIやmod_perl向けのアプリケーションがあります。どうすればいいですか?

いくつかの方法があります:

CGI::PSGI

クエリーパラメーターをハンドルするためにCGI.pmを使っているウェブアプリケーション(またはフレームワーク)であれば、CGI::PSGIがPSGIへの移行を手助けできます。あなたは、CGIオブジェクトの作成方法や、レスポンスヘッダーおよびボディーの返却方法を変更しなければなりませんが、残りのコードは変更なしに動くでしょう。

CGI::Emulate::PSGIとCGI::Compile

必要最小限の変更にとどめたい(または全く変更したくない)枯れたCGIスクリプトであれば、CGI::Emulate::PSGICGI::Compileがそれをコンパイルし、PSGIアプリケーションとして包み込んでくれます。

CGI::PSGIと比較すると、標準出力と標準エラー出力をキャプチャーして環境変数を切り刻んでいるために効率が悪いかも知れませんが、CGI.pmのみならずどのようなCGI実装を使っても動くはずですし、CGI::CompileはCGIスクリプトをmod_perlのレジストリーと同様にコードリファレンスに変換する役目を果たしてくれるはずです。

Plack::RequestとPlack::Response

HTTP::Engineに基づくアプリケーション(フレームワーク)である場合や、アプリケーションを一から書いてCGIより優れたインターフェースを必要とする場合や、Apache::Requestを使っている場合には、Plack::RequestPlack::Responseがお望みのものかも知れません。それは、PSGI環境ハッシュとレスポンス配列を巧く処理する、行儀のよいリクエストおよびレスポンスのオブジェクトAPIをもたらしてくれます。

注意: 一度だけ実行されて終了するというCGIスクリプトを持っていて、それを永続化プロセスに切り替えるときは、再設定される必要がある変数や、閉じられるか削除される必要があるファイルなどの、リクエスト後に都度起こす必要のあるものが一掃されるかもしれないということを忘れないでください。PSGIは、ここで親切な注意を行うことを除いて、この問題については手の打ちようがありません(あなたがそれを修正する必要があります)。

HTTP::Engine

なぜHTTP::EngineではなくてPSGI/Plackなのですか?

HTTP::Engineは偉大な実験でしたが、アプリケーションインターフェース(request_handlerインターフェース)と実装が混ざっていましたし、一枚岩のクラス階層と役割に基づくインターフェースは、新たなバックエンドを記述するのには大きな困難を生じさせました。私達はHTTP::Engineの存在は保ったままで、これを3つの部分に割りました: インターフェース仕様(PSGI)と、リファレンスのサーバー実装(Plack::Server)と、標準的なAPIおよびツール(Plack)です。

HTTP::Engineは使われなくなるのですか?

使われなくなることはないでしょう。HTTP::Engineは現在の状態のままでいるでしょうし、フレームワークではなく極小のウェブサーバーアプリケーションを書きたい場合には、有用なままでもいるでしょう。

PSGIインターフェースへ適合させるために、私のHTTP::Engineアプリケーションを書き直す必要がありますか?

いいえ、既存のHTTP::Engineアプリケーションを書き直す必要はありません。HTTP::Engine::Interface::PSGIを使うことで、PSGIアプリケーションへ簡単に切り替えることができます。

あるいは、HTTP::Engine::RequestHTTP::Engine::ResponseのAPIと互換性のあるPlack::RequestPlack::Responseを使うことができます:

use Plack::Request;
use Plack::Response;

sub request_handler {
    my $req = Plack::Request->new(shift);
    my $res = Plack::Response->new;
    # ...
    return $res->finalize;
}

これで、request_handlerがPSGIアプリケーションになります。

私のHTTP::EngineアプリケーションをPSGIで動くように改造することの利点は何ですか?

今日では、Plackによる多くのウェブサーバー実装とミドルウェア実装が、HTTP::Engineと同様に大抵は使えますので、PSGIへ切り替えることの直接的な利益をご案内できないかも知れません。しかし、PSGIはさらに将来が保証されています。そして、とても高速なサーバー実装(RubyのRackのためのPassengerを思い起こしてください)や、今日HTTP::Enginが備えていない多くの便利なミドルウェアを、私達が近い将来にも手に入れられるであろうと、大いに期待されています。

詳細は「私のフレームワークは既にCGIとFCGIとmod_perlで動かせます。どうしてPSGIに対応させたいのですか?」の質問を参照してください。

API設計

PSGI仕様において選ばれた多くの設計は、バックエンドの必要条件を最小化するためのものであり、それによって色々な物事を最適化できていることに留意してください。PSGIレイヤーにおいて凝ったインターフェースを追加したり、柔軟性を許容すると、エンドユーザーに対しての口当たりはよくなるでしょうけれども、バックエンドがそれに対応する必要があるので、それは結局最適化の妨げになったり、さらなるバグをもたらしてしまうでしょう。凝ったAPIを作ってウェブアプリケーション開発者を魅惑するのはあなたのフレームワークであって、PSGIではありません。

APIがオブジェクトではなく巨大な環境ハッシュであるのはどうしてですか?

インターフェースの簡潔性は、WSGIとRackを成功させる鍵となっています。PSGIは、バックエンドとウェブアプリケーション開発者との間にある低水準のインターフェースです。もし私達が、どのオブジェクトが認められるか、どんなメソッドが実装されなければならないか、などとAPIを定義したら、バックエンドでは多くの重複したコードが生まれてしまうでしょうし、そのうちいくつかはバグが多くなってしまうでしょう。

例えば、PSGIは$env->{REMOTE_ADDR}を文字列として定義しています。それでは、PSGI仕様をNet::IPのインスタンスにするためには何が必要なのでしょうか。バックエンドのコードは、通常ならNet::IPモジュールに依存させるところですが、Net::IPの全てのメソッドを実装するモックオブジェクトを書きます。特定のモジュールへバックエンドが依存していることや、たくさんのものを再発明することは有害であると考えられますので、インターフェースは必要最小限のものになっています。

エンドユーザーのために行儀のよいAPIを作成することはウェブアプリケーションフレームワーク(アダプター開発者)の仕事であるべきで、PSGIの仕事ではありません。

アプリケーションが->callメソッドを持つオブジェクトではなくコードリファレンスであるのは何故ですか?

コードリファレンスに加えてオブジェクトを要求することは、いくらかのより冗長な行数を「すべての」バックエンドのコードへ作らせてしまいますし、さらに、コードリファレンスに加えてオブジェクトを要求することは、もう一つのクラスと、オブジェクトをインスタンス化するコードを、アプリケーション開発者に書かせてしまいます。

言い換えれば、callメソッドが動くオブジェクトはよいでしょうけれども、繰り返しますが、PSGIは可能な限り簡素になるように設計されています。そして、クラスやオブジェクトの中からコードリファレンスをこしらえることは簡単ですが、逆に、コードのいくらかの行数と、ことによると新たなファイルも常に要求してしまうのです。

ヘッダーがハッシュリファレンスではなく配列リファレンスを返すのはなぜですか?

端的には: 複合的なヘッダー(例えばSet-Cookie)をサポートするためです。

具体的には: PythonのWSGIにおいて、レスポンスヘッダーは(header_nameheader_valueから成る)タプル(tuples)、すなわちtype(response_headers) is ListTypeであり、これによって同一のヘッダーキーに複数のエントリーを持たせることができています。RackとJSGIにおいては、ヘッダー値は"\n"で区切られた行から成る文字列です。

私達はここではPythonの仕様を好んでいて、Perlのハッシュが(それがtieされているときを除いては)同一のキーによる複数のエントリーを認めていないために、[ key => value, key => value ]を格納するために配列リファレンスを使うことで、フレームワークのアダプターとバックエンドを簡素なままに保つための最も単純な解決策としています。配列リファレンスに加えて生のスカラー値を許容すると、(フレームワークのアダプターとバックエンドの)両側のコードに不必要な冗長さを生じさせる、というような意見もあります。

$bodyはイテレーターに対応していないのですか?

WSGIとRackが、Pythonのイテレート可能オブジェクトや、Rubyのイテレーターという、PythonとRubyの言語が持つ長所を実によく活用していることを、私達は学びました。

例えばRackでは、eachメソッドに応答してバッファーをもたらすというオブジェクトとして、ボディーを以下のように予期しています:

body.each { |buf| request.write(buf) }

このコードは、ボディーが配列であろうと、FileIOオブジェクトであるろうと、イテレーターを実装しているオブジェクトであろうと、正に魔法のように動きます。Perlには、autoboxがロードされていない限り、このような素晴らしいものは言語に備わっていません。PSGIはautoboxを前提モジュールにすべきでないので、私達は単純な配列リファレンスかファイルハンドルのみに対応させました。

IO::Handleのようなオブジェクトを書くことは、getlinecloseを実装するだけなので、とても簡単です。少し不安定だとみなされるかも知れませんが、Considerファイルハンドルのように振る舞うオブジェクトを書くために、PerlIOを使うこともできます。

イテレーターのような何かをIO::Handleのようなものに変える方法については、IO::Handle::Utilも参照してください。

sendfile(2)に基づくサービスへ切り替えることを、サーバーはどのように判断したらよいでしょうか?

まず第一に、アプリケーションは常に、getlinecloseに応答するIO::Handleのようなオブジェクト(または大量の配列)をボディーへセットする「べき」です。そうすれば、どのサーバーでも確実に働きます。

もしサーバーがperlで書かれているか、ファイル供給用としてファイルデスクリプター番号(※訳註: UNIX系ファイルシステムにおけるi-node番号などのこと)を「C-land」(※訳註: 先生、わかりません! キリッ 「C(言語)の世界(つまりCで書かれたウェブサーバ然りOSのこと)」?)へ知らせることができるならば、ボディーが真のファイルハンドルであるかを、サーバーは任意に検査「できます」(ひょっとしたらPlack::Utilis_real_fh関数を使えるかも知れません)し、そうすれば、その検査結果を使って、filenoとsendfile(2)の呼び出しによるか、ゼロコピーデータ通信(※訳註: ユーザーメモリーからカーネルメモリーへのコピーを行わない、高速な通信方法のこと)相当のものによって、ファイルデスクリプターを取得できるでしょう。

そうでない場合で、サーバーがファイルデスクリプターを使ったファイルを送信できないのに、ローカルファイルのパスを必要としている場合(mod_perlやnginxのような場合)は、アプリケーションはpathメソッドに応答するIO::Handleのようなオブジェクトを返せます。この種のIO的オブジェクトは、IO::File::WithPathや、IO::Handle::Utilや、Plack::Utilset_io_path関数を使って、簡単に作れます。

ミドルウェアは、ボディーがpathメソッドを備えていて、かつ、何かができるか(X-Sendfileヘッダーが設定されているか否かなど)を、確認することもできます。

要約すると:

  • 静的ファイルをサービスするとき、アプリケーションは常に真のファイルハンドルかIO::Handleオブジェクトを返すべきです。これで、どこでも動くようになりますし、いくつかの環境では最適化できるでしょう。

  • アプリケーションは、pathメソッドを加えたIO::Handleのようなオブジェクトを設定することもでき、それによって、どんな場所でもアプリケーションを実行し直せるはずですし、さらに多くの環境で最適化できます。

long-poll実装によるComet送信をするか、コンテントをストリーミングしたい場合は、どうすればいいですか?

サーバープッシュを実装するための最も簡単な方法は、プッシュ送信されるコンテントを返すために、あなたのアプリケーションに、getlineを実装するIO::Handleのようなオブジェクトをコンテントボディーとして返し続けさせることです。そうすればどこでも動きますけれども、それはプッシュ(push)というよりはプル(pull)であるのに過ぎず、Coroに見られるようなものを除いては、非ブロッキングIOをすることが難しいです。

アプリケーションをイベントループ内で実行し、クライアントの準備が整い次第コンテントボディーをプッシュするというサーバープッシュを行いたいならば、レスポンスを遅延させるためにコールバックを返すべきです。

# long-poll Cometのようなチャットアプリケーション
my $app = sub {
    my $env = shift;
    unless ($env->{'psgi.streaming'}) {
        die "This application needs psgi.streaming support";
    }
    return sub {
        my $respond = shift;
        wait_for_new_message(sub {
            my $message = shift;
            my $body = [ $message->to_json ];
            $respond->([200, ['Content-Type', 'application/json'], $body]);
        });
    };
};

wait_for_new_messageは、ブロッキング通信であることも、非ブロッキング通信であることも可能です: どうするかはあなた次第です。非ブロッキング通信で実行しようと望む多くの場合、AnyEventのようなイベントループを使うべきです。非ブロッキング通信できるか、そうでなければブロッキングコールにフォールバックすることができるかを調べるため、psgi.nonblockingの値を検査することもできます。

さらに、(FlashソケットやマルチパートXMLHTTPRequestを介してメッセージをストリーミングするように)コンテントボディーをストリーミングする場合は、以下のようにしてください:

my $app = sub {
    my $env = shift;
    unless ($env->{'psgi.streaming'}) {
        die "This application needs psgi.streaming support";
    }
    return sub {
        my $respond = shift;
        my $writer = $respond->([200, ['Content-Type', 'text/plain']]);
        wait_for_new_message(sub {
            my $message = shift;
            if ($message) {
                $writer->write($message->to_json);
            } else {
                $writer->close;
            }
        });
    };
};

ストリーミングするために、どのフレームワークを使うべきですか?

非ブロッキング通信に対応するサーバー(psgi.nonblockingが真に設定されているもの)がありますが、フレームワーク側では非同期イベントループ(asynchronous event loop)に対応している必要がないことが問題です。例えばCatalystにはレスポンスオブジェクトにwriteメソッドがあります:

while ($cond) {
    $c->res->write($some_stuff);
}

これは、たとえブロッキング通信されているものであっても、マルチプロセス(psgi.multiprocessが真である)のものであっても、psgi.streamingに対応していれば全てのサーバーで動くでしょう。

getlineに対応するIO::Handleのようなオブジェクトにも、Catalyst::Engine::PSGIは対応していますので、IO::Handle::Utilを使うこともできます:

my $io = io_from_getline sub {
     return $data; # または、done()の場合にはundef
};
$c->res->body($io);

これでストリーミングとして満足に動かすことができますが、これは非同期サーバープッシュ(asynchronous server push)ではなくブロッキング通信(プル(pull))であるので、あなたはこのアプリケーションを非ブロッキング(および非マルチプロセス)サーバー環境で実行しないように、再度気を付けなければなりません。

非同期および非ブロッキングストリーミングインターフェースに焦点を当てた多くのウェブフレームワークが現れる可能性や、既存のフレームワークがこれに対応する可能性があります。

psgi.streamingインターフェースはサーバーにとって必須ですか?

このインターフェースを実装しない強い理由がない限りは、それは記される「べき」で、全てのサーバーはこのインターフェースを実装することが推奨されます。

しかし、究極の性能を追求するためにPerl XSインターフェースを使ったたり、Apacheやnginxのようなウェブサーバーと一体化させたりするというPSGIサーバーを実装する場合や、あるいは(Google AppEngineやHerokuのような)サンドボックス的な環境(※訳註: 実行権限が制約されている環境のこと)や、Gearmanのようなツールを使ったを分散プラットフォームを実装する場合には、このインターフェースを実装すべきではありません。

それでいいのです。そうした事例では、ストリーミングインターフェースに頼っているアプリケーションは、バッファリングした出力を非対応のサーバーに対して行う処理へフォールバックするために、Plack::Middleware::BufferedStreamingを相変わらず使えるのですから。

ハッシュとしてのHTTPヘッダーではなくCGIスタイルの環境変数を使うのはなぜですか?

多くの既存のウェブアプリケーションフレームワークには、CGI環境下で実行するためのコードやハンドラーが既に備わっています。HTTPヘッダーの代わりにCGIスタイルのハッシュキーを使うことで、PSGIをサポートするためのアダプターをフレームワークの開発者がより簡単に実装することができます。例えば、Catalyst::Engine::PSGICatalyst::Engine::CGIと数十行だけしか違わないですし、それは1時間未満で書かれたものです。

どうしてPATH_INFOのURIはデコードされないのですか?

CGI仕様(RFC 3875)および多くの(Apacheやlighttpdのような)ウェブサーバーの実装との互換性を保つためです。

トレイリングパス(trailing path)でfoo%2fbarfoo/barを見分けることが不便であることは理解していますが、CGI仕様では「PATH_INFOはサーバーによってデコードされるべきであり、ウェブサーバーは%2fを含んでいるリクエストなどを拒絶できる(そのようなリクエストはPATH_INFOで情報が失われるであろうため)」と明確に規定されています。これらのデコードされていない(または部分的にデコードされている)予約文字をそのままにしておくと、foo%252fbarfoo%2fbarを区別できず、二重エンコードまたは二重デコードによるセキュリティーホールになりえますので、事態が悪化してしまうでしょう。

Catalystのような)ウェブアプリケーションフレームワークには、実際の生URIのさらなる制御が必要とされますので、私達はREQUEST_URI環境ハッシュキーを必須としています。サーバーはデコードされていない(解析されていない)本来の(クエリー文字列を含む)URIをこのキーにセットすべきです。REQUEST_URIは、たとえエンコードされた実体がURIとして使えるものであっても、完全に生の状態であることに注意してください。

ちなみに、WSGI (PEP-333)はSCRIPT_NAMEPATH_INFOの両方がデコードされていると定義していて、Rackはその定義を実装依存のものとしています。

http://www.python.org/dev/peps/pep-0333/#url-reconstruction http://groups.google.com/group/rack-devel/browse_thread/thread/ddf4622e69bea53f

SEE ALSO / 参考情報

WSGIのFAQは、APIをどのように設計するかということについての、たくさんの質問へ明快に答えていて、その内のいくつかはPSGIに直接適用することができます。

http://www.python.org/dev/peps/pep-0333/#questions-and-answers

MORE QUESTIONS? / 他に質問がありますか?

ここで回答されていない質問がありましたら、または、全く同意できない回答がありましたら、#plackというirc.perl.org上のIRCチャネルか、http://groups.google.com/group/psgi-plackというメーリングリストに参加してください。その際には、あなたがどちらの肩書きを持っているのかを明らかにしてください: アプリケーション開発者か、サーバー実装者か、ミドルウェア開発者か。そして、仕様に対して「ためにする批判」をしないでください: 仕様の制約によって動かない、または複雑になっている正確なコードを開示してください。私達は粗探しや「自転車置き場の議論」(※訳註: 些末な対象ほど紛糾してしまう議論のこと)には配慮しないでしょう。

AUTHOR / 作者

Tatsuhiko Miyagawa <miyagawa@bulknews.net>

COPYRIGHT AND LICENSE / 著作権とライセンス

※訳註: 原文にはこのセクションの内容が(見出しを除いて)記述されていません( http://twitter.com/gardejo/statuses/13853425119 )。ただし、PSGI.podと同様のものが適用されるという見解を宮川さんから頂戴しています( http://twitter.com/miyagawa/statuses/13865409851 )。

version: 1.03
distribution: PSGI

NAME / 名前

PSGI - Perl Web Server Gateway Interface Specification / Perlウェブサーバーゲートウェイインターフェース仕様

ABSTRACT / 摘要

この文書は、ウェブサーバーと、Perlウェブアプリケーションまたはフレームワークとの間の、標準的なインターフェースを規定するものです。このインターフェースは、ウェブアプリケーションの移植性を増進し、ウェブアプリケーションフレームワーク開発者による重複した取り組みを低減するために設計されています。

PSGIがさらにもう一つの(Yet Another)ウェブアプリケーションフレームワークでないことに留意してください。PSGIはウェブアプリケーションフレームワークのコードをウェブサーバー環境から切り離すための仕様です。PSGIはまた、ウェブアプリケーションのAPIでもありません。ウェブアプリケーション開発者(エンドユーザー)は、PSGIインターフェースを直接用いて自身のウェブアプリケーションを実行するのではなく、その代わりにPSGIに対応するフレームワークを使用することが推奨されます。

TERMINOLOGY / 用語

サーバー(Servers)

サーバー(Server)とは、HTTPリクエストを受け容れ、そのリクエストをウェブアプリケーションにディスパッチし、HTTPレスポンスをクライアントに返すウェブサーバーです。PSGI仕様の文脈において、サーバーとは、HTTPサーバー内部で実行されているPerlのプロセス(例えばApache内のmod_perl)や、ウェブサーバーから呼ばれるデーモンプロセス(例えばFastCGIデーモン)や、ピュアPerlなHTTPサーバーに該当します。

アプリケーション(Applications)

アプリケーション(Applications)とは、HTTPリクエストを受け容れ、HTTPレスポンスを返すウェブアプリケーションです。PSGI仕様において、アプリケーションとはコードリファレンスに該当します。

ミドルウェア(Middleware)

ミドルウェア(Middleware)とは、PSGIアプリケーション(コードリファレンス)であり、かつサーバー(Server)です。ミドルウェア(Middleware)は、サーバー(server)から呼ばれる際にはアプリケーション(application)のように見えて、他のアプリケーション(application)を順番に呼ぶことができます。これはPSGIアプリケーションを拡張するプラグイン(plugin)だと考えられます。

フレームワーク開発者(Framework developers)

フレームワーク開発者(Framework developers)とは、ウェブアプリケーションの作者です。その人達は、PSGIのインプットを受け取り、ウェブアプリケーションを実行し、サーバー(server)へPSGIレスポンスを返すアダプター(またはエンジン)を記述する必要があります。

ウェブアプリケーション開発者(Web application developers)

ウェブアプリケーション開発者(Web application developers)とは、ウェブアプリケーションフレームワーク上でコードを書く人達です。これらの開発者は、PSGIを直接取り扱う必要が全くありません。

SPECIFICATION / 仕様

アプリケーション(Application)

PSGIアプリケーションはPerlのコードリファレンスです。これはきっかり1つの引数を環境に引き渡し、きっかり3つの値が入った配列リファレンスを返します。

my $app = sub {
    my $env = shift;
    return [
        '200',
        [ 'Content-Type' => 'text/plain' ],
        [ "Hello World" ], # またはIO::Handleのようなオブジェクト
    ];
};

環境(Environment)

環境(environment)は以下で詳述するCGIのようなヘッダーを含んだハッシュリファレンスでなければ「なりません」。アプリケーション(application)は環境を変更する必要がありません。環境は、それらが正常に空である場合を除いて、(PEP 333RackJSGIから借用した)3つのキーを含んでいなければ「なりません」

値としてブール値を求める環境のキー(environment key)は、その値をPerlのブール値でない記法に合わせなければ「なりません」。これはつまり、空文字と明示的な0の両方ともに正当な偽値であることを意味します。ブール値のキーが存在しない場合、アプリケーションはこれを偽値であるものとして取り扱うでしょう

(ピリオドで終わる名前が付けられていない)全てのCGIキーの値は、スカラー文字列でなくては「なりません」

詳細は以下を参照してください。

  • REQUEST_METHOD: "GET"や"POST"のようなHTTPリクエストメソッドです。これは空文字であっては「いけません」し、常に必須とされます。

  • SCRIPT_NAME: アプリケーションに相当する、リクエストURLのパス(path)の開始部分です。これはアプリケーションに対して仮想的な"場所(location)"を知らせます。アプリケーションがサーバーのルートURIに相当する場合、これは空文字であるでしょう。

    このキーが空でない場合は、それはフォワードスラッシュ/で始まっていなければ「なりません」

  • PATH_INFO: リクエスト対象の仮想的な"場所(location)"をアプリケーションに対して明示する、リクエストURLのパス(path)のリマインダーです。リクエストURLがアプリケーションルートを指し示していて、かつ、トレイリングスラッシュ(※訳註: URLの末尾に付けるスラッシュのこと)がない場合、これは空文字であるでしょう。"/www.ietf.org/rfc/rfc3875" in RFC 3875http:に準拠するため、この値はサーバーによってURIデコードされているべきです。

    このキーが空でない場合は、それはフォワードスラッシュ/で始まっていなければ「なりません」

  • REQUEST_URI: デコードされていない、生のリクエストURL行です。 これはHTTPのGET /... HTTP/1.x行に現れている生のURIパスとクエリー部(query part)であり、URIスキーム(URI scheme)とホスト名(host name)は含んでいません。

    PATH_INFOと違って、この値はサーバーによってデコードされていては「いけません」PATH_INFOの代わりにこのキーを使うことを選んだならば、URLをアプリケーションハンドラーにマッピングするためにパスを適切にデコードするのは、アプリケーションの責務となります。

  • QUERY_STRING: リクエストURLの?があれば、それに後続する部分です。このキーは空で「ありえます」が、空であっても常に存在していなければ「なりません」

  • SERVER_NAME, SERVER_PORT: SCRIPT_NAMEPATH_INFOが兼ね備えられている場合、これらのキーはURLを完全なものにするために使えます。しかし、リクエストURLを再構築するためには、HTTP_HOSTが存在する場合にはSERVER_NAMEよりも優先して使うべきだということに留意してください。SERVER_NAMESERVER_PORT は空文字であっては「いけません」 し、常に必須とされます。

  • SERVER_PROTOCOL: クライアントからのリクエストの送信に用いられたプロトコルのバージョンです。一般的に、これは"HTTP/1.0"または"HTTP/1.1"のようなもので、アプリケーションがどのようにHTTPリクエストヘッダーを取り扱うかを決定するために用いられるでしょう。

  • CONTENT_LENGTH: 整数として表現される、コンテントのバイト長です。このキーの存否は、リクエスト内のHTTPのContent-Lengthヘッダーの存否に相当します。

  • CONTENT_TYPE: クライアントにより明示される、リクエストのMIME型です。このキーの存否は、リクエスト内のHTTPのContent-Typeヘッダーの存否に相当します。

  • HTTP_* キー群: これらのキーは、クライアントから供給されたHTTPリクエストヘッダーに相当します。これらのキーの存否は、リクエスト内の適切なHTTPのContent-Lengthヘッダーの存否に相当します。

    同一のキーを持つ複数のヘッダー行が存在する場合、サーバーは"/www.ietf.org/rfc/rfc2616" in RFC 2616http:に則って、それらが,によって結合されて1行として送られたかのように取り扱うでしょう。

この他に、PSGI環境は以下のPSGI特有のキーも含めなければ「なりません」:

  • psgi.version: このPSGIのバージョンを[1,0]のように表す、配列リファレンスです。第1の数字はメジャーバージョンで、第2の数字はマイナーバージョンです。

  • psgi.url_scheme: リクエストURLに依拠する、httpまたはhttpsという文字列です。

  • psgi.input: インプットストリーム(input stream)です。詳細は以下を参照してください。

  • psgi.errors: エラーストリーム(error stream)です。詳細は以下を参照してください。

  • psgi.multithread: アプリケーションが同一プロセスの他のスレッドから同時に呼び出される場合に真でなくては「ならず」、そうでない場合に偽であるという、ブール値です。

  • psgi.multiprocess: 同等のアプリケーションオブジェクトが他のプロセスから同時に呼び出される場合に真でなくては「ならず」、そうでない場合に偽であるという、ブール値です。

  • psgi.run_once: アプリケーションが、それが含まれるプロセスの一生の内、一度しか呼ばれないことをサーバーが予期している(が保証していない!)場合に真になるという、ブール値です。通常は、CGI(または同様のもの)に基づくサーバーでのみ真になります。

  • psgi.nonblocking: サーバーが非ブロッキングイベントループ(non-blocking event loop)でアプリケーションを呼び出している場合に真になるという、ブール値です。

  • psgi.streaming: サーバーがコールバックスタイルの遅延レスポンス(delayed response)とライターオブジェクト(writer object)に対応している場合に真になるという、ブール値です。

PSGI環境は、以下の追加の拡張キーを含めることが「できます」:これらは任意のものであるので、アプリケーションとミドルウェアコンポーネントは、 使用前に環境にこれらが存在していることを検査しなければ「なりません」

  • psgix.logger: メッセージをロギングするための、コードリファレンスです。このコードリファレンスは、ロギングされるメッセージを表すハッシュリファレンスとして、1つの引数を渡されます。このハッシュリファレンスは、最低でもlevelmessageの2つのキーを含んでいなければ「ならず」levelは以下の文字列の中の一つでなければ「なりません」: debug, warn, info, errorおよび fatalmessageはプレーンな文字列か、文字列化されたスカラー変数スカラー変数でなければ「なりません」

  • psgix.session: セッションデータを保存・復元するための、ハッシュリファレンスです。このハッシュリファレンスを更新する際には、ミドルウェアコンポーネントが永続されていなければ「なりません」し、成功しているリクエストが復元されなければ「なりません」。セッションデータの永続化と復元の方法と、リクエスト元のクライアントを同定する方法のいずれも、実装固有のものです。

    psgix.session.options: セッションデータの操作方法をリクエスト後にミドルウェアコンポーネントへ伝えるための、ハッシュリファレンスです。受け容れ可能なキー・値は、実装固有のものです。

サーバーまたはアプリケーションは、自身のデータを環境に格納することもできます。これらのキーは、最低でも1つのドットを含んでいなければ「なりません」し、一意な接頭辞が付けられていなければなりません。

psgi.接頭辞はPSGIコア仕様が使うために予約されており、psgix.接頭辞は公式にブレスされた拡張のために予約されています。これらの接頭辞は、他のサーバーやアプリケーションによって使われては「なりません」

環境は、HTTP_CONTENT_TYPEまたはHTTP_CONTENT_LENGTHと名付けられたキーを含んでは「なりません」

SCRIPT_NAME またはPATH_INFOの1つはセットされていなければなりません。REQUEST_URI/である場合、PATH_INFO/であるべきで、SCRIPT_NAMEは空であるべきです。SCRIPT_NAME/であっては 「いけません」 が、空であることは「ありえます」

インプットストリーム(Input Stream)

psgi.inputにあるインプットストリームは、HTTP POSTまたはPUTの生のデータをストリーミングする、IO::Handleのようなオブジェクトです。これがファイルハンドルである場合、バイナリーモードでオープンされていなければ「なりません」。インプットストリームは、readメソッドに応答しなければ「ならず」seekメソッドを実装しても「よい」です。

Perlの組み込みファイルハンドルまたはIO::Handleに基づくオブジェクトは、PSGIサーバー内でもあるがままに動くべきです。アプリケーション開発者は、このストリームの型やクラスを検査すべきでは「ありません」。その代わりに、readメソッドをこのオブジェクトでただ単に呼ぶ「べき」です。

アプリケーション開発者は、インプットストリームを読み込むために、Perlの組み込みread関数やイテレーター(<$fh>)を使う「べきではありません」。その代わりに、アプリケーション開発者は、ダックタイプが許可されているものとして、readをメソッドとして($fh->read)呼ぶべきです。

自身が触れられないアップストリームコードの内で組み込みのread()関数によってインプットストリームが使われることを知っているフレームワーク開発者は、この問題に対処するために、PerlIOまたはタイされたハンドルを使う「べき」です。

インプットストリームオブジェクトには、readメソッドが提供されていることが期待されます:

read
$input->read($buf, $len [, $offset ]);

実際に読み込んだ文字数を、ファイルの末尾であれば0を、エラーが発生していればundefを返します。

インプットストリームオブジェクトには、選択的なseekメソッドが実装されていることもあります。

seek
$input->seek($pos, $whence);

成功時には1を、そうでなければ0を返します。

メソッドの厳密な動作方法の詳細は、IO::Handleのドキュメンテーションをご覧ください。

エラーストリーム(Error Stream)

psgi.errors にあるエラーストリームは、エラーを印字するための、IO::Handleのようなオブジェクトです。エラーストリームは、printメソッドが実装されていなければなりません。

インプットストリームと同様に、Perlの組み込みファイルハンドルまたはIO::Handleに基づくオブジェクトは、PSGIサーバー内でもあるがままに動くべきです。アプリケーション開発者は、このストリームの型やクラスを検査「すべきであはありません」。その代わりに、printメソッドをこのオブジェクトでただ単に呼ぶ「べき」です。

print
$errors->print($error);

成功時に真を返します。

レスポンス(Response)

アプリケーションは、3要素の配列リファレンスか、遅延/ストリーミングレスポンスのためのコードリファレンスのいずれかによって、レスポンスを返さなければ「なりません」

レスポンスの配列リファレンスは、以下の要素から成り立ちます:

ステータス(Status)

HTTPステータスコードです。これは100以上の整数でなければ「なりません」 し、RFC 2616に記述されているHTTPステータスコードでなければ「なりません」

ヘッダー(Headers)

ヘッダーは、(ハッシュリファレンスではなく)キー/値のペアから成る配列リファレンス「でなくてはなりません」。これはつまり、偶数個の要素を含んでいなければ「ならない」ということを意味しています。

ヘッダーには、Statusと名付けられたキーと、改行文字を名前に含むキーのいずれも、含んでは「なりません」。(※訳註: onrはnorの誤記と思われます: http://twitter.com/gardejo/status/13853380264-_で終わるどのキーも含んでは「なりません」

全てのキーは、アルファベット文字とアラビア数字と_-のみで成り立っていなければ「なりません」。全てのキーは、文字で始まっていなければ「なりません」。ヘッダーの値はスカラー文字列でなくてはなりません。値の文字列は、ASCII chr(32)(ホワイトスペース)のためのものを除いては、ASCII chr(37)(※訳註: %のこと)を含んでは「なりません」

同一のキー名が配列リファレンス内で複数回出現した場合には、それらのヘッダー行はクライアントに対して分割されて送られなければ「なりません」(例えば、Set-Cookie行)。

コンテント型(Content-Type)

Status が1xxや204や304であるためにcontent typeが存在して「ならない」場合を除いて、Content-Typeは存在していなければ「なりません」

コンテント長(Content-Length)

Statusが1xxや204や304である場合には、Content-Lengthは存在していては「なりません」

ステータスが1xxや204や304であるか、Content-Lengthヘッダーが存在していない場合には、PSGIサーバーはボディー(Body)を参照してコンテント長を計算するでしょう。この値はその後、アプリケーションによって返されるヘッダーのリストによって、付け加えられることがあります。

ボディー(Body)

レスポンスボディーは、アプリケーションによって、配列リファレンスかハンドルのいずれかの形で返されなければ「なりません」

  • ボディーが配列リファレンスである場合、それはボディーを作成する行の配列に相当することが期待されます。

    my $body = [ "Hello\n", "World\n" ];

    配列リファレンスの各要素の末尾の改行文字が「必須ではない」ことに注意してください。サーバーは、クライアントへ対して、それぞれの要素をあるがままに書き込まなくては「ならず」、行が改行文字で終わっているか否かを気にするべきでは「ありません」

    1つの値から成る配列リファレンスは、正当です。つまり、[ $html ]は正当なレスポンスボディーです。

  • ボディーは、Perlの組み込みファイルハンドルか、IO::Handleのようなオブジェクトのいずれかのハンドルであることもできます。

    open my $body, "</path/to/file";
    open my $body, "<:via(SomePerlIO)", ...;
    my $body = IO::File->new("/path/to/file");
    
    my $body = SomeClass->new(); # getline()とclose()メソッドを実装しているモッククラス

    サーバーは、ボディーの型やクラスを検査すべきでは「ありません」 。その代わりに、ボディーをイテレートするためのgetlineと、それが完了したときのためのcloseを、ただ単に呼ぶべきです。

    サーバーは、filenoおよびScalar::Util::reftypeを使って、ボディーが真のファイルハンドルであるか否かを検査「してもよい」です。ボディーが真のファイルハンドルである場合、サーバーは最適化のためにsendfile(2)のような技巧を凝らすことが「できます」

    ボディーオブジェクトは、pathメソッドに応答することも「できます」。このメソッドは、サーバーによってアクセス可能なファイルを示すパスを返すことが期待されています。これにより、ファイルを受け持つファイルデスクリプター番号(※訳註: UNIX系ファイルシステムにおけるi-node番号などのこと)の代わりに、この情報をサーバーが使用できるようになります。

    サーバーは、getlineメソッドを用いて$bodyからコンテントを読み込む場合には、$/特殊変数へバッファーサイズをセットしなければ「なりません」(※訳註: $/は入力レコードセパレーターです。デフォルトは\nです。整数や、整数が入った変数へのリファレンスの場合には、当該バイト分を1レコードとして読み込みます)。これは、$/に整数へのリファレンス($/ = \8192)を設定することで行われます。

    ボディーのファイルハンドルがPerl組み込みのファイルハンドルであるIO::Handleオブジェクトである場合、この値が守られるでしょう。同じAPIを提供するオブジェクトは、上記と同様にこの特殊変数を守ることも「できます」が、そのような挙動は必須ではありません。

遅延レスポンス(Delayed Response)とストリーミングボディー(Streaming Body)

PSGIインターフェースにより、アプリケーションおよびサーバーは、上述の3要素の配列リファレンスの代わりに、コールバックスタイルのレスポンスを提供することもできます。これにより、遅延レスポンス(delayed response)と(サーバープッシュ型の)ストリーミングボディー(streaming body)を実現できます。

このインターフェースは、PSGIサーバーによって実装されていなければ「なりません」し、そのサーバーによってpsgi.streaming環境が真に設定されていなければ「なりません」

遅延レスポンスを有効にするためには、アプリケーションがレスポンスとしてコールバック関数を返さなければ「なりません」。アプリケーションは、psgi.streaming環境が真にセットされているかを検査「できます」し、真でなければ直接レスポンスへフォールバックさせることもできます。

このコールバックは、他のサブルーチンリファレンス(これは今後レスポンダー(responder)として参照されるようになります)をただ一つの引数として呼ばれるでしょう。レスポンダー(responder)は、標準的な3要素の配列リファレンスによるレスポンスを順番に呼ばなけばなりません。実例の最適な説明は以下の通りです:

my $app = sub {
    my $env = shift;

    # ネットワークからコンテントを取得している間、レスポンスを遅延させる
    return sub {
        my $responder = shift;

        fetch_content_from_server(sub {
            my $content = shift;
            $responder->([ 200, $headers, [ $content ] ]);
        });
    };
};

アプリケーションは、レスポンダー(responer)が呼ばれた場合、3番目の要素(ボディー)を無視「できます」。ボディーが無視される場合、レスポンダー(responder)は、writeメソッドとcloseメソッドを実装したさらにもう一つの(yet another)オブジェクトを返さなければ 「なりません」。これもまた実例が最もよい説明です。

my $app = sub {
    my $env = shift;

    # レスポンスが直ちに開始され、コンテントがストリーミングされる
    return sub {
        my $responder = shift;
        my $writer = $responder->([ 200, [ 'Content-Type', 'application/json' ]]);

        wait_for_events(sub {
            my $new_event = shift;
            if ($new_event) {
                $writer->write($new_event->as_json . "\n");
            } else {
                $writer->close;
            }
        });
    };
};

この遅延レスポンス(delayed response)とストリーミングAPIは、非ブロッキングI/O通信(non-blocking I/O)に基づくサーバーストリーミングや、ロングポール(long-poll)によるComet送信技術を実装したい場合に便利ですが、ブロッキングサーバーにおいて、バッファリングされていない書き込みを実装するために使うこともできます。

ミドルウェア(Middleware)

ミドルウェア(middleware)コンポーネントはPSGIアプリケーションを取得し、それを実行します。サーバーの観点からすれば、ミドルウェアコンポーネントはPSGIアプリケーションに該当します。ミドルウェアコンポーネントによって起動されるアプリケーションの観点からすれば、ミドルウェアはサーバーに該当します。これらは一般的に、PSGI環境ハッシュ上での幾つかの種類の前処理や、レスポンスの後処理を実装するために行われます。

以下に、PSGIアプリケーションに対して特別なHTTPヘッダーであるX-PSGI-Usedを付け加えるという、簡単な実例を掲げます。

# $appは単純なPSGIアプリケーションです
my $app = sub {
    my $env = shift;
    return [ '200', [ 'Content-Type' => 'text/plain' ], [ "Hello World" ] ];
};

# $xheader は$appを包み込むミドルウェアの一部です
my $xheader = sub {
    my $env = shift;
    my $res = $app->($env);
    push @{$res->[1]}, 'X-PSGI-Used' => 1;
    return $res;
};

ミドルウェアは、サーバーの観点から見てPSGIアプリケーションであるように、正確に振る舞わなければ「なりません」。ミドルウェアは、これまでに述べたストリーミングインターフェースに対応しないことを決定「できます」が、理解できないレスポンスタイプを素通ししなければ「なりません」

CHANGELOGS / 更新履歴

バージョン1.1: 2010年2月某日

  • 拡張として、随意選択のPSGIキーを追加しました: psgix.loggerおよびpsgix.session

  • psgi.streamingはPSGIサーバーによって実装「されうる」のではなく、実装「されなければならない」ようになりました。

  • psgi.run_onceと、psgi.nonblockingと、psgi.streamingのPSGIキーは、PSGIサーバーによって設定「されなければならない」ようになりました。

  • ライターメソッド(writer methods)からpoll_cbを取り除きました。

ACKNOWLEDGEMENTS / 謝辞

この仕様のいくらかの部分は下記の仕様から借用されています。

私はこれらの素晴らしい文書の作者に感謝したいと思います。

AUTHOR / 作者

Tatsuhiko Miyagawa <miyagawa@bulknews.net>

CONTRIBUTORS / 貢献者

以下の人々は、コードのコミットや、パッチの送付や、バグの報告や、質問や、有用な助言の提案や、粗探しや、IRCでのチャットや、私のブログへのコメントで、PSGI仕様とPlack実装へ貢献してくれました(順不同):

Tokuhiro Matsuno
Kazuhiro Osawa
Yuval Kogman
Kazuho Oku
Alexis Sukrieh
Takatoshi Kitano
Stevan Little
Daisuke Murase
mala
Pedro Melo
Jesse Luehrs
John Beppu
Shawn M Moore
Mark Stosberg
Matt S Trout
Jesse Vincent
Chia-liang Kao
Dave Rolsky
Hans Dieter Pearcey
Randy J Ray
Benjamin Trott
Max Maischein
Slaven Rezić
Marcel Grünauer
Masayoshi Sekimura
Brock Wilcox
Piers Cawley
Daisuke Maki
Kang-min Liu
Yasuhiro Matsumoto
Ash Berlin
Artur Bergman
Simon Cozens
Scott McWhirter
Jiro Nishiguchi
Masahiro Chiba
Patrick Donelan
Paul Driver
Florian Ragwitz

COPYRIGHT AND LICENSE / 著作権とライセンス

Copyright Tatsuhiko Miyagawa, 2009.

このドキュメントは、クリエイティブ・コモンズ・ライセンス「by-sa」(表示・継承)によってライセンスされています。

The original text: This document is licensed under the Creative Commons license by-sa.

@gardejo
Copy link
Author

gardejo commented May 17, 2010

I, gardejo, had executed a git remote add http://git.coderepos.org/share/lang/perl/PSGI-Doc-JA.git command.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment