俺の雑記帳

My random memorandumです。(つまり、個人的な備忘録であり、その点ご容赦を。)

俺のmod_rewrite再入門

mod_rewriteは、複雑でわかりにくい。俺のはてなブックマーク(非公開)にはmod_rewriteタグがあるが、最古のものは15年も前だ!(歳を感じる)
Webばかりやってるわけではないから仕方ないが、なかなか理解しない(もしくは理解しても しばらく使わなかったりして忘れる)。
※ 10年も前にも記事(というかリンク集の備忘)を書いているが(mod_rewrite - 俺の雑記帳)、今回参照していない…。

.

復習

昔のブックマークを確認すると、 mod_rewrite 以外に、シンプルな用途で(恐らくもう少し一般的に使われている)mod_alias系のコマンドがある。

www.neobit.jp

  • mod_alias: Redirect, RedirectMatch, Alias, AliasMatch (最後の二者は...confのみで.htaccessには書けん)。
  • mod_redirect: RewriteRule, RewriteCond。

.
なお、nginxでも Apacheからの移行ユーザのため、これらの構文が使えるらしい。(mod_rewriteのほうは、(理解していないで使われるからか)Apache同士で移行しても動かなくなったりするので、難しいかも。)

今回書いたものを例示 ― 段階的に新サイトへ移行するケース(多言語サイト)

/gl/.htaccess

#・・・(前略)・・・#
RewriteEngine On
RewriteBase /
#・・・(中略)・・・#
#############################################
### 20221111- test for the redirection to the new site.   ###
#############################################
 #試行錯誤の記録
  #これは効くが、RewriteRule一つ一つに書かなくてはならないので、不採用。
   #RewriteCond %{HTTP_HOST} ^test56\.xxx\.co\.jp$
  #以下のようなREQUEST_URIのRewriteCondが効かない?@test56.xxx.co.jp。ただ,いずれにせよRewriteRule一つ一つに書かなければならないので、【1】のRewriteRuleで(US|UK|..)のように指定するのが良いと判明。
   #RewriteCond %{REQUEST_URI} ^([A-Z]{2})/$
   #RewriteCond %{REQUEST_URI} ^US/products/03010000.html$
   #RewriteCond %{REQUEST_URI} ^US/(.*)$ [OR]
   #RewriteCond %{REQUEST_URI} ^AU/(.*)$ [OR]
   #RewriteCond %{REQUEST_URI} ^(US|UK|AU)/(.*)$
   #RewriteCond %{REQUEST_URI} ^/US/
  #ドキュメントルート(/)の.htaccessではRewriteRuleが効かない?:
   #(a)test56.xxx.co.jpで効かない。<- (mod_rewriteでは?)下位の.htaccessが優先される,という話があったのでそのせいかも。
   #(b)tmp.xxx.comで効かない。<- 内部転送は効くが外部転送が効かない。また、httpsやトレイリングスラッシュが強制される。
   #(c)xxx.comでは効く?<- Kさんのテスト結果。

#【0】旧サーバに残るページはリダイレクトさせん。>当.htaccessの最後方で行っている内部転送をここで行い【2】を効かせない。
#RewriteRule ^(US|UK|AU)/corp-pro/(\w+/)?$ - [L]   >redirect抑制はできるが当.htaccess最後方に記載の内部転送ができず404表示。[R,L]でも同じ。
RewriteRule ^(US|UK|AU)/corp-pro/(\w+/)?$ /gl/corp-pro/$2index.php?country=$1 [L]
RewriteRule ^(US|UK|AU)/corp-pro/assets/(.+) /gl/corpo-pro/assets/$2?country=$1 [L]

#【1】まず/US/等より後ろを内部転送で置換する(指定されたもののみ)。本来[L]を付けなければ,後続の【2】が実施されるはずだが,されない@test56。逆に[L]を付けると後続が効く。httpd.confと異なり.htaccessでは[L]がうまく働かないため,[L]が動作すること自体は不思議ではない([L]が付いても変更されたURLで再度上から読込み直す)。(本番にアップされても問題ないように、今はwww.xxx.comをいちいちRewriteCondeを書き除外する。)
RewriteCond %{HTTP_HOST} !^www\.xxx\.com$
RewriteRule ^(US|UK|AU)/services/(\w*)(\.html)? /gl/$1/survey/$2 [L]
RewriteCond %{HTTP_HOST} !^www\.xxx\.com$
#RewriteRule ^(US|UK|AU)/products/08000000\.html /gl/$1/products/corrugated-board [L]   > 次行参照
#RewriteRule ^(US|UK|AU)/products/08010000\.html /gl/$1/products/corrugated-board#cb01 [L,NE] < アンカーが動作せん為,08000000と統合
RewriteRule ^(US|UK|AU)/products/080[0-9]0000\.html /gl/$1/products/corrugated-board [L]
RewriteCond %{HTTP_HOST} !^www\.xxx\.com$
RewriteRule ^(US|UK|AU)/products/08010100\.html /gl/$1/products/corrugated-board/cardboard/process [L]

