アクセスされたURLが例えば「http://example.jp/foo/?」のように「?」だけで他のクエリパラメータがついていない場合にだけ、mod_rewriteで「http://example.jp/foo/」に直したいという要望がありました。
このURLはクエリが渡される場合があるため、単にクエリを全部消す、というだけでは満たされない条件です。
RewriteRuleがマッチングする文字列にはクエリ文字列が含まれないため単純に
RewriteRule (.*/)\?$ $1 [L,R]
のように末尾の「/?」とマッチさせるように書いても、「/foo/」のように末尾の「?」がないものと比較されてしまうのでマッチしません。
REQUEST_URIやREQUEST_FILENAMEにもクエリ文字列は含まれていないため、検知できません。
じゃあとQUERY_STRINGと比較しても、QUERY_STRINGは「?」以降の文字列になるため、QUERY_STRINGが空というだけでは「?」が有って空なのか、無くて空なのかがわからないのです。
ということで、REQUEST_URIとQUERY_STRINGが分割されていないものはないか、と探したところ「THE_REQUEST」に
GET /index.html HTTP/1.1
のように入っていることがわかりました。
mod_rewrite - Apache HTTP Server Version 2.4
そこでTHE_REQUESTに対して下記のような条件で引っ掛ければ検出できることがわかりました。
RewriteCond %{THE_REQUEST} " .*/\? "
が、この条件で引っ掛けてREQUEST_URIに対してリダイレクトさせると、勝手にクエリ文字列を追加してくれてしまうため、結局「?」が追加されてしまい元の木阿弥に…
そこでクエリ文字列を引き継がない設定を探すと
mod_rewriteでクエリ文字列(/?q=)を引き継がずにURL置換 | ええかげんブログ(本店)
クエリ文字列を削除するには「置換文字列の最後をクエスチョンマークにする」
とすれば良いとのことでした。
これだとクエリがついていてもすべて削除されてしまうのですが、末尾が「?」で終わっている条件だからクエリは空なので、クエリ文字列自体を消してしまって大丈夫です。
なので、次のようなルールで動くようになりました。
RewriteCond %{THE_REQUEST} " .*/\? " RewriteRule .* %{REQUEST_URI}? [L,R]
んで、これをtwitterで相談していたところ、ふみやすさんから
と教えていただきました。ありがたや!!
リダイレクトは正しくはフルで書いてないとダメなのですね。
ということで最終的には
RewriteCond %{THE_REQUEST} "/\? " RewriteRule .* http://%{HTTP_HOST}%{REQUEST_URI}? [redirect=301,last]
という形になりました。
しかし… 単にURL末尾の「?」消したいだけなのに、だいぶバッドノウハウ感の高いルールとなってしまいました。
なんかもそっと簡単に書ける方法はないもんなんでしょうかね。
(参照)
mod_rewriteの考え方。 - こせきの技術日記
Apacheのmod_rewriteモジュールの使い方を徹底的に解説 | OXY NOTES
mod_rewrite - Apache HTTP Server Version 2.4
mod_rewriteでクエリ文字列(/?q=)を引き継がずにURL置換 | ええかげんブログ(本店)