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って・・気づかなかったことに。