#【2】次に /gl/AU/ から /en-AU/ のような変換をしつつ外部転送をする。
RewriteCond %{HTTP_HOST} !^www\.xxx\.com$
RewriteRule ^US/(.*)$ https://xxx.newsite.com/$1 [R=301,L]
RewriteCond %{HTTP_HOST} !^www\.xxx\.com$
RewriteRule ^UK/(.*)$ https://xxx.newsite.com/en-GB/$1 [R=301,L]
RewriteCond %{HTTP_HOST} !^www\.xxx\.com$
RewriteRule ^AU/(.*)$ https://xxxnewsite.com/en-AU/$1 [R=301,L]
#【2】の[R,L]は、本番リリースし安定したら[R=301,L]に書き直す。301(Permanent Redirect)にすると、ブラウザがキャッシュしデバッグが難しくなるし,リリース時に誤動作したら記憶されてしまう。

# 上記の各「RewriteCond %{HTTP_HOST} !^www\.xxx\.com$」は、新サイト公開前のテスト用(本番サーバに影響させないため)
#・・・(後略)・・・#

RewriteCond

最も俺が分かってなかった(他の多くの人も最初に理解し難い(?))RewriteCondについて、まず理解する必要があった。まずこれを見て 基本的な理解を始められた。(RewriteCondを無視してRewriteRuleだけまず理解するのが良いかもだが、勘違いしやすい~Condを放置したまま ~Ruleを深く理解しようとしても、抵抗感が出てくるだろう。) ysklog.net

  1. RewriteCondは必須ではない。RewriteRule で正規表現が書くことができ、URLのパターンで変換する場合は RewriteCond不要な場合が殆ど。
  2. したがって、RewriteCondでは、URLを条件に入れることは少ない。RewriteCond %{REQUEST_URI}... はあまり使わず、他の条件(...%{HTTP_HOST}... 等)を使うことが多い。
  3. RewriteCondに影響されるのは、直後のRewriteRule一行だけ。*1
  4. RewriteCondは複数条件にできる。RewriteCondを複数行並べ、最初にRewriteRuleが出てくるまでが複合条件となる。末尾に[OR]を書くとOR条件、何も書かなければAND条件。
  5. 「RewriteCond 条件A (改行) RewriteCond 条件B [OR] (改行) RewriteCond 条件C」の場合、「A and (B or C)」となる。(cf. [Apache] RewriteCondのANDとORの優先順位 | ハックノート)

次にここを見れば、ほとんどの事が理解できるかな: azisava.sakura.ne.jp

パスの書き方

  1. .htaccessでまず書く「RewriteEngine On (改行) RewriteBase /」のRewriteBaseは、まだ理解していないので取敢えずおまじないとしておこう。(⇒いや、次の記事の項で理解した: 前述したページの"置換文字列とRewriteBase"節 。< 当社のやり方と違い、.htaccessの配置場所を指定するのが一般的の様だ。なお、正規表現には影響せず、転送先にしか効かない。)
  2. 上記1.の場合(に限るか分からんが)、RewriteCondやRewriteRule右側の正規表現は、.htaccessの配置場所から。RewriteRule左側の内部転送先はドキュメントルートから書く(外部転送はもちろんhttp(s)から)。
  3. 転送先(「置換文字列」と言うようだ)に「-」を指定すると、何もしない。通常、後続の処理をさせないために書く(次節↓参照)。

.htaccessの複雑性

  1. .htaccessでは動きが複雑 ― 前述したページの"フラグ"節 から抜粋:

    よく使われる[L]フラグは、ルール適用をそこで打ち切るものであるが、.htaccess等のディレクトリコンテキストでは、打ち切られても書き換え後のURLに対するルールが存在すれば再び書き換えが行なわれてしまうので注意が必要である。これは、.htaccessmod_rewriteを使用する際に最も多くの人が勘違いしているポイントと思われる。~ それでは[L]フラグの存在価値が無いように思われるが、大量のルールを記述した場合などに、それ以降マッチしないことが確実なURLを早い段階で排除できるので、ディレクトリコンテキストでも使用する意義はある。

  2. 後に追加された[END]でこの問題を回避。
    この辺の複雑な動作は、次の二つの記事などに詳しい:

  3. 下位ディレクトリ優先?
    .htaccess RewriteCond, RewriteRule, 各種一覧表 | いちりのテクの部屋 より抜粋:

    ディレクトリに.htaccessがある場合は下位のディレクトリの内容が優先される。 子ディレクトリの.htaccessに設定が無い場合、親ディレクトリの.htaccessの内容が継承される。

    • (Kさんへのコメント) 現に、今回、ドキュメントルートの.htaccessでは動作せず、/gl/.htaccessだと動作しました。
      つまり、/gl/.htaccessには元からいろいろリダイレクトが書いてありますので、 /.htaccess(@ドキュメントルート)だと 優先度が低いのでうまく動作しないのかも知れません。(/jp/.htaccessについては知りませんが。。。)

