phpでmod_rewriteもどきを作ってみた

最近よくmod_rewrite絡みを使ったり使いたかったりなんかすることが多かったりします。の割りに使っているApachemod_rewriteモジュールが入っていなかったりして非常にげんなりすることが多いので、php.htaccessを使ってそれっぽく動くものを作ってみました。

でも、たぶん実用はしないだろうなぁ。恐ろしくて

基本的な考え方

.htaccessの設定で、404 not foundの際に表示するページをphpのプログラムに差し替えます。差し替えられたphpのプログラムでは要求されたURLをパターンごとに置換、置換後のURLにHTTP経由でアクセスしてコンテンツを返しています。

非常に乱暴な動きをしますが、とりあえず動いている様子なのでOKという方向です。ちなみに今回は手元にあったphpで書いていますがphp以外でも書けると思います。

注意すること

こんなもどきなので注意することは一杯ありますが、いくつか抜き出すとこんな感じ。

転送のループには気をつけよう

下手すると転送が無限に回って面倒なことになることがあるので気をつけたいです。

一回のリクエストごとに裏でもう一回リクエストされているので気を受けよう

スーパー手抜きをして、変換後のURLのコンテンツをHTTP経由で再度呼んでいます。多分それを気にするくらいアクセスがある用途ならばApachemod_rewriteモジュールを入れるくらいはできると思いますので素直にそちらをつかったほうがマシです。

phpの設定に気をつけよう

HTTPのリクエストを再度発行するときに、手を抜いてfile_get_contentsを使っています。ので実行には、「fopen wrappers」とやらが有効になっている必要があります。詳しくはfile_get_contentsのマニュアルを参照のこと。

この部分を変えたいなら自力で実装するなり、ライブラリを使うなり頑張る必要があります。

getメソッドにしか対応してないです

普通に考えるなら、せめてpostメソッドにも対応していてほしいところですが、面倒くさかったのでgetメソッドにしか対応していません。

対応したい時は、要求されたアクションによってfile_get_contentsの部分で$_POSTを投げるように変えてやるか、ライブラリを使うか好きにする必要があります。

そもそも.htaccessを設定できるか確認しよう

プログラムの基本的な考え方が.htaccessでエラーページを差し替えられることを前提にしているので、それができなかった場合は動きません。まぁ仕方ない。

リクエストされたURLが既にアクセス可能だったら動きません

プログラムの基本的な考え方が.htaccessでエラーページを差し替えられることを前提にしているので、そもそもファイルが存在してエラーが起きなかったら動きません。まぁ仕方ない。

構文が本物とは違います

URLの置換パターンをphpのpreg_replaceに頼っているので、本式のmod_rewriteとは構文が違ってきてしまいます。本式に書きたいなら、パターンの前処理がもう一段階必要になってしまうので面倒くさかったこと、本式の書き方がいまいち良く分かっていなかったことから放っておきました。

動かす

長々と前置きを書いてきたので、動かし方の覚書。
今回は例示として「/mod_rewrite/」ディレクトリに置いたという前提。

.htaccessを記述する

|
ErrorDocument 404 /mod_rewrite/mod_rewrith.php

|<

プログラム本体を置く

/mod_rewrite/mod_rewrith.phpとして以下のプログラムを置いておく。

|php|
<?php
// 既にファイルが存在している場合は、発火しません

  // ex. htaccessの書き方  
  // file not found 時に発火する書き方  
  //  ErrorDocument 404 /mod_rewrite/mod_rewrith.php

  // forbidden時に発火する書き方  
  //  ErrorDocument 403 /mod_rewrite/mod_rewrith.php

  mod_rewrite_util::process();

  /**  
   * PHPを使ってmod_rewriteを実行するためのユーティリティクラス  
   * @author atyks  
   *  
   */  
  class mod_rewrite_util {

    /**  
     * mod_rewriteを行うためのパラメータを記述する  
     *   この分は、人手で編集すること  
     *  
     * @return array  
     */  
    function getPattern() {  
      $rules = array();

      // 以下にmod_rewriteのルールを記述  
      //  ルールを記述する際には、「/」のエスケープは不要  
      $rules['pattern'][0] = '^/mod_rewrite/search/(.*)$';  
      $rules['replace'][0] = '/mod_rewrite/index.php?action=search&param=${1}';

      return($rules);  
    }

    /**  
     * 実際の処理を行う  
     *  
     * @return null  
     */  
    function process() {  
      $_404 = ""; // 本来の404ページ  
      $url = $_SERVER['REQUEST_URI']; // リクエストされたURLを取得する  
      $host = $_SERVER['HTTP_HOST'];  
      $rules = mod_rewrite_util::getPattern();

      // 前述のパターンは、「/」をエスケープしていないので、後付でエスケープする  
      $rules['pattern'] = mod_rewrite_util::mod_rewrite_parse_pattern($rules['pattern']);

      // パターンにしたがってURLを置換  
      $url = preg_replace($rules['pattern'], $rules['replace'][0], $url);

      // URLがパターン経由後、URLが変化しなければそのまま終了  
      //  本来の404ページが指定されていれば、それを表示  
      if ($url == $_SERVER['REQUEST_URI']) {  
        if ($_404)  
          print file_get_contents(mod_rewrite_util::getUrl($_404));

        exit;  
      }

      // 404で創出されていたHTTPのステータスを上書き  
      //  本当のmod_rewriteとは異なり、返すステータスは200固定  
      header("HTTP/1.0 200 OK");

      // URLをHTTP付のものに整形して、HTTPリクエストを発行  
      //  結果を標準出力に送出  
      print file_get_contents(mod_rewrite_util::getUrl($url));  
    }

    /**  
     * ファイルのパスからURLを生成する  
     *  
     * @param string $path  
     * @return string  
     */  
    function getUrl($path) {  
      $host = $_SERVER['HTTP_HOST'];

      if (!preg_match("/^http/", $path)) {  
        $url = sprintf("%s://%s%s", (mod_rewrite_util::isSSL() ? "https" : "http"), $host, $path);  
      }

      return ($url);  
    }

    /**  
     * mod_rewriteでマッチングのパターンを記述した文字列の配列を受け取り、  
     *   preg_replaceに使えるよう変換する  
     *  
     * @param array $patterns  
     * @return array  
     */  
    function mod_rewrite_parse_pattern($patterns) {  
      $new_pattern = array();

      foreach ($patterns as $n => $pattern) {  
        $new_pattern[$n] = sprintf('/%s/', ereg_replace("/", "\\/", $pattern));  
      }

      return ($new_pattern);  
    }

    /**  
     * SSLが有効なアクセスかどうかを判定する  
     *  
     * @return boolean  
     */  
    function isSSL() {  
      if ($_SERVER['https'] == 1) /* Apache */ {  
        return TRUE;  
      }  
      elseif ($_SERVER['https'] == 'on') /* IIS */ {  
        return TRUE;  
      }  
      elseif ($_SERVER['SERVER_PORT'] == 443) /* others */ {  
        return TRUE;  
      }  
      else {  
        return FALSE; /* just using http */  
      }

    }  
  }  

||<