それマグで!

知識はカップより、マグでゆっくり頂きます。 takuya_1stのブログ

習慣に早くから配慮した者は、 おそらく人生の実りも大きい。

Apacheのmod_rewriteによるURL書換についてのまとめ(3)RewriteMapと、Mapスクリプトの連携

mod_rewriteで何でも出来ると書かれてるけど

mod_rewriteはスイスアーミーナイフ、mod_rewriteは黒魔術、mod_rewriteで何でもできる。と書かれてるけど、実際のところコピペが多くて仕組みがよく分かんないです。

Rewiteリクエスト処理へなんでもアリと呼称されますが、思い通りになりません。悲しい。だから調べてます。

前回までで

RewriteRuleの処理、RewriteCondの書き方、組み合わせて複数書くところまで調べた。今回は複数記述を省略できるRewriteMapを調べた。

RewriteMapを使ってみる。

RewriteMapはRewriteRuleやRewriteCondをたくさん書かずに済む、設定集。RewriteMapに記述すれば、RewriteRuleを連発しなくて済むようになってます。

RewriteMapの下準備

RewriteMapを使う前に、実験環境を整えておきます。カンタンなRewriteRuleを書いておきます。

ディレクトリ構造はこんな感じ
takuya@~/Sites/rewrites$ tree . -a
.
├── ex1
│   ├── .htaccess
│   ├── data.txt 
│   └── index.php
└── get.php
.htaccess ファイルに次のように記述
RewriteRule  (.*)   ../get.php?from=ex1&map=$1
RewriteRuleによる書換が行われることを確認。
takuya@~/Sites/rewrites/ex1$ curl    http://localhost/~takuya/rewrites/ex1/Foooooo
array(2) {
  ["from"]=>
  string(3) "ex1"
  ["map"]=>
  string(7) "Foooooo"
}

リクエストの末尾のディレクトリ(ファイル名)が書き換えられ、URLに埋め込まれるカンタンなものです。

これをベースにRewriteMapを使おうと思います。

最初の書換の目標

最初にやってみるRewriteMapによる書換は、URLのファイル名が来たら、それを*書換えてクエリ文字列に埋め込みたいと思います。

http://localhost/~takuya/rewrites/ex1/Foooooo
↓
http://localhost/~takuya/rewrites/get.php?from=ex1&map=Mapで置換済み文字列

RewriteMap の準備

次のようなハッシュマップを用意します、ファイル名に指定したらRewriteMapがURLに書き換え処理を走らせたい。

とりあえず、サイト名を入れたら、ドメインを返すように設定を書くようにする。

Mapによる書換文字列
yahoo www.yahoo.co.jp
mixi  mixi.jp
google www.google.com
apple www.apple.com
twitter t.co

このファイルをRewriteMapとして登録する。

RewriteMap は .htaccessでは動作しません。それは、起動時に読み込まれ展開されるからです。

そこで、/etc/apache2/users/takuya.conf 、次の記述を追加しました。

/etc/apache2/users/takuya.conf
RewriteMap titles "txt:/Users/takuya/Sites/rewrites/ex1/data.txt"
<Directory "/Users/takuya/Sites/rewrites/ex1" >
RewriteEngine On
</Directory>

もし動的に指定出来たら、RewriteMap指定したファイルが存在しない時に、エラーになっちゃいますし。 動的に出来るならセキュリティ・ホールに・・・存在しないファイル読みに行くのは怖いですね。また侵入者が.htaccess書き換えてゴニョゴニョできちゃうもんね。。(個人的には動的Mapでもイイと思うけど。

RewriteRuleでRewriteMapを使うように設定する。
RewriteRule  (.*)   ../get.php?from=ex3&map=${titles:$1|no_match}
書換処理を確認

RewriteRuleを書きました。つぎにリクエストを実行してみます。

takuya@~/Sites/rewrites/ex1$ curl    http://localhost/~takuya/rewrites/ex1/auction
array(2) {
  ["from"]=>
  string(3) "ex3"
  ["map"]=>
  string(8) "no_match"
}
takuya@~/Sites/rewrites/ex1$ curl    http://localhost/~takuya/rewrites/ex1/mixi
array(2) {
  ["from"]=>
  string(3) "ex3"
  ["map"]=>
  string(7) "mixi.jp"
}

無事にMapの定義通りハッシュマップを通して書き換わっているのがわかります。

RewriteMapの記述方法

わかりやすく日本語で表記すると、次のようになりました。

RewriteMap 名前 "タイプ:ファイル名"

Rewrite の タイプにはいくつかある。

タイプ 説明
txt スペース区切りの一覧表
rnd txt で値が複数指定されたものがランダムで選択
prg 独自のプログラムを使う
dbm SDBM/GDBM などが使える
dbd SQL でデータベースにアクセス

RewriteMapは関数

RewriteMapは関数だと解釈すれば理解が容易だと思います。

RewriteMap titles "type:source"

これを関数と考えておく。

var  titles = function (type,  source){..}

ということで、RewriteMapとRewriteRuleを組み合わせると

関数の定義と、

RewriteMap titles "txt:/Users/takuya/Sites/rewrites/ex1/data.txt"
RewriteRule  (.*)   ../get.php?from=ex3&map=${titles:$1|no_match}

RewriteMap type TXT の動作イメージを関数で表現してみた。

//RwriteMap
var titile =  (function ( source )
     var data = File.load(source);
     return function( key ){
           return  data[key] || ""
     })( "/Users/takuya/Sites/rewrites/ex1/data.txt" )
//RewriteRule
if ( REQUEST_URI.match(//)) ){
     REQUEST_URI.replace( "./get.php?from=ex3&map=" +  title( match[1]  || "default_value" ) )
}

プログラムで表現しても絶対同一にはならないので、そのへんはご容赦下さい。理解の助けのためのイメージ図描くよりプログラムで表現しほうが、私は理解しやすかったです。

RewriteMapでRandomもやってみる

rnd を指定したら、ランダムに結果を返してくれるので、ランダムの結果を表示するようにしてみる。

httpd.conf
RewriteMap titles "rnd:/Users/takuya/Sites/rewrites/ex1/data.txt"
<Directory "/Users/takuya/Sites/rewrites/ex1" >
RewriteEngine On
</Directory>
.htaccess
RewriteRule  (.*)   ../get.php?from=ex3&map=${titles:$1|no_match}
書換データベースを用意する
takuya@~/Sites/rewrites/ex1$ cat data.txt
じゃんけん グー|チョキ|パー
リクエストを送ってみる。
takuya@~/Sites/rewrites/ex1$ curl    http://localhost/~takuya/rewrites/ex1/グー
array(2) {
  ["from"]=>
  string(3) "ex3"
  ["map"]=>
  string(6) "パー"
}
takuya@~/Sites/rewrites/ex1$ curl    http://localhost/~takuya/rewrites/ex1/パー
array(2) {
  ["from"]=>
  string(3) "ex3"
  ["map"]=>
  string(9) "チョキ"
}

PHPを書かなくても設定だけでランダム動かせるとかテンション上がりますね。

RewriteMapランダム何に使うの。。。

何に使うといえば、、、うーん。負荷分散とかに使えそう。

プロキシと組み合わせてランダム動かせば、負荷分散に役立ちそう。まぁ通常はDNSラウンドロビンをするだけろうけど。。。

RewriteMapでプログラムを使う。

RewriteMapがプログラムを定義できるので、プログラムでRewriteCondで使ってみる。

RewriteMapが単なる関数だと考えたら、プログラムを呼び出す関数を作れば自由にできる。それがprg。

httpd.conf
RewriteMap sample1 "prg:/Users/takuya/Sites/rewrites/ex1/sample1Map.rb"
<Directory "/Users/takuya/Sites/rewrites/ex1" >
RewriteEngine On
</Directory>
.htaccess
RewriteRule  (.*)   ../get.php?from=ex3&map=${sample1:$1|no_match}
書換の関数。

ここがメイン部分。RewriteMapにより呼び出される側のプログラム

#!/usr/bin/env ruby

require 'logger'
log = Logger.new("/tmp/ruby_rewrite_map.log")

$stdin.sync = $stdout.sync = true

loop{
  line = $stdin.gets #ここでブロックされる。Blocking IOになる。
  line.force_encoding("utf-8") # 日本語URLも来るのでエンコード強制
  line = line.strip                     # 改行消す

  log.info( "READ #{line}" )

  if( line.match(/免許/i ) )
    $stdout.puts "早く免許取るんだ"
  else
    $stdout.puts "あ、なんでもない"
  end
}

RewriteMapから呼び出されるプログラムは、STDOUT/STDINを使ってデータの受け渡しをする。

一行書き込まれたら、一回のリクエスト。ソレに応答して一行書いたら終わり。

IOのREAD待ちでブロッキングされるので、無限ループで処理して問題ない。

プログラムで書換テスト

リクエストを送ったら返してくれるのを確認する。

takuya@~/Sites/rewrites/ex1$ curl   http://localhost/~takuya/rewrites/ex1/免許
array(2) {
  ["from"]=>
  string(3) "ex3"
  ["map"]=>
  string(24) "早く免許取るんだ"
}
takuya@~/Sites/rewrites/ex1$ curl   http://localhost/~takuya/rewrites/ex1/コーヒ
array(2) {
  ["from"]=>
  string(3) "ex3"
  ["map"]=>
  string(24) "あ、なんでもない"
}

RewriteCondでRewriteMapを使う。

RewriteMapで定義したマッピングは、RuleでもCondでもどちらでも使える。

たとえば、特定のヘッダをチェックしてようと思います。

Cookieチェックしてアクセス禁止

Cookieの値をチェックしてアクセス禁止して見るサンプルを書いてみようと思う。

httpd.conf
RewriteMap sample2 "prg:/Users/takuya/Sites/rewrites/ex1/cookie_check.rb"
<Directory "/Users/takuya/Sites/rewrites/ex1" >
RewriteEngine On
</Directory>
.htaccess
RewriteCond  ${sample2:%{HTTP_COOKIE}|none}  !^OK$
RewriteRule  ^   - [F]
書換プログラム
#!/usr/bin/env ruby

require 'logger'
require 'cgi'

$stdin.sync = $stdout.sync = true

log = Logger.new("/tmp/ruby_rewrite_map.log")

loop{
  line = $stdin.gets #ここでブロックされる
  line.force_encoding("utf-8")
  line = line.strip
  log.info( "READ #{line}" )

  params = CGI::parse line

  log.info( "params #{params}" )
  if( params.has_key? "login" )
    log.info("write OK")
    $stdout.puts "OK"
  else
    log.info("write NG")
    $stdout.puts "NG"
  end

}

リクエストしてみる。

Cookie無しでアクセスしてみた結果。
takuya@~/Desktop$ curl  -I  http://localhost/~takuya/rewrites/ex1/index.php
HTTP/1.1 403 Forbidden

Cookieがないのでアクセスを制限された!

Cookieつけてアクセスをした結果。
takuya@~/Desktop$ curl -I -b "login=1"  http://localhost/~takuya/rewrites/ex1/index.php
HTTP/1.1 200 OK

アクセスできる

RewriteMap で起動したプログラムについて

RewriteMapで起動したプログラムは、プロセスとしてずっと起動しっぱなしになっています。

IO待ちでBlockされるので当然なのですが。次のようにプロセスとして常駐しています。

takuya@~/Desktop$ pstree  45145
-+= 45145 root /usr/sbin/httpd -D FOREGROUND
 |--- 45728 root ruby /Users/takuya/Sites/rewrites/ex1/sample1Map.rb
 |--- 45729 root ruby /Users/takuya/Sites/rewrites/ex1/cookie_check.rb
 |--- 45730 _www /usr/sbin/httpd -D FOREGROUND
 \--- 46225 _www /usr/sbin/httpd -D FOREGROUND

さらに、プロセスとして常駐しているので、なにかエラーが有って強制終了すると面倒なことになります。。。応答がなくなるなど。

今回は取扱しなかったMapタイプ

dbm は sdbm がよくわからなかったのでパスした。httxt2dbm で作って簡単にできる。ただしdbm ファイルをPythonRubyから取り扱う方法がわからなかったのでパス。

dbd はデータベースにアクセスで、mod_authのときに、dbd 扱ったので今回はパス

基本的な使い方はわかった。

ここまでで、基本的な使い方はわかった。

RewriteRuleでURLにマッチ、

RewriteCondで条件を補完、

RewriteCondで条件を組み合わせる

RewriteMapで条件記述をシンプルに柔軟に出来る。

とわかった。

あとは、よく使うようなRewriteのオプションを書きながら、色々考えてみたいと思う。

参考資料

先人に感謝