読者です 読者をやめる 読者になる 読者になる

mod_rewriteの考え方。

Apache

を見ながら。

  • URLが正規表現(A)にマッチし、かつ
  • 文字列(B)が条件(C)を満たす場合に、
  • URLを(D)に書き換える。

というのが基本。

RewriteRule

URLが(A)正規表現にマッチしたら(D)で書き換える。

正規表現(A)は、リライトを実行するかどうかの条件(真偽値)であって、置換 url =~ s/(A)/(D)/ ということではない。たとえば、以下のような正規表現でリライトされる。

# 1文字マッチしたらリライト実行。空文字列でなければ実行する。
RewriteRule . index.php [L]
# 先頭にマッチしたらリライト実行。常に実行する。
RewriteRule ^ - [L]

よく見かけるのは .* だが、$0で後方参照するのでなければ全体にマッチする必要はないと思う。先頭にマッチ^でも十分なはず。理由があるなら知りたい。

# dispatch.phpへのアクセスでなければdispatch.phpにアクセスする。QSAでクエリー文字列を足す。.*で全体をマッチさせる必要ある?
RewriteCond %{REQUEST_URI} !dispatch\.php$
RewriteRule .* dispatch.php [L,QSA]
# 認証ユーザを環境変数に設定。.*で全体をマッチさせる必要ある?
RewriteEngine on
RewriteRule .* - [E=REMOTE_USER:%{HTTP:Authorization},L] 

^(.*)$も大量に見つかるが、カッコの意味はあるんだろうか。

# .*も()も無駄では?
RewriteRule ^(.*)$ dispatch.fcgi [QSA,L] 
# .*も()も無駄では?
RewriteRule ^(.*)$ ./inc/bx/php/UserFile.php?file=$1 

$1の代わりに$0が使えるし、%{REQUEST_URI}が使える。

RewriteRule .* index.php/$0 [PT,L]
# mod_balancerとの連携。ファイルが無かったらmongrel_clusterに送る。
# .*も()も無駄では?
RewriteCond %{DOCUMENT_ROOT}/%{REQUEST_FILENAME} !-f
RewriteRule ^(.*)$ balancer://mongrel_cluster%{REQUEST_URI} [P,QSA,L]

ちなみに、abでベンチマークを取ってみたが、^でも^(.*)でも速度はまったく変わらなかった。無意味な () はどうかと思うが .* は読みやすくていいのかも。


(D)に指定できるのは

  • ファイルのパス /www/index.html
  • URLのパス /www/index.html ファイルのパスと区別がつかない。→/wwwがファイルシステムに存在するかどうかで判断。
  • 絶対URL http://www.example.com/example.html → ホスト名が一致したら取り除いてURLパスに。でなければリダイレクトかプロキシ。
  • ハイフン(置換しない) →フラグの操作だけを実行したい場合に使う。

フラグ[F1]でURLの置換を超えた操作を指定できる。

  • L 打ち止め。このURLにはこれ以上RewriteRuleを適用しない。
  • R リダイレクト
  • P プロキシ

とか。

RewriteCond

正規表現(A)に追加する条件。複数個指定できる。

  • URLが(A)にマッチし、かつ、RewriteCondを全て満たしたら(D)に書き換える。
  • (B)はHTTPヘッダ、環境変数正規表現(A)(C)の後方参照 $1/%1 など。
  • (C)正規表現、文字列の比較<=>、ファイルテスト(-f)など。

フラグ[F2]で条件の動作オプションを指定できる。

  • OR 通常ANDで接続するところをORでつなぐ。
  • NC 大文字小文字を区別しない。

ドメイン名の正規化

www.example.jp と example.jp と www.example.comexample.com にリダイレクトしたい場合。名前ベースのバーチャルホストなら、

<VirtualHost *:80>
  ServerName  example.com
  ServerAlias example.jp
  ServerAlias www.example.com
  ServerAlias www.example.jp

  RewriteEngine On
  RewriteCond %{HTTP_HOST}   !^example\.com$ [NC]
  RewriteRule ^/(.*)         http://example.com/$1 [L,R=301,NE]
