株式会社クイックのWebサービス開発blog

HAPPYなエンジニア&デザイナーのブログです

Nginxのrefererでサーバを振り分ける方法を考えてみた

こんにちは。
よく外国の方に道を聞かれます。


matsBです(๑•̀ㅂ•́)و✧


yume曰く、私は黒人顔だそうです。(いい意味で)



みんな大好きNginxですが、"valid_referers"は使われていますか?
"ngx_http_referer_module"モジュールなんですが、便利です。
デフォルトでenableになるので再コンパイルの必要ないので、今すぐ使えます!


実際にNginxをリバースプロキシとして使用していて、refererで各サーバへの振り分けをしたいなぁ
と思うときありますよね!?

Yahoo砲を頂いた際に、本サイトを落としたくない。
キャンペーンを打った時に、本サイトに影響を出したくない。
サイトを統合する際に、ソースのマージが面倒だからそのままのサーバを使いたい。

とか。


"valid_referers"を使えば楽勝でいけます。
Module ngx_http_referer_module

"valid_referers"を検索すると、直リンクを禁止させたりとかの用途で使用する例が多いみたいですが
今回は、refererを基点にサーバの振り分けの話をしたいと思います。



今回は別々のドメインのサーバを統合して、片方のいちディレクトリとして扱う場合を例にします。

イメージはこんな感じです。
f:id:aimstogeek:20160622185310p:plain




fuga.919.co.jpをhoge.919.co.jpの/fugadir/に移行させて、hoge.919.co.jpドメイン自体は新規のNginxが持ちます。

振り分けとしては
http://hoge.919.co.jp/fugadir/ → 旧fuga.919.co.jp(10.0.0.1)に
・それ以外のアクセスは → 旧hoge.919.co.jp(192.168.1.1)に


一見下記のような設定で振り分けで出来そうですが、、、

ダメな例

server {
    listen 80;
    server_name hoge.919.co.jp;

    access_log  /var/log/nginx/access.log main;
    error_log  /var/log/nginx/error.log debug;
    rewrite_log on;

    set $backend01 "192.168.1.1";
    set $backend02 "10.0.0.1";

    location ^~ /fugadir/ {
        rewrite /fugadir/(.*) /$1 break;
        proxy_pass http://$backend02;
        include /etc/nginx/conf.d/proxy.conf;
    }

    location / {
        proxy_pass https://$backend01;
        include /etc/nginx/conf.d/proxy.conf;
        break;
    }
}

実はこのままだと画像とかCSSなどが表示できません。
アクセスログは下記のようになります。

"GET /fugadir/ HTTP/1.1" 200 "referer無し"                           #$backend02に振り分け
"GET /img/01.jpg HTTP/1.1" 404 "http://hoge.919.co.jp/fugadir/"      #$backend01に振り分け
"GET /css/01.css HTTP/1.1" 404 "http://hoge.919.co.jp/fugadir/"      #$backend01に振り分け
"GET /img/02.jpg HTTP/1.1" 404 "http://hoge.919.co.jp/css/01.css"    #$backend01に振り分け

これは、http://hoge.919.co.jp/fugadir/ にリクエストが来た際に、プログラムを変更していないので生成されたHTML内のソースが"/img/01.jpg"や"/css/01.css"になります。
/fugadir/以外は$backend01(192.168.1.1)にリクエストが流れるので、404 NOT FOUNDが返ってきます。



じゃあどうするの?
と言うと、valid_referersを使ってrefererで引っ掛けて$backend02(10.0.0.1)に振り分けます。

まず先ほどのLogのrefererに注目すると

"GET /fugadir/ HTTP/1.1" 200 "referer無し"                           #PHPが動きHTMLが生成される
"GET /img/01.jpg HTTP/1.1" 404 "http://hoge.919.co.jp/fugadir/"      #生成されたHTMLが01.jpgをGET
"GET /css/01.css HTTP/1.1" 404 "http://hoge.919.co.jp/fugadir/"      #生成されたHTMLが01.cssをGET
"GET /img/02.jpg HTTP/1.1" 404 "http://hoge.919.co.jp/css/01.css"    #呼ばれたCSSが02.jpgをGET

となり、HTMLの生成元がrefererとして付きます。
ただ、CSSが画像を引っ張ってくる時は、CSSrefererになるので注意です。


valid_referersを使って/img/へのアクセスはrefererで引っ掛けて$backend02に振り分けます。

いい例

server {
    listen 80;
    server_name hoge.919.co.jp;

    access_log  /var/log/nginx/access.log main;
    error_log  /var/log/nginx/error.log debug;
    rewrite_log on;

    set $backend01 "192.168.1.1";
    set $backend02 "10.0.0.1";

    location ^~ /fugadir/ {
        rewrite /fugadir/(.*) /$1 break;
        proxy_pass http://$backend02;
        include /etc/nginx/conf.d/proxy.conf;
    }

    location /img/ {
        valid_referers ~hoge\.919\.co\.jp/fugadir/;       #※該当したら$invalid_refererを空文字にする
        valid_referers ~hoge\.919\.co\.jp/css/.*\.css;    #該当したら$invalid_refererを空文字にする
        if ($invalid_referer != "1") {                    #1以外(空文字)の場合は$backend02に振り分ける
            proxy_pass http://$backend02;
        }
        include /etc/nginx/conf.d/proxy.conf;
        break;
    }

    location / {
        proxy_pass https://$backend01;
        include /etc/nginx/conf.d/proxy.conf;
        break;
    }
}

Logを確認すると

"GET /fugadir/ HTTP/1.1" 200 "referer無し" ""                          #$backend02に振り分け
"GET /img/01.jpg HTTP/1.1" 200 "http://hoge.919.co.jp/fugadir/" ""     #$backend02に振り分け
"GET /css/01.css HTTP/1.1" 200 "http://hoge.919.co.jp/fugadir/" ""     #$backend02に振り分け
"GET /img/02.jpg HTTP/1.1" 200 "http://hoge.919.co.jp/css/01.css" ""   #$backend02に振り分け

と、思っていた通りに動いてくれてプログラムを変更せずにサイトを統合できました!
この辺を応用すれば、refererで色々と振り分けが可能になりYahoo砲だけ臨時AWSに飛ばすとか、Sorryサーバに飛ばすとかの振り分けが可能になります!


ちなみにですが、$invalid_refererの値(valid_referersにヒットしてるか)を確認したい場合は、Nginxのlog_formatに"$invalid_referer"を追記すれば閲覧可能です。

  log_format main '$remote_addr - $remote_user [$time_local] "$request" '
                  '$status $body_bytes_sent "$http_referer" '
                  '"$http_user_agent" "$invalid_referer"';






※最初混乱しましたが、valid_referersに当てはまると空文字で、当てはまらないと“1”になるそうです。
http://nginx.org/en/docs/http/ngx_http_referer_module.html

$invalid_referer
Empty string, if the “Referer” request header field value is considered valid, otherwise “1”.