2011年11月06日

Ruby1.9のcgi.rbが行うコンテンツサイズチェックについて

Ruby1.9系で大量のファイルをアップロードするcgiスクリプトを書いてハマったことを書きます。

このblogの編集用に画像ファイルをサーバにアップロードするスクリプトを書いたのですが、ファイルが少ないと上手く動くものの、何故か数百Mのアップロードとなるとアップロードが完了してから落ちるという謎現象が起こりました。アップロード自体は成功しているので、Apacheではなくcgi側だと思ったのですが、cgi側も少数のファイルでは正常に動作することから問題の切り分けが難航。

そんな中、ひたすらgoogleで検索していると「るびま」のcgi.rb がイケてない 12 の理由がヒット。このなかで、ruby1.8系のcgi.rbは無制限にファイルを受け取れるという記述を発見。

cgi.rb では、受信する HTTP リクエストデータのサイズを確認していません。 そのため、例えば 10GB の動画ファイルを送られてきた場合、それを正直に受け取ってしまうため、サーバ資源を食い荒らされてしまいます。

これを防ぐには、Content-Length の値を確認し、大きすぎるようであれば受信しないようにする必要があります。 以下がそのためのパッチです*4。

この記事の中で、ruby1.8系のcgi.rbでリクエストパラメータの数とサイズを制限するパッチが書かれていました。ひょっとすると、ruby1.9系のcgi.rbにマージされたかと思い、1.9系 cgi/core.rbで検索をかけると予想通りマージされていました。
rubyのバージョンはruby 1.9.2p290 (2011-07-09 revision 32553) [amd64-freebsd8]

344   # Maximum content length of post data
345   ##MAX_CONTENT_LENGTH  = 2 * 1024 * 1024
346
347   # Maximum content length of multipart data
348   MAX_MULTIPART_LENGTH  = 128 * 1024 * 1024
349
350   # Maximum number of request parameters when multipart
351   MAX_MULTIPART_COUNT = 128

これにより、パラメータ数は128個。最大サイズは128MBまでということが判明。
これよりも増やしたい場合は、単純に定数の上書きをすれば良いようです。(定数をあとから上書きして良いのかという話はありますが・・・。本当ならば自分用のパッチでも当てた方がいいのかもしれません。)

require "cgi.rb"
CGI::MAX_MULTIPART_LENGTH  = 512 * 1024 * 1024
CGI::MAX_MULTIPART_COUNT = 512

参考文献

cgi.rb がイケてない 12 の理由
[ruby-list:44296] multipartで送信した時のRails/cgi.rbの動作について