ようへいの日々精進XP

よかろうもん

ngx_mruby でリダイレクト実装が 256 倍捗ったのでメモ

tl;dr

とあるサービスの移行作業で 200 個くらいのリダイレクト処理を ngx_mruby で実装したのでメモしておきます.

ngx_mruby とは

github.com

matsumotory さんが実装された Nginx に組み込むことで, Nginx の各種イベントをフックして mruby コードを実行することが出来る OSS です. Apache でも同様に mruby を実行出来る mod_mruby も同じく matsumotory さんによって実装されています.

リダイレクト案件

実はシンプル

今回のリダイレクトは特に複雑なことはなく, A サイト (https://A/path/100) を B サイト (https://B/2778425522) にリダイレクトをかけるだけです. ただし, リダイレクト元とリダイレクト先でパスの名前には関係性は無い為, リダイレクト 1 件毎に, リダイレクト元とリダイレクト先を記述していくことを検討しました. また, このような場合, 以下のように Nginx の rewrite を利用することで対応出来そうです. (他にも手法があるかもしれません)

nginx.org

以下のように, リダイレクト先が三件くらいまでなら, まあ, なんとか頑張れると思いましたが...

server {
  listen 80;
  server_name A;

  rewrite ^/path/100$ https://B/2778425522$1 permanent;
  rewrite ^/path/101$ https://B/5499644550$1 permanent;
  rewrite ^/path/102$ https://B/0739584639$1 permanent;
}

ところが,今回はリダイレクト対象が csv で 200 件近く渡されました.

さて, どうしようか

先述の通り, rewrite 文を 200 個くらい並べるのは現実的ではありません. また, 今後, リダイレクト設定の追加や削除が想定される場合には, リダイレクトの管理は CSV に統一しておいたほうが良さそうです.

以下はリダイレクト元とリダイレクト先が記述された CSV ファイルのサンプルです.

/path/to/100,https://example.com/1483465573
/path/to/101,https://example.com/9009130268
/path/to/102,https://example.com/2431394607
...
/path/to/199,https://example.com/4117255063
/path/to/200,https://example.com/8894250467

ここで, ngx_mruby の登場です.

戦略として, CSV ファイルを読み込んでおいて, リクエストパスと照合して, 一致したら Location にリダイレクト先の URL を設定してステータス 301 を返すようにしたいと思います.

Nginx の設定

以下は Nginx の設定です.

user daemon;
daemon off;
master_process off;
worker_processes 1;
error_log stderr notice;
#access_log /dev/stdout;

events {
    worker_connections  1024;
}

http {
    map $http_user_agent $log_ua {
        ~ELB-HealthChecker 0;
        default 1;
    }

    access_log /dev/stdout combined if=$log_ua;
    server {
        listen 80;

        location ~ ^/path/to/.*$ {
            mruby_content_handler /usr/local/nginx/hook/redirects.rb;
        }
    }
}

/path/to/xxxx でアクセスされた場合, mruby_content_handler で指定された mruby コードを実行するようにしています. この mruby コードで CSV ファイルを読み込み, パスのチェック, リダイレクト処理を実行します.

mruby コード

以下は mruby のコードです.

# /usr/local/nginx/hook/redirects.rb
r = Nginx::Request.new

redirect_csv = '/usr/local/nginx/conf/redirects.csv'
hash = {}
begin
  File.open(redirect_csv, 'r') do |f| 
    while l = f.gets
      key, value = l.chomp.split(',')
      hash[key] = value
    end
  end
rescue
  Nginx.errlogger Nginx::LOG_CRIT, "Could not read redirect list!!!"
  Nginx.return Nginx::HTTP_INTERNAL_SERVER_ERROR
end

key = r.uri
if hash[key]
  Nginx.errlogger Nginx::LOG_INFO, "Redirect working! SRC: #{key} DST: #{hash[key]}"
  Nginx.redirect(hash[key], Nginx::HTTP_MOVED_PERMANENTLY)
else
  Nginx.errlogger Nginx::LOG_NOTICE, "No redirect target. Move to https://example.com."
  Nginx.redirect("https://example.com", Nginx::HTTP_MOVED_PERMANENTLY)
end

思ったよりもシンプルに書くことが出来て驚いています.

  • Nginx::Request クラスにはリクエスト情報が含まれてて, インスタンスメソッドで各種情報 (スキーマやメソッド, パス等) を取得出来ます
  • Nginx クラスはレスポンスを操作出来るメソッドが含まれていて, returnredirect 等のクラスメソッドを利用しています
  • HTTP ステータスは定数として用意されていますので, 今回は 301 を返したいので Nginx::HTTP_MOVED_PERMANENTLY を利用しています

詳細はドキュメントをご一読下さい.

github.com

ちょっと動かしてみた

以下のリポジトリの Dockerfile を利用させて頂きました.

github.com

mruby コードと Nginx の設定, CSV ファイルを用意するだけで動作確認することが出来ました.

$ git clone https://github.com/matsumotory/docker-ngx_mruby.git
$ cd docker-ngx_mruby
$ vim docker/conf/nginx.conf # 先述の内容を記述
$ vim docker/conf/redirects.sample.csv
/path/to/100,https://yahoo.co.jp/
/path/to/101,https://google.co.jp/
$ vim docker/hook/redirects.rb # 先述の内容を記述
$ docker build -t ngx_mruby .
$ docker run -d -p 18080:80 ngx_mruby

コンテナを起動後, リクエストを投げてみます.

$ curl -s localhost:18080/path/to/100 -LI
HTTP/1.1 301 Moved Permanently
Server: nginx/1.15.12
Date: Sun, 14 Jun 2020 14:50:12 GMT
Content-Type: text/html
Content-Length: 170
Connection: keep-alive
Location: https://yahoo.co.jp/

HTTP/1.1 301 Redirect
Date: Sun, 14 Jun 2020 14:50:12 GMT
Connection: keep-alive
Cache-Control: no-store
Location: https://www.yahoo.co.jp/
Content-Type: text/html
Content-Language: en
Content-Length: 306

HTTP/2 200
content-length: 0
content-type: text/html; charset=utf-8
date: Sun, 14 Jun 2020 14:50:12 GMT
etag: W/"0-2jmj7l5rSw0yVb/vlWAYkK/YBwk"
set-cookie: B=65eq4ktfece94&b=3&s=nd; expires=Wed, 15-Jun-2022 14:50:12 GMT; path=/; domain=.yahoo.co.jp
vary: Accept-Encoding
x-vcap-request-id: 268b7a41-fdfa-4ce9-4d52-770b9291ad90
age: 0
via: http/1.1 edge1801.img.bbt.yahoo.co.jp (ApacheTrafficServer [c sSf ])
server: ATS

レスポンスから yahoo.co.jp にリダイレクトされていることが判ります. また, 以下のようなログが記録されています.

172.18.0.1 - - [14/Jun/2020:14:50:12 +0000] "HEAD /path/to/100 HTTP/1.1" 301 0 "-" "curl/7.54.0"

いい感じです.

最後に

ngx_mruby についてはほぼ初めての利用となりましたが, とても簡単に大量のリダイレクトを処理することが出来そうでエンジニアリング魂が揺さぶられました. これからも機会があれば ngx_mruby をはじめ mruby をギョームで利用出来れば良いなと考えています.