nginx でトレイリングスラッシュなしの URL に POST するとリクエストボディが欠落する
nginx におけるトレイリングスラッシュの有無と POST リクエストにまつわる話。
目次
発端
nginx サーバの次のような位置に PHP を配置した。
http://example.com/example/index.php
この index.php
は、GET リクエストすると普通の HTML を返す。POST リクエストすると、ちゃんとそのデータを受け取ってレスポンスできる、というモノになっている。
/index.php
という部分は、nginx の index
ディレクティブにより、トレイリングスラッシュ (末尾のスラッシュ) なしでアクセスしても、キチンと /index.php
が返るようになっている。
# nginx の設定ファイル
server {
# …中略…
location / {
root /var/www/html;
index index.html index.php; # ← コレのおかげで…
}
}
# 以下のどれでも同じように GET リクエストできている
$ curl -X GET http://example.com/example/index.php
$ curl -X GET http://example.com/example/
$ curl -X GET http://example.com/example
example/
で終わっているのが「トレイリングスラッシュあり」、example
で終わっているのが「トレイリングスラッシュなし」と呼ばれる URL 形式だ。
コレで /index.php
を書かずに済むならスッキリするなーと思ったのだが、POST してみてつまづいた。
# /index.php まで指定した場合。リクエストボディを認識しているテイ
$ curl -X POST -d 'my_param=example' http://example.com/example/index.php
Hello [example] !
# トレイリングスラッシュありでも。リクエストボディを認識している
$ curl -X POST -d 'my_param=example' http://example.com/example/
Hello [example] !
# トレイリングスラッシュなし
$ curl -X POST -d 'my_param=example' http://example.com/example
<html>
<head><title>301 Moved Permanently</title></head>
<body>
<center><h1>301 Moved Permanently</h1></center>
<hr><center>nginx/1.16.1</center>
</body>
</html>
お!?
トレイリングスラッシュなしのパターンだけ、301 のリダイレクトが発生している…。
じゃあリダイレクトして確認してみるか、と思うと…
# トレイリングスラッシュなし・リダイレクト (-L) とデバッグ出力 (-vvv) を有効にする
$ curl -X POST -d 'my_param=example' -L -vvv http://example.com/example
# デバッグログ抜粋
< HTTP/1.1 301 Moved Permanently
< Location: http://example.com/example/
* Issue another request to this URL: 'http://example.com/example/'
* Violate RFC 2616/10.3.2 and switch from POST to GET
# GET リクエストのレスポンスが返される
なんと、トレイリングスラッシュありの URL にリダイレクトしつつ、POST メソッドが GET メソッドに変換されているようだ。コレでは POST リクエストが通らないだろう。
トレイリングスラッシュ「なし」から「あり」にリダイレクトする挙動は元々の仕様らしいが、その中に「POST リクエストだった場合は GET リクエストに変換する」という動きがあるようだ。
- 参考 : URLの最後に付ける「トレイリング スラッシュ」ありなしの違いはSEOに影響するのか? | 海外SEO情報ブログ
- 参考 : php - Curl : Violate RFC 2616/10.3.2 and switch from POST to GET - Stack Overflow
解決策
ドンズバな解決策がネット上では見つからず、自分で編み出した解決策を紹介する。
# nginx の設定ファイル
server {
# …中略… (今までの設定部分はそのまま)
# トレイリングスラッシュなしで POST リクエストされる URL パスを指定する
location ~ ^/example$ {
return 307 $scheme://$host/example/index.php?$args;
}
}
HTTP 307 で /index.php
まで付与した URL をレスポンスするように指定している。
- 参考 : 307 Temporary Redirect - HTTP | MDN
-
307 はリダイレクトされたリクエストが行われるときに、メソッドと本文が変更されないことが保証される
-
- 参考 : 302 Found - HTTP | MDN
-
POST メソッドのままリダイレクトする場合は代わりに 307 Temporary Redirect (こちらでは明確にメソッドの変更が禁止されている) を使用することが推奨されています。
-
http
でも https
でも認識するように $scheme
変数を使い、Public IP 直打ちでも独自ドメインでも CORS 制約に引っかからないようにするために $host
変数を使っている。$args
はリクエストパラメータが付いていた場合の制御だが、なくても大丈夫かと。
POST リクエストを処理したい URL ごとにコレを用意しないといけなくて、ちょっと微妙な気がしているが、そんなに大量の設定を nginx で持たないように、nginx はプロキシ的に軽く使うのが本来は推奨なんだろうなーと思った。
nginx の設定ファイルはまだまだ勉強していかないとよく分からないぞー。
- 参考 : nginxでPOSTメソッドのリダイレクトを行う - Qiita
- 307 によるリダイレクトが参考になった
- 参考 : 書き換えによってHTTP POSTリクエストを転送するには、nginxを取得するにはどうすればよいですか?
proxy_set_header
とか出てきてる、別のローカルサーバに転送する前提っぽい
- 参考 : nginx rewrite POST request - Stack Overflow
proxy_pass
- 参考 : laravel 4 - nginx rewrite post data - Stack Overflow
- 参考 : nginxのrewriteを使ったリダイレクト | Skyarch Broadcasting
rewrite
・last
で再検索させる方法なども試したが、キレイにいかなかった