LoginSignup
18
17

More than 5 years have passed since last update.

Nginxで、POSTメソッドがエラー405で弾かれる場合

Last updated at Posted at 2015-07-02

Nginxで、GETは通るけれど、POSTが通らず、
ステータスコード 405: Method Not Allowed(許可されていないメソッド)
が返される場合。

原因:
Nginxは、スタティックなページでは、POSTが使えません。
スタティックなページ:ファイルの内容をそのまま返す場合など。
(というよりも、デフォルトでは全てスタティックと思ってよい感じです。)


対策(まとめ):
急いでいる時の応急処置: エラー405を200に付け替える。
(本当のエラーも付け替える事になるので、応急処置です。)

ちゃんとした対策: 外から見える側のlocationで、POSTを受けられるようにする。
例えば、openrestyを使っている場合は、ngx.req.get_post_args()などを呼んでおく。
(そのlocationから、ngx.exec()でどこかに飛ばす場合も、呼び出し元のlocationで
get_post_args()などを呼んでおく必要があります。)

処理後に固定の結果を返す場合でも、ngx.exec()は使えません。
ngx.exec()を使うと、POSTが使えないlocationに転送される場合はエラー405が発生します。
ngx.say()は使えます。(POSTが使えるlocationから外に出ていないので。)


回避方法 その1:
とにかく、とりあえず通したい時に。
(dirty hack..と書かれているけれど)
エラー405 を200(OK)に付け替えます。

参考ページの回答欄から。

error_page 405 =200 $uri;


回避方法(??) その2:
参考ページのコメント欄から。

If nginx's certain location contains proxy_pass or fastcgi_pass directive, this is a dynamic content, otherwise -- static.

要約:(略)・・fastcgi_passを使っていないlocationは、スタティックになります。

proxy_passにしてみる。
location ^~ /real/ {
・・・
}
location ^~ /p/ {
proxy_pass http://127.0.0.1/real/;
}

405ではじかれます。
アクセス先が動的に変わるだけなので、アクセス先がスタティックな内容なら、
処理はスタティックなページと同じです。
ということで、proxy_pass案は却下。


fastcgi_passにしてみる・・のがよさそうですが・・

Nginx wiki
PHPFcgiExampleJa

NginxはFCGIプロセスを自動的には生成しません。
そのためFCGIプロセスは別途実行させる必要があります。

要するに、別枠で(サーバとして)起動しておいて、用があったらCGIとして呼んでね!
(このあと、無茶苦茶○○した という流れになるわけで。)
使いたい言語で、サーバ機能が使えるといいですね・・(遠い眼)

例えば、POSTしたデータを、データベースに流し込みたい時。
・・Apacheで!と言われそうなので考えるのはやめておこう・・
Nginx->CGI->PHP->データベース でもよいのですが、
別枠で処理するのは無駄なので、POSTをあきらめたほうがよさそうです。
Nginx->(openresty)->データベース の環境で使いたいので。

(クエリでキャッシュが汚染されそうですが)
分解して、GETで渡してしまうのが妥当な線かな・・・と。
URIに含めたくない情報が入るのが難点ですが。
(特に、間にCDNがはさまっている場合。)

参考ページ:
Stackoverflow
POST request not allowed - 405 Not Allowed - nginx, even with headers included


めも1:
openresty/lua-resty-upload
multipart/form-data で送る。
form:read() で、こける。


めも2:
ngx.req.get_post_args() とか、
ngx.req.get_body_data()
なら行ける。
application/x-www-form-urlencoded で送る。

ただし、外からアクセスするためのlocationに、
POSTされたデータの受け口を置かないといけないので、
データベースアクセス用のlocationと分けている場合は、使いにくい。
(外からアクセスされる側でGET用のクエリなどを組み立てて、
データベースアクセス用のlocationで、クエリを分解しないといけないので。
大きなデータの場合は、ファイルに落としておいて、パスを渡すなど。)

・・と思いましたが、GETのクエリが残る現象から、
(locationの中で変更して@nameに渡しても、変更前のクエリが渡る)
POSTも残っているのでは?と思って実験してみたら、
しっかり残っていました。
外からアクセスされる側のlocationでPOSTを受けられるようにしておいて、
(外からアクセスされる側のlocationでngx.req.get_post_args()などを実行した後に)
再度、データベースアクセス用のlocationでPOSTのデータを読めば良いようです。
※外からアクセスされる側で、ngx.req.read_body()しただけでは、エラー405が発生します。
ちなみに、外からアクセスされる側で、ngx.req.discard_body()しても、
データベースアクセス用のlocationでngx.req.get_post_args()すると、
データが取得できました。

テスト用。

init_by_lua '
    cjson = require "cjson"
';
# 外から見える部分。
# フォームのデータを受け取ります。
# ngx.req.get_post_args()の行をコメントアウトすると、エラー405になるはず。
# argsを分解するのが面倒なので、cjsonに流し込む・・という手抜き仕様です。
location ^~ /test/ {
    default_type application/json;
    content_by_lua '
    ngx.req.read_body()
    local args, err = ngx.req.get_post_args()
    ngx.req.discard_body()
    if not args then
        ngx.say("{}")
        do return end
    end
    --ngx.say(cjson.encode(args))
    -- DO NOT use ngx.say() before ngx.exec()
    -- Call another(internal) location
    ngx.exec("/for/db/")
    ';
}
# 内部用(データベース用など)
# @name にすると、GETのパラメータが外れる(外からアクセスした時のものになる)ので注意しましょう。
# 動作確認用なので、データを表示したら終了するコードです。
location ^~ /for/db/ {
    interrnal;
    default_type application/json;
    # Get POST body data
    content_by_lua '
    ngx.req.read_body()
    local args, err = ngx.req.get_post_args()
    ngx.req.discard_body()
    if not args then
        ngx.say("{}")
        do return end
    end
    ngx.say(cjson.encode(args))
    ';
}

おまけ:
参考: nginxのproxy_passの注意点

proxy_passで、サーバの(IP)アドレスだけを指定する場合と、後続のパスがある場合で動作が変わります。
アドレスだけを指定した場合は、アドレスを付け替えます。
パスがある場合は、パスを付け替えます。

http://example.com/some/place/fooにアクセスした時、
例1:
location ^~ /some/place/ {
proxy_pass http://127.0.0.1
}

の場合は、http://127.0.0.1/some/place/fooに接続します。
※IPアドレスだけを付け替えるイメージです。

例2:
location ^~ /some/place/ {
proxy_pass http://127.0.0.1/
}

の場合は、http://127.0.0.1/fooに接続します。
/some/place//に置き換えるイメージです。

例3:
location ^~ /some/place/ {
proxy_pass http://127.0.0.1/other/place/
}

の場合は、http://127.0.0.1/other/place/fooに接続します。
/some/place//other/place/に置き換えます。

例4:
location ^~ /some/place/ {
proxy_pass http://127.0.0.1/other/place
}

の場合は、http://127.0.0.1/other/placefooに接続します。
/some/place//other/placeに置き換えます。
はい・・予想外です!
とならないように注意しましょう。

えっと、お約束のあれを。
サーバの数珠つなぎでの情報共有をなくそう。 あっ、Webって・・気づかなかったことに。

18
17
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
18
17