LoginSignup
1
0

More than 3 years have passed since last update.

unicornにパッチを当ててカスタムgemを作る

Posted at

概要

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のビルドに必要なツールが色々書いてある。

  1. GNU make
  2. Ragel
  3. pandoc
  4. 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番で起動するはずだ。

config.ru
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から上記の記述で使えるようになる。厄介なところだ。いい方法があったら教えていただきたい。

1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0