概要
RubyのアプリケーションサーバunicornにはURLの最大長の制限がC言語でハードコードされており、実行時に変更できないようだ。
DEF_MAX_LENGTH(REQUEST_URI, 1024 * 15);
DEF_MAX_LENGTH(FRAGMENT, 1024); /* Don't know if this length is specified somewhere or not */
DEF_MAX_LENGTH(REQUEST_PATH, 4096); /* common PATH_MAX on modern systems */
DEF_MAX_LENGTH(QUERY_STRING, (1024 * 10));
この上限値を書き換えてカスタムunicorn gemを作ってみようと思う。
unicornのgemを作成
まずはunicornのリポジトリをclone。
git clone https://github.com/defunkt/unicorn.git
cd unicorn
リポジトリ内のHACKING
ファイルを読むとgemのビルドに必要なツールが色々書いてある。
- GNU make
- Ragel
- pandoc
- olddoc(書いてないのだが必要)
1~3、そしてgccなどはお使いのOSのパッケージマネージャでインストールしていただきたい。Arch Linuxの場合はpacmanで全部そろう。
問題はolddocというマイナーなツールで、これは
gem install olddoc
でインストールできる。
以上で準備は整った。
gitにタグを打つ。これがgemのバージョン番号になる(この辺の仕組みはGIT-VERSION-GEN
ファイルを参照)。
git tag -a v5.5.1.custom -m 'v5.5.1.custom'
gemをビルド:
make gem
pkg
ディレクトリの中にunicorn-5.5.1.custom.gem
といファイルが作成されているはずだ。
ビルドしたgemを試してみる
gem
コマンドでインストールしてみる
gem install pkg/unicorn-5.5.1.custom.gem
下記を実行してバージョンが5.5.1.custom
と表示されればインストール成功だ。
ruby -r unicorn -e 'puts Unicorn::Const::UNICORN_VERSION'
URL最大長の確認
下記のファイルを任意のディレクトリに作り、unicorn -p 9000
を実行すればunicornがポート9000番で起動するはずだ。
class TestApp
def call(env)
[ 200, # ステータス(Integer)
{ 'Content-Type' => 'text/plain' }, # レスポンスヘッダ(Hash)
env.keys.sort.map {|k| "#{k} = #{env[k]}\n" } # body(StringのArray)
]
end
end
run TestApp.new
curlで適当な長さのURLを生成して投げてみよう。
curl -si "http://localhost:9000/?aaa=$(ruby -e 'puts "x"*10')"
"x"*10
の部分が10300バイトくらいになると
HTTP/1.1 414 URI Too Long
が返るようになるはずだ。
unicornのソースを書き換えてURL最大長を上げる
下記のように書き換える。
diff --git ext/unicorn_http/global_variables.h ext/unicorn_http/global_variables.h
index f8e694c..0964209 100644
--- ext/unicorn_http/global_variables.h
+++ ext/unicorn_http/global_variables.h
@@ -62,10 +62,10 @@ NORETURN(static void parser_raise(VALUE klass, const char *));
/* Defines the maximum allowed lengths for various input elements.*/
DEF_MAX_LENGTH(FIELD_NAME, 256);
DEF_MAX_LENGTH(FIELD_VALUE, 80 * 1024);
-DEF_MAX_LENGTH(REQUEST_URI, 1024 * 15);
+DEF_MAX_LENGTH(REQUEST_URI, 1024 * 64);
DEF_MAX_LENGTH(FRAGMENT, 1024); /* Don't know if this length is specified somewhere or not */
DEF_MAX_LENGTH(REQUEST_PATH, 4096); /* common PATH_MAX on modern systems */
-DEF_MAX_LENGTH(QUERY_STRING, (1024 * 10));
+DEF_MAX_LENGTH(QUERY_STRING, (1024 * 64));
static void init_globals(void)
{
再度make gem
でビルドすると今度はgemファイル名がunicorn-5.5.1.custom.dirty.gem
のようにdirty
とついているかもしれない。これは最終コミットから変更があることを意味する。これが嫌ならコミットして再度タグを打ってからビルドしよう。
前回のgemをアンインストールしてから新しいgemをインストールする。
gem uninstall unicorn -v 5.5.1.custom
gem install pkg/unicorn-5.5.1.custom.dirty.gem
またunicornを立ち上げてcurlで投げると、今度は65536バイト近くまでいけるようになっているはずだ。
ビルドしたgemを他のプロジェクトのGemfileから利用できるようにする
gemをS3に置く方法
これが良いのではないか。
http://fly1tkg.github.io/2014/09/s3-gem/
Gemfileでgitリポジトリを指定する方法
修正したソースコードを独自リポジトリに置いてGemfileで
gem "unicorn", git: 'git@github.com:MY_NAME/unicorn.git', branch: 'bbd270b'
のように指定したくなるかもしれない。しかしこのままではうまくいかない。
ビルドに必要な下記ファイルが欠けているからだ。
ext/unicorn_http/unicorn_http.c
lib/unicorn/version.rb
これらはmake gem
時に生成される。.gitignore
でも無視するように指定されている。これらもリポジトリに含めればGemfileから上記の記述で使えるようになる。厄介なところだ。いい方法があったら教えていただきたい。