nginx で location の中に limit_except と try_files を併記すると 404 Not Found になる件

upstream を準備する
----

`GET /app` と `POST /app` に応答する単純な Sinatra アプリを準備。

```
$ rbenv local 2.3.0
$ cat <<EOF > Gemfile
source 'https://rubygems.org'

ruby '2.3.0'

gem 'sinatra'
gem 'thin'
gem 'rack-cors', require: 'rack/cors'
EOF
$ bundle install
$ cat <<EOF > app.rb
require 'sinatra'

get '/app' do
  'GET'
end

post '/app' do
  'POST'
end
EOF
$ cat <<EOF > config.ru
require './app'
run Sinatra::Application
EOF
$ bundle exec rackup -p 3000
$ curl http://localhost:3000/app
GET
$ curl -X POST http://localhost:3000/app
POST
```


try_files を使った内部リダイレクトを準備する
----

URLパスに対応するファイルがなければ upstream にリダイレクトする設定。

```
$ curl -L -O http://downloads.sourceforge.net/project/pcre/pcre/8.38/pcre-8.38.tar.bz2
$ tar -xjvf pcre-8.38.tar.bz2
$ nginx-build -d . -clear --with-pcre=$(pwd)/pcre-8.38
$ mkdir logs conf
$ cp nginx/1.9.15/nginx-1.9.15/conf/mime.types ./conf
$ cat <<EOF > conf/nginx.conf
worker_processes 1;

events {
    worker_connections 1024;
}

http {
    include mime.types;

    upstream app {
        server localhost:3000;
    }

    server {
        listen 8000;
        server_name localhost;
        root html;

        location / {
            try_files $uri $uri/index.html $uri.html @app;
        }

        location @app {
            proxy_pass http://app;
        }
    }
}
EOF
$ mkdir html
$ echo html > html/index.html
$ ./nginx/1.9.15/nginx-1.9.15/objs/nginx -p $(pwd) -c conf/nginx.conf
$ curl http://localhost:8000/index.html
html
$ curl http://localhost:8000/app
GET
$ curl -X POST http://localhost:8000/app
POST
```


Basic 認証を設定する
----

`limit_except` の前に、リクエストメソッドにかかわらず全てに対して Basic 認証をかけてみる。

```
$ cp conf/nginx.conf conf/nginx.conf.orig
$ vi conf/nginx.conf
$ diff -u conf/nginx.conf.orig conf/nginx.conf
--- conf/nginx.conf.orig	2016-04-23 16:31:35.000000000 +0900
+++ conf/nginx.conf	2016-04-23 16:48:22.000000000 +0900
@@ -18,6 +18,8 @@
 
         location / {
             try_files $uri $uri/index.html $uri.html @app;
+            auth_basic "Basic auth";
+            auth_basic_user_file .htpasswd;
         }
 
         location @app {
$ echo "user:$(openssl passwd -apr1 password)" > conf/.htpasswd
$ ./nginx/1.9.15/nginx-1.9.15/objs/nginx -p $(pwd) -c conf/nginx.conf -s reload
$ curl -u 'user:password' http://localhost:8000/app
GET
$ curl -X POST -u 'user:password' http://localhost:8000/app
POST
```


limit_except を使って POST リクエストにのみ Basic 認証
----

POST メソッドでのリクエストの時のみ、Basic 認証を要求する。

```
$ vi conf/nginx.conf
$ diff -u conf/nginx.conf.orig conf/nginx.conf
--- conf/nginx.conf.orig	2016-04-23 16:31:35.000000000 +0900
+++ conf/nginx.conf	2016-04-23 16:51:08.000000000 +0900
@@ -18,6 +18,10 @@
 
         location / {
             try_files $uri $uri/index.html $uri.html @app;
+            limit_except GET {
+                auth_basic "Basic auth";
+                auth_basic_user_file .htpasswd;
+            }
         }
 
         location @app {
$ ./nginx/1.9.15/nginx-1.9.15/objs/nginx -p $(pwd) -c conf/nginx.conf -s reload
$ curl http://localhost:8000/app
GET
$ curl -i -X POST -u 'user:password' http://localhost:8000/app
HTTP/1.1 404 Not Found
Server: nginx/1.9.15
Date: Sat, 23 Apr 2016 07:51:33 GMT
Content-Type: text/html
Content-Length: 169
Connection: keep-alive

<html>
<head><title>404 Not Found</title></head>
<body bgcolor="white">
<center><h1>404 Not Found</h1></center>
<hr><center>nginx/1.9.15</center>
</body>
</html>
```


404 Not Found
----

なぜ？

ちなみに、Basic 認証とか関係なく `limit_except` ディレクティブを使った時点で 404 Not Found になってしまう。

```
$ vi conf/nginx.conf
$ diff -u conf/nginx.conf.orig conf/nginx.conf
--- conf/nginx.conf.orig	2016-04-23 16:31:35.000000000 +0900
+++ conf/nginx.conf	2016-04-23 16:54:31.000000000 +0900
@@ -18,6 +18,8 @@
 
         location / {
             try_files $uri $uri/index.html $uri.html @app;
+            limit_except GET {
+            }
         }
 
         location @app {
$ ./nginx/1.9.15/nginx-1.9.15/objs/nginx -p $(pwd) -c conf/nginx.conf -s reload
$ curl http://localhost:8000/app
GET
$ curl -i -X POST -u 'user:password' http://localhost:8000/app
HTTP/1.1 404 Not Found
Server: nginx/1.9.15
Date: Sat, 23 Apr 2016 07:51:33 GMT
Content-Type: text/html
Content-Length: 169
Connection: keep-alive

<html>
<head><title>404 Not Found</title></head>
<body bgcolor="white">
<center><h1>404 Not Found</h1></center>
<hr><center>nginx/1.9.15</center>
</body>
</html>
```


limit_except を別の location に移す
----

`try_files` の内部リダイレクト先の `location` ディレクティブの方で `limit_except` ディレクティブを使えば 404 Not Found にはならない。

```
$ vi conf/nginx.conf
$ diff -u conf/nginx.conf.orig conf/nginx.conf
--- conf/nginx.conf.orig	2016-04-23 16:31:35.000000000 +0900
+++ conf/nginx.conf	2016-04-23 16:58:00.000000000 +0900
@@ -21,6 +21,10 @@
         }
 
         location @app {
+            limit_except GET {
+                auth_basic "Basic auth";
+                auth_basic_user_file .htpasswd;
+            }
             proxy_pass http://app;
         }
     }
$ ./nginx/1.9.15/nginx-1.9.15/objs/nginx -p $(pwd) -c conf/nginx.conf -s reload
$ curl http://localhost:8000/app
GET
$ curl -X POST -u 'user:password' http://localhost:8000/app
POST
```


まとめ
----

そういうものなのかな？
ドキュメントの探し方が悪いのか、該当する説明は見つけられませんでした。
ソースを読めば分かるのかも知れませんが、めんどくさくなってきたので、泥臭いけど動く方法でごまかそうと思ってます。