先日、Redmine の Googlebot 対策として Apache の mod_rewrite を使った RewriteRule を追加しました。

まずは CPU 使用率を下げることが最優先だったため、とにかく確実に動くことを重視して場当たり的に正規表現を追加しました。

なので、今回は、RewriteRule を書き直しながら改めて感じた、「正規表現は短く書くためではなく、保守しやすくするための道具」という話です。

動けばいい。だが、そんな状態では後でメンテしづらい

最初に書いた設定はこんな感じでした。

RewriteRule ^/projects/.*/knowledgebase - [G,L]
RewriteRule ^.*/knowledgebase - [G,L]

RewriteCond %{HTTP_USER_AGENT} (Googlebot|GoogleOther|bingbot|Baiduspider) [NC]
RewriteRule ^/(projects/[^/]+/)?issues\.pdf$ - [G,L]

RewriteCond %{HTTP_USER_AGENT} (Googlebot|GoogleOther|bingbot|Baiduspider) [NC]
RewriteRule ^/(projects/[^/]+/)?issues\.csv$ - [G,L]

RewriteCond %{HTTP_USER_AGENT} (Googlebot|GoogleOther|bingbot|Baiduspider) [NC]
RewriteRule ^/(projects/[^/]+/)?issues\.atom$ - [G,L]

もちろん、これでも動きます。むしろトラブル対応中であれば、このくらい分かりやすく書いた方が安全です。

ただ、冷静になって眺めてみると、

「同じことを何回も書いている」

ことに気付きました。

正規表現とは何か

そこで、さらに正規表現で直していきます。「正規表現」という言葉を聞くと、難しそうな印象を持たれる方も多いかもしれませんが、思ったよりも単純なルールでできます。

例えば、

issues.pdf
issues.csv
issues.atom

この3つは末尾だけが違います。

これを

issues\.(pdf|csv|atom)

と書けば、

.pdf でも .csv でも .atom でも一致する」

という意味になります。MtGで言うなれば、

  • 日没を遅らせる者
  • 時を解す者
  • ドミナリアの英雄

につく「テフェリー」は

(日没を遅らせる者|時を解す者|ドミナリアの英雄)、テフェリー

と書けます。このように、共通点があるものを一つのルールで表現する。それが正規表現です。

書き直した結果

最終的には次のような形に整理しました。

RewriteCond %{HTTP_USER_AGENT} (Googlebot|GoogleOther|bingbot|Baiduspider) [NC]
RewriteCond %{REQUEST_URI} ^/(projects/[^/]+/)?issues\.(pdf|csv|atom)$ [NC,OR]
RewriteCond %{QUERY_STRING} (^|&)(sort|set_filter|per_page)= [NC]
RewriteRule ^/(projects/[^/]+/)?issues - [G,L]

PDF、CSV、Atom を一つの正規表現へまとめ、さらに「動的クエリ」と「エクスポート要求」を一つの RewriteRule で処理できるようにしています。

行数だけを見ると少し減った程度です。

ですが、保守性はかなり向上しました。

保守性を求めた正規表現

今回の目的は「短く書くこと」ではありません。

例えば将来、

issues.json

も遮断対象にしたくなったとします。以前の書き方なら RewriteRule を一本追加します。この書き方であれば

(pdf|csv|atom|json)

と一か所を書き換えるだけで済みます。変更箇所が一つだけになる。これは保守する上でかなり大きな違いです。

Knowledgebaseも同じ考え方で整理。

Knowledgebase の RewriteRule も整理しました。

以前は

RewriteRule ^/projects/.*/knowledgebase - [G,L]
RewriteRule ^.*/knowledgebase - [G,L]

のように複数行で書いていました。これを

RewriteRule ^/(home/www-data/|(.*/)?knowledgebase) - [G,L]

という一つのルールへまとめています。さらに、このルールでは以前問題になった

/home/www-data/...

のような物理パスへの誤アクセスも同時に処理しています。

「Knowledgebaseだけ」ではなく、「Rails に渡したくないもの」という視点で整理し直した結果です。

「同じ意味のもの」をまとめる

今回 RewriteRule を整理していて感じたのは、正規表現を書くことが目的ではないということです。目的は、「同じ意味を持つものを一つのルールで表現する」ことでした。

  • Knowledgebase は「存在しない旧機能」です。
  • PDF、CSV、Atom は「重いエクスポート」です。
  • sort や set_filter は「重い動的クエリ」です。

そう考えると、設定ファイルも「何を止めたいのか」が分かる構成になっていきます。

未来の自分が読める設定にする

サーバー設定は、一度書いたら終わりではありません。半年後、一年後あるいは障害対応中の深夜に、また自分が読むことになります。

その時に

「この RewriteRule は何を止めているんだっけ?」

と悩むようでは、あまり良い設定とは言えません。もちろん、正規表現は凝ろうと思えばいくらでも複雑にできます。ですが、複雑さは必ずしも保守性につながりません。

今回の書き直しで目指したのは、「短い設定」ではなく、 「意図がまとまっていて、後から見ても理解しやすい設定」 でした。
正規表現は、そのための道具なのだと改めて感じた次第です。