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

# 概要

Rubyの[アプリケーションサーバunicorn](https://github.com/defunkt/unicorn)にはURLの最大長の制限が[C言語でハードコードされており](https://github.com/defunkt/unicorn/blob/master/ext/unicorn_http/global_variables.h#L65)、実行時に変更できないようだ。

```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というマイナーなツール](https://rubygems.org/gems/olddoc/versions/1.7.1?locale=ja)で、これは

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