RewriteRule関連その他

  1. RewriteRuleでは、[R] か http(s):// 指定で外部転送。そうでなければ内部転送。
    .htaccessで[L]で2回転送しても(上記 01. 参照)、一回目が内部転送で2回目が外部転送ならブラウザやロボットには中間の転送は気づかれない。それを、冒頭のサンプルの【1】【2】で実践している。
  2. [R]フラグを利用するときは、必ず[L] を併記する必要がある。<(https://qiita.com/bibouroku/items/503d99438ddb8e3d95a1#%E5%A4%96%E9%83%A8%E8%BB%A2%E9%80%81 ("外部転送"の節))・・・確かに今回実際に動作しなかった。
  3. アンカー:
    index.html#in-the-middle-of-page のように、アンカーに飛ばしたい時は[NE]が使えるとのこと(< 【.htaccess】ディレクトリからページ内アンカーにリダイレクトする【Tips】 | オランダで生きていく しかし、サンプルに記述があるように、今回は動作しなかった。 そもそも、[NE]はNon-Escapeの略だそうで(.htaccess RewriteCond, RewriteRule, 各種一覧表 | いちりのテクの部屋 ("フラグ一覧"))、なぜアンカーに跳ばすのに使えるのか、まだ理解していない(まだちゃんと読んでいない)。
  4. [NC]で大小英字の区別なし。これは、{%HTTP_HOST}を使う際など、必要かも(必要でなくとも、付けて置くと安全?)。その他フラグや、正規表現、サーバー変数など、すべて網羅していそうな詳しいページ: ichiri.biz

デバッグ方法など

  1. ブラウザーによるDNSキャッシュに注意! .htaccess でのリダイレクト(転送)設定の書き方 | WWWクリエイターズ ("5.3 ブラウザーによるDNSキャッシュに注意!")

    例えば、Google Chrome を始めとするブラウザーは301コードの転送が成功すると、リダイレクト先のURLをブラウザ上でキャッシュしてしまいます。つまり、.htaccess で設定内容を書き換えても、一度成功した301転送先URLに(勝手に!)直接接続され続けてしまいます。 普段は表示速度を早めてくれているのですが、開発に置いてはちょっとしたおせっかいですね。 これを迂回するため、工夫としては、.htaccess の設定内容をテストしている時は、あえて、R=302と設定してデバッグを行うと、手軽にこのキャッシュ機能を迂回できます。

  2. ログ httpd.confなどに「LogLevel debug rewrite:trace2」などの書き方でログを出力できる serv-ops.com

    一番多くの情報を出力するのは rewrite:trace8 ですが情報が多くなりすぎるため、rewrite:trace2 くらいからはじめて、必要に応じて数字を大きくするのがデバッグ効率が上がります。

  3. .htaccessでリダイレクト書式のテストをできるWeb上のサービス:(前項のページ末尾で紹介され、知った。) htaccess.madewithlove.com
  4. 簡易デバッグ 正規表現のテストや、%{HTTP_HOST} などで想定した値が取れているかなどのテストをするのに、URLパラメターに出してしまえばよい。
    RewriteRule ^(.*)$ /?$1 [R,END]
    RewriteRule ^(.*)$ /?%{REQUEST_URI} [R,END]
    RewriteRule ^(.*)$ /?M=$1,RF=%{REQUEST_FILENAME},RU=%{REQUEST_URI} [R,END]
    mod_rewrite、RewriteRule のデバッグ。正規表現にマッチした値を表示する より。 ※ .htaccessがドキュメントルートに置いてある場合しかテストできないようだ。<正規表現の部分に.htaccess自身が置いてあるパスを足せばよい。
    .

*1:「RewriteCond条件に当てはまれば次のRewriteRuleが実行される」と普通捉えるが、RewriteRuleに合致してから直上のRewriteCondeの判定に回る」のが正確との事。結局何が違うのか分からないが。。。次のページなどで解説: koseki.hatenablog.com