実例で学ぶ、Nginxのrewriteに指定できるbreakとlastの違い

Nginxのrewriteで指定できるlastbreakの違いを、実例を交えて紹介します。実例には、「apple-touch-iconの様々なサイズのリクエストに対して、一種類だけ用意したアイコン画像を返す」を使用します。

/apple-touch-icon.pngを返すために、Nginxの設定にはこんな感じで記述しておきます。

location ~ ^/apple-touch-icon(.*)\.png$ {
    root /tmp/nginx/;
    %rewriteルール%
}

上記の%rewriteルール%に以下の4つのどれかを書いたとします。

  1. rewrite ^(.*)$ /apple-touch-icon.png break;
  2. rewrite ^(.*)$ /apple-touch-icon.png last;
  3. rewrite ^/apple-touch-icon(.+)\.png$ /apple-touch-icon.png break;
  4. rewrite ^/apple-touch-icon(.+)\.png$ /apple-touch-icon.png last;

この中で、2番目のrewrite ^(.*)$ /apple-touch-icon.png last;だけは、/apple-touch-icon.pngにアクセスするとHTTPステータスコード500が返ってくるようになってしまいます。この記事ではその原因を紹介します。

lastとbreakの違いの概要

Nginxのドキュメントのrewriteには、こんな説明があります。

last
stops processing the current set of ngx_http_rewrite_module directives and starts a search for a new location matching the changed URI;
break
stops processing the current set of ngx_http_rewrite_module directives as with the break directive;

この説明を読む限り、どうやらlastはURIをrewriteした後に、locationディレクティブのマッチングをやり直すようです。逆に、breakはrewriteした後に、locationディレクティブのマッチングをやり直さないみたいです。

lastとbreakの違いを実例で紹介

例で使用するapple-touch-icon.pngについて

スマホ向けのサイトには、大抵はapple-touch-icon.pngという、ホーム画面などで使われるアイコンを用意することになります。この画像は76x76やら120x120やら、色々なサイズが要求されます。また、仕様もしばしば変わるため、様々なサイズを用意するのが面倒だったりします。

それらサイズ違いの画像に対して、以下のようなリクエストがサーバに投げられます。

  • /apple-touch-icon.png
  • /apple-touch-icon-76x76.png
  • /apple-touch-icon-120x120.png
  • などなど...

この煩雑さを解消するために、Nginxのrewriteを使って、apple-touch-iconxxx.pngのリクエストに対して、用意した一種類のアイコン画像を返すようにします。

rewrite(break)を使用

apple-touch-icon.pngapple-touch-icon-76-76.pngといった色々なリクエストに対して、リダイレクトをせずに、アイコン画像を返したい場合は、正規表現を用いたlocationディレクティブとrewriteを使って、以下のように書くと実現できます。

location ~ ^/apple-touch-icon(.*)\.png$ {
    root /tmp/nginx/;
    rewrite ^(.*)$ /apple-touch-icon.png break;
}

上記rootで指定したディレクトリにapple-touch-icon.pngを置いておくと、リクエストがapple-touch-icon.pngだろうがapple-touch-icon-76x76.pngだろうが、同じ画像(apple-touch-icon.png)を返すことができます。

※Nginxはローカルで動かし、8080番ポートで待ち受けています。

$ curl -I http://localhost:8080/apple-touch-icon.png
HTTP/1.1 200 OK
Server: nginx/1.8.0
...
ETag: "568b0ef5-e77"
...

$ curl -I http://localhost:8080/apple-touch-icon-76x76.png
HTTP/1.1 200 OK
Server: nginx/1.8.0
...
ETag: "568b0ef5-e77"
...

$ curl -I http://localhost:8080/apple-touch-icon-120x120.png
HTTP/1.1 200 OK
Server: nginx/1.8.0
...
ETag: "568b0ef5-e77"
...

上記のように、curlを使った確認でも/apple-touch-icon.png/apple-touch-icon-76x76.png/apple-touch-icon-120x120.pngは同じEtagを返していることが確認できました。

なぜこのような結果になるかというと、apple-touch-icon.xxx.pngというリクエストを、rewriteを使って置き換えているからです。

rewrite ^(.*)$ /apple-touch-icon.png break;

そのrewriteのルールは、locationに一致した全てのリクエスト(^(.*)$)を、アイコンの画像(/apple-touch-icon.png)に置き換えるというものです。また、最後にbreakを指定しているので、置き換えたURIで、locationディレクティブのマッチングをやり直しません。

rewriteのbreakをlastに変更すると...

先ほどのlocationディレクティブのrewriteを、lastに変更してみます。

location ~ ^/apple-touch-icon(.*)\.png$ {
    root /tmp/nginx/;
    rewrite ^(.*)$ /apple-touch-icon.png last;
}

そうすると、/apple-touch-icon.png/apple-touch-icon-76x76.pngにアクセスすると、HTTPステータスコード500が返ってくるようになります。

$ curl -I http://localhost:8080/apple-touch-icon.png
HTTP/1.1 500 Internal Server Error

ステータスコードが500になってしまう原因は、rewriteの変換がループしてしまうからです。例えば、apple-touch-icon.pngへのリクエストは、以下のようにループが終わらなくなってしまいます。

  1. location(~ ^/apple-touch-icon(.*)\.png$)にマッチする。
  2. rewrite ^(.*)$とあるので、必ず/apple-touch-icon.pngに置き換えられる。
  3. rewritelastフラグがあるので、locationのマッチをやり直す。
  4. location(~ ^/apple-touch-icon(.*)\.png$)にマッチする。
  5. rewrite ^(.*)$とあるので、必ず/apple-touch-icon.pngに置き換えられる。
  6. rewritelastフラグがあるので、locationのマッチをやり直す。
  7. 繰り返し...

エラーログを見ると、rewrite or internal redirection cycle while processingというメッセージが表示されています。

エラーログ

2016/01/05 xx:xx:xx [error] 25466#0: *9595 rewrite or internal redirection cycle while processing "/apple-touch-icon.png", client: 127.0.0.1, server: localhost, request: "HEAD /apple-touch-icon.png HTTP/1.1", host: "localhost:8080"

lastを使った場合でも動かすには

先ほどの、HTTPステータスコード500が返ってきてしまう記述は、rewriteの変換ループが繰り返されないようにすれば修正することができます。例えば、rewriteの記述を以下のように変更します。

location ~ ^/apple-touch-icon(.*)\.png$ {
    root /tmp/nginx/;
    # rewrite ^(.*)$ /apple-touch-icon.png last;  # 500が返ってくるバージョンはコメントアウト
    rewrite ^/apple-touch-icon(.+)\.png$ /apple-touch-icon.png last;
}

こうすると、/apple-touch-icon.png/apple-touch-icon-76x76.pngといったリクエストに対して、画像が正常に返ってくるようになります。

$ curl -I http://localhost:8080/apple-touch-icon.png
HTTP/1.1 200 OK

$ curl -I http://localhost:8080/apple-touch-icon-76x76.png
HTTP/1.1 200 OK

なぜ、ステータスコード200が返ってくるようになったかというと、rewriteの正規表現を絞ってapple-touch-icon.pngではrewriteされないようにしたからです。

修正版のrewriteの正規表現は、^/apple-touch-icon(.+)\.png$というルールにしているため、/apple-touch-iconの後に任意の文字が1文字以上続き、最後に.pngで終わるリクエストをapple-touch-icon.pngに書き換えています。つまり、/apple-touch-icon.pngは、rewriteのルールに引っかからないことになります。

例えば、/apple-touch-icon-76x76.pngというリクエストの場合、以下のような流れになります。

  1. /apple-touch-icon-76x76.pngは、location(~ ^/apple-touch-icon(.*)\.png$)にマッチする。
  2. rewrite ^/apple-touch-icon(.+)\.png$とあるので、/apple-touch-icon-76x76.png/apple-touch-icon.pngに置き換えられる。
  3. rewritelastフラグがあるので、locationのマッチをやり直す。
  4. location(~ ^/apple-touch-icon(.*)\.png$)にマッチする。
  5. /apple-touch-icon.pngは、rewrite ^/apple-touch-icon(.+)\.png$に一致せず、rewriteしない。

上記のようにrewriteが無限にループせずに、ステータスコードが500ではなく200が返ってくるようになります。

この記事が役に立った場合、シェアしていただけると励みになります!!