</VirtualHost>

のように設定する。


以下にApacheオススメの設定(以下ガイド版と呼ぶ)がある。上はこのガイド版を修正したもの。

RewriteCond %{HTTP_HOST}   !^fully\.qualified\.domain\.name [NC]
RewriteCond %{HTTP_HOST}   !^$
RewriteRule ^/(.*)         http://fully.qualified.domain.name/$1 [L,R,NE]


各フラグの意味は、

  • NC 大文字小文字を無視
  • L 変換打ち切り(確定)
  • R リダイレクト
  • NE エスケープしない

1行目、否定の ! がポイント。正規ドメイン名以外にマッチした場合に書き換える。否定パターンを書く方がシンプルで更新も楽。非正規ドメイン名をServerAliasで好きなだけ足せる。

2行目はひとまず置いておいて、3行目。NEがポイント。NEを付けないと、

にリダイレクトされてしまう。%が%25にエスケープされるのを止めるには、NEが必要。

例えば、今日(2009-08-31)現在、

にリダイレクトされる。NEの付け忘れでは。

修正箇所

ガイド版から修正したのは、

の3カ所。

2行目の謎

ガイド版の2行目がよくわからなかった。

RewriteCond %{HTTP_HOST}   !^$

空のHTTP_HOSTは置換対象から外している。

HTTP_HOSTはどんな場合に空になるのか。ソースを見ると、

HTTP_HOSTには、Hostヘッダの値が設定されているようだ。

考慮すべきこと。

  • HTTP/1.0ではHostヘッダは送られないかもしれない。
    • IE2は送らなかった(らしい)
  • HTTP/1.1ではHostヘッダは必須。
    • ただし「もし、リクエストされた URI がリクエストされているサービスのインターネットホスト名を含んでいなければ、Host ヘッダフィールドの値は空にしなければならない。」これはどういう場合?
    • 原文: If the requested URI does not include an Internet host name for the service being requested, then the Host header field MUST be given with an empty value.
  • Request-URIがAbsoluteURIの場合、HTTP/1.1の仕様でhostヘッダは無視されるハズ。
  • Hostヘッダを送らず、リダイレクトを正常に処理するクライアントがあった場合、HTTP_HOSTが空の場合を除外しないと、ループするかもしれない。

特にそのドメインでアプリケーションを動かしている場合、Hostヘッダの有無を考慮したテストなんかできるはずがない。Hostヘッダがないリクエストには別のコンテンツを返しておくのがよい。

という理由で、Apache設定の2行目は外した。


RewriteRuleが連鎖すると、入力が空文字列になることがある。

RewriteRuleでURLを空文字列に書き換えられる。

RewriteRule .  ""            # URLが空文字列に書き換わる
RewriteRule .  /abc.html [L] # 任意の1文字。このルールにはマッチしない。
RewriteRule .* /xyz.html [L] # 0文字以上。これにマッチする。[L]で終了。

一番上のRewriteRuleで空文字列になり、2行目にはマッチしない。3行目の/xyz.htmlにリクエストが送られる。

RewriteRule ^ ""

だけだと400 Bad Requestになっていた。RewriteLogで空文字列に書き換わっているのが確認できる。

127.0.0.1 - - [14/Jun/2009:05:44:38 +0900] [default.local/sid#1008de948][rid#10096fe98/initial] (3) applying pattern '^' to uri '/index.html'
127.0.0.1 - - [14/Jun/2009:05:44:38 +0900] [default.local/sid#1008de948][rid#10096fe98/initial] (2) rewrite '/index.html' -> ''
127.0.0.1 - - [14/Jun/2009:05:44:38 +0900] [default.local/sid#1008de948][rid#10096fe98/initial] (2) local path result: 

REQUEST_FILENAMEのナゾ

ドキュメントに%{REQUEST_FILENAME}はファイルシステムのフルパスだというようなことが書いてあるが正しくない。コンテキストによってREQUEST_FILENAMEの内容が変わる。らしい。

バグトラックでは以前から言われている。


この記事は書きかけです。