If は、location のコンテキストで使う場合に邪悪です

はじめに

ディレクティブif は、locationコンテキスト内で使われた場合に問題があります。場合によっては期待した通りに動作せず、代わりに完全に異なる何かを行います。 場合によってはsegfaultにさえなります。可能であればそれを避けるのが一般的によい方法です。

location コンテキストである場合に内部で実行できる100%安全な操作は次の通りです:

それ以外には、SIGSEGVの可能性も含めて、予想できない挙動を起こす可能性があります。

if の動作に矛盾が無いように注意することが重要です。2つの同一のリクエストがある場合、一方でランダムには失敗せず、もう一方は動作しないかもしれません。適切なテストと‘’‘使える’‘’かどうかの理解が必要です。ただし、利用可能な他のディレクティブを使うことへのアドバイスはまだ適用されます。

例えば、等価なディレクティブが無い変数をテストする必要があり、ifの使用を単純に避けることができないことがあります。

if ($request_method = POST ) {
  return 405;
}
if ($args ~ post=140){
  rewrite ^ http://example.com/ permanent;
}

代わりにどうすればよいか

要望に合えば try_files を使ってください。他の場合には、 “return ...” あるいは “rewrite ... last” を使ってください。場合によっては if をサーバレベルに移動することもできます (その中には他の書き換えモジュールのディレクティブのみが許可されるため、安全です)。

例えば、以下はリクエストを処理するために使われるlocationを安全に変更するために使われるかも知れません:

location / {
    error_page 418 = @other;
    recursive_error_pages on;

    if ($something) {
        return 418;
    }

    # some configuration
    ...
}

location @other {
    # some other configuration
    ...
}

スクリプトを実行するために組み込みのスクリプトモジュール (embedded perl, あるいは様々な NGINX サードパーティモジュール) を使うことが良い考えになるかも知れない場合があります。

なぜ if が邪悪なのかの幾つかの例です。自宅ではこれを試さないでください。警告しました。

# Here is collection of unexpectedly buggy configurations to show that
# if inside location is evil.

# only second header will be present in response
# not really bug, just how it works

location /only-one-if {
    set $true 1;

    if ($true) {
        add_header X-First 1;
    }

    if ($true) {
        add_header X-Second 2;
    }

    return 204;
}

# request will be sent to backend without uri changed
# to '/' due to if

location /proxy-pass-uri {
    proxy_pass http://127.0.0.1:8080/;

    set $true 1;

    if ($true) {
        # nothing
    }
}

# try_files wont work due to if

location /if-try-files {
     try_files  /file  @fallback;

     set $true 1;

     if ($true) {
         # nothing
     }
}

# nginx will SIGSEGV

location /crash {

    set $true 1;

    if ($true) {
        # fastcgi_pass here
        fastcgi_pass  127.0.0.1:9000;
    }

    if ($true) {
        # no handler here
    }
}

# alias with captures isn't correcly inherited into implicit nested
# location created by if

location ~* ^/if-and-alias/(?<file>.*) {
    alias /tmp/$file;

    set $true 1;

    if ($true) {
        # nothing
    }
}

ここにリストされていない例を見つけたと思った場合 - それをNGINX 開発メーリングリストに報告しようとするのは良い考えです。

なぜこれが起こり、まだ修正されないのか

ディレクティブ "if" は指示を否応なしに評価するrewriteモジュールの一部です。一方で、NGINX設定は普通は宣言的です。ある時点で、ユーザの要求により、“if” 内の幾つかの非書き換えディレクティブを有効にする試みが行われ、これが現在の状況に繋がります。ほとんどは動作しますが、上を見てください。

唯一の修正の仕方は if 内の非rewriteディレクティブを完全に無効にすることのように思えます。しかしそれは多くの設定をぶち壊すかも知れないのでまだされていません。

まだ location コンテキストの中で if を使いたい場合

上の全てを読んだあとでまだ if を使いたい場合:

  • それがどう動作するかを本当に理解してください。幾つかの基本的な考えが、例えばここで見つかるでしょう。
  • 適切なテストをしてください。

警告しました。

inserted by FC2 system