2015/02/16

PSGI Server がリクエストボディをどうしているのか知りたくなったので…

背景

諸般の事情により(お察しください)、巨大なファイル(おおむね2GB以上)が HTTP で POST された場合に、PSGIでどのように処理されるのかを調べてみましたよ。
観点としては、PSGIより下のレイヤー(PSGI server)でリクエストボディをどのように扱っているのかを調査。全体をバッファリングしてしまうのか、またボディの読み込みを遅延(あるいは拒否)できるのかを確認した。

また、想定しているシステムの temp 領域がかなり小さいということもあって、PSGI app の中で HTTP ヘッダの内容にもとづいてボディの扱いを変えたい(指定したディスクにバッファリングするのか、それともそもそもボディを無視するのか)。そのため、バッファリングが遅延できるようになっていると嬉しい。

結論

総じていうと、 Stream::Buffered を使って事前にリクエストボディ全体をバッファリングしてしまう方法がスタンダード。(Starman から発祥し、Starletで固められたコードが決定版的な扱いになっている)

Stream::Buffered の挙動として、デフォルトでは 1MB まではメモリにバッファし、それ以上は tempfile に書き出してバッファリングするようになっている。($Stream::Buffered::MaxMemoryBufferSize で調整可能)

PSGI app が呼ばれる前にバッファリングされてしまうので、メモリかディスクにボディ全体が一度保存されるのは避けられない模様(´・ω・`)

以下、それぞれの実装の該当箇所への参照とメモ↓

Starman

このあたり↓

Plack::TempBuffer を使っているけど、現在 Plack::TempBuffer は Stream::Buffered のエイリアスになっているため上述の挙動となる。

Starlet

このあたり↓

Plack::TempBuffer を利用。Starman とだいたい同じ。

Monoceros

このあたり↓

Plack::TempBuffer を利用。Starman とだいたい同じ、というか Starlet のコードをそのまま使ってますね。

Stream::Buffered を利用。Stream::Buffered を使っている以外は、ほぼ Starlet のコードそのまま。 

Twiggy

このあたり↓

PerlIOを利用して、すべてオンメモリにバッファリングしている。
ちなみに、リクエストの chunked エンコーディングには対応していないようだ(´・ω・`)

Feersum

や、やばい…何やってるのか全然理解できない…
タンさんのコードが天才的すぎるのか…((((;゚Д゚))))

追記: AnyEvent::HTTPD

ついでに AnyEvent::HTTPD も読んでみた。このへん↓
https://metacpan.org/source/AnyEvent::HTTPD::HTTPConnection#L352

Content-length 分だけ丸っとメモリに読み込んでいますね。
ちなみに、chunked エンコーディングには対応していない様子。

他にも読むべき PSGI Server があったら教えてください><