[mod_mruby] 静的リソースをリバースプロキシで配信する


これは [mod_mruby ngx_mruby Advent Calendar 2014](http://qiita.com/advent-calendar/2014/mod-ngx-mruby) の１６日目(12/16)の投稿です。

今回は mod_mruby でのリバースプロキシして、Amazon S3 上の静的リソース（JavaScipt/CSS/HTML/画像）を配信する方法について書きます。S3 の WEBホスティングに対して HTTP でリソースを取得しにいっているので、S3 でなくても別にどのサーバに対しても、向き先をかえれば応用できます。

> 前提となる環境は、アドベントカレンダーの３日目に書きましたので、何かあればそちらを参照ください。
> ⇒ [mod_mruby を Amazon EC2、Apache2.4 へ導入する](http://qiita.com/hkusu/items/ca8622c83b0bfc2cac72)
> ⇒ Apache のバージョンは `2.4.10`、mod_mruby のバージョンは `1.9.7` です。
> 
> また、Apache へのディレクティブ設定、および、mod_mruby スクリプトのちょっとした例については １０日目に書きました。
> ⇒ [mod_mruby の可能性とディレクティブ設定/スクリプトの例](http://qiita.com/hkusu/items/dce6ab77a418bb26765c)

## 実現する構成

図にするとこんな感じです。

![スクリーンショット 2014-12-16 21.55.22.png](https://qiita-image-store.s3.amazonaws.com/0/35733/560fd2e9-815c-dede-c7bc-d4101b3268a7.png "スクリーンショット 2014-12-16 21.55.22.png")

最初からクライアントのデバイスから S3 の URL を参照しておけばいいじゃんという意見があるかもしれませんが、この構成は次のようなメリットがあると思っています。

- ① WEBサーバが複数ある場合に、静的リソースを S3 で一元管理できる。
	- 静的なリソースのデプロイはアプリケーションと切り離して S3 だけで完結する。
- ② リリース後でも、クライアントへのインタフェースはそのままに、後から素材の場所(S3 から S3 以外へ)を変更したり、例えば 画像以外は やっぱりWEBサーバに置く、画像だけ別のサーバに置く、というような変更ができる。
- ③ 上記①と被るかもですが、HTTP レスポンスヘッダ `Last-Modified` および `Etag` の値をどの WEBサーバへのリクエストでも同じものにすることができる。
	- クライアントブラウザにキャッシュさせるときに重要。
	- WEBサーバへデプロイする際に揃えるという方法もあるにはある。
- ④ URL のドメインを WEBサーバ(APIサーバ)と 静的リソースで同じにできる。
	- 気分や管理の問題もありますが、それ以外には JavaScript アプリケーションでクロスオリジン(クロスドメイン)対策する必要がなくなる。

> 本当は プロキシした静的リソースに対して、mod_mruby で画像変換や JavaScript/CSS ファイルの縮小(minify)や結合(concat)、gzip 圧縮、WEBサーバへのキャッシュなどまでできれば強力なのですが、それは別の機会にということで.. （gzip や キャッシュは mod_deflate や mod_cache と組み合わせれば何となくいけそう。）
> 
> 自分の職業柄、スマホ向けの通信の最適化などは意識している分野なので。通信の最適化については、こちらのアドベントカレンダーに書きました。
> ⇒ [モバイルフロントエンドで通信を削減する戦略についての考察](http://qiita.com/hkusu/items/b8b8a1bf456a0aaec39f)

悪用すれば、任意のWEBサイトの静的リソースを勝手に（厳密には動的 API も）自ドメインから配信できてしまうのですが^^; それはやらないでください。

また今回の構成だと静的リソースのみリバースプロキシしていますが、自サービスのバックエンドの動的 API にもリバースプロキシは可能です。

## Apache での設定方法

とっても簡単です。

まず前提として、Apache で HTTP プロトコルの Proxy が使えるようにしておきます。下記がコメントアウトされていたら(デフォルトでは恐らくコメントアウトされている)、コメントアウトを外します。

```apacheconf:***.conf
LoadModule proxy_module modules/mod_proxy.so
LoadModule proxy_http_module modules/mod_proxy_http.so
```

適当な名称で mod_mruby スクリプトを書きます。リクエストされたファイルの拡張子を見て、該当するものであればリバースプロキシします。ソースはこれだけ。

```ruby:/usr/local/apache2/mruby/proxy.rb
REMOTE_HOST = 'http://s3-hoge.amazonaws.com'
r = Apache::Request.new

if /\.(css|js|gif|jpe?g|png)$/ =~ r.filename
  r.reverse_proxy REMOTE_HOST + r.uri
end

Apache::return(Apache::OK)
```

書いた mod_mruby スクリプトを .conf ファイルで Apache の処理フェーズにフックさせます。

```apacheconf:***.conf
mrubyFixupsLast /usr/local/apache2/mruby/proxy.rb
```

`cache` オプションつけた方が良いかも。

> `mrubyTranslateNameMiddle` フェーズにフックだと、Proxy の設定がキャッシュされてしまう？感じだったので `mrubyFixupsLast` フェーズにフックしています。

あとは Apache を再起動したら終わりで、この設定を WEBサーバの台数分やります。すると例えば S3 の `http://s3-hoge.amazonaws.com/hoge.js` の JavaScript ファイルが `http://自ドメイン/hoge.js` で取得できます :smaile:

ただアクセスが多いサイトだと、どこかの層にキャッシュをもった方が良いので、例えば WEBサーバの前段に CroutFront を置くか、もしくは S3 の前段でも良いかと。もしくは上記の mod_mruby スクリプトに追加で実装するか、または mod_cache と組み合わせるとか。

> まあ順当に考えて、WEBサーバの前段に CroudFront が手軽かな。ネットワーク帯域がもったいないし。１分のキャッシュでも強力に効くと思います。

## おわりに

リバースプロキシを使いこなすと、色々たのしく便利なことが実現できるので、是非チャレンジしてもらえればと思います。明日は [@matsumotory](https://twitter.com/matsumotory) さんによる「生成コストの高いオブジェクトを共有するための基本」です！