If は邪悪

はじめに

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

The only 100% safe things which may be done inside if in a location context are:

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

It is important to note that the behaviour of if is not inconsistent, given two identical requests it will not randomly fail on one and work on the other, with proper testing and understanding ifs ‘’‘can’‘’ be used. The advice to use other directives where available still very much applies, though.

There are cases where you simply cannot avoid using an if, for example, if you need to test a variable which has no equivalent directive.

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

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

要望に合えば try_files を使ってください。他の場合には、 “return ...” あるいは “rewrite ... last” を使ってください。In some cases, it’s also possible to move ifs to server level (where it’s safe as only other rewrite module directives are allowed within it).

例えば、以下はリクエストを処理するために使われる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 が邪悪なのかの幾つかの例です。Don’t try this at home. 警告しました。

# 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設定は普通は宣言的です。At some point due to users demand an attempt was made to enable some non-rewrite directives inside “if”, and this lead to situation we have now. ほとんどは動作しますが、上を見てください。

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

まだ if を使いたい場合

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

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

警告しました。

TOP
inserted by FC2 system