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

チラシの裏からうっすら見える外枠の外のメモ書き

新聞に挟まってる硬い紙のチラシの裏からうっすら見える外枠の外に走り書きされたようなものです。思いついたときにふらふらと。

さくらのレンタルサーバでPHPファイルを隠してかっこいいURLを作るときにハマること

プログラミング

この話題、何番煎じかわからないぐらい有名なことかもしれない

 

だが、思いっきりハマってしまい2日かけてなんとかしたので記念に残しておく。

 

日本のレンタルサーバとして老舗かつ有名なところはさくらインターネットの提供するさくらのレンタルサーバではないだろうか。

 

このさくらのレンタルサーバは低価格で良品質なサーバリソースを提供してくれるのだが、一部仕様により設置しにくいプログラムがある。

 

今回はその中のPHPとhtaccessによるRewriteRuleの仕様について少し書く。

 

注意として、この方法はApacheサーバを対象としている。

nginxに関しては別となる。

 

 

PHPファイルは基本的に「○○○.php」など、拡張子に"php"とつくものが主流である。

 

そして、PHPファイルへデータをWeb上で渡す場合、一般的な方法として次が挙げられる。

 

  • POST
  • GET
  • PATHINFO

 

POSTは問い合わせなどを行うフォームで、GETはGoogle検索などで使われている。

一方、このPATHINFOはURLの一部としてデータを渡している。

単にURLの一部として渡すだけであればそれはGETと同じだが、渡し方が異なっており、パラメータの形ではないため評価されやすい。

 

 

PATHINFOでデータを渡すほうがかっこいいURLになると考えているし、そう考えられる場合が多い。

しかし、URLの途中に"example.php"などと、プログラムファイルが入るのはなんとも不格好なので、それを取り除こうと考えた。

 

 

URLの振る舞いとして、通常ドメイン以下の部分はディレクトリないしファイル名と認識する。

もし一番最後の部分のファイルまたはディレクトリが存在しない場合、その上の部分へ戻ってそれをプログラムファイルとして認識し、その先をPATHINFOとするのが多い。

 

 

例えば、ルートディレクトリにindex.phpを設置してPATHINFOを与えるとどうなるだろうか。

多くのサーバではそれらはPATHINFO自体を何らかのファイル名またはディレクトリと考え404などエラーが発生する。

 

 

これらを防ぐため、このアクセスをすべてPATHINFOとしてindex.phpに紐付けることをする。

その際、httpd.confに設定を追加しても問題ないのだが、レンタルサーバの場合は.htaccessに設定することとなる。

 

 

上が実際に隠す方法だ。

説明すると、1行目及び9行目で条件判断である「mod_rewrite.cが導入されているか」が判定される。

この"mod_rewrite.c"とは、URLを内部で書き換えるために必要となるモジュールである。

これが導入されている場合、2行目から8行目の内容が実行される。

 

2行目ではRewrite機能を有効にしている。これを設定しないとそれ以降の機能は利用できない。

 

4行目では置き換え後のパスが設定される。今回は例としてルートディレクトリとなるためこう書いてあるが、ルートディレクトリでない限りは必要ない。

設置場所がルートディレクトリでない場合は、"/"ではなくそのディレクトリを記述すること。

 

6行目、7行目ではアクセスされたファイル名がディレクトリでないこと、ファイルでないことを条件として設定している。

特徴として、何かを実行する際の条件は予め設定することが必要となる。

 

8行目では、上で設定した条件のもと、アクセスをすべてindex.php以降の何らかのデータへ変更している。

しかし、この書き方は以下のように直していることに注意してほしい。

 

 

つまり、この形に変更したURLはPATHINFOでなくGETの形でデータを渡している。

この状態でPATHINFOを取得できるかと言えば「できない」ので、以下のコードをindex.phpの最初に追加する。

 

call_user_func(function(){
    if(!isset($_SERVER['PATH_INFO'])){
        if(isset($_SERVER['ORIG_PATH_INFO'])){
            $path_info=$_SERVER['ORIG_PATH_INFO'];
        }else{
            /**
             * `RewriteRule ^(.*)$ index.php?/$1 [QSA,L]`
             * などの記述によりPATH_INFOを取得出来ない場合のフォールバック
             */

            $sn_sp=strrpos($_SERVER['SCRIPT_NAME'],'/index.php');
            if($sn_sp===false){
                $sn_sp=strrpos($_SERVER['SCRIPT_NAME'],'index.php');
            }
            if(isset($_SERVER['REDIRECT_URL'])){
                $pi_urlencode=
                $pi_urldecode=ltrim(substr($_SERVER['REDIRECT_URL'],$sn_sp),'/');
            }else{
                $pi_urlencode=ltrim(substr($_SERVER['REQUEST_URI'],$sn_sp),'/');
                $pi          =strstr($pi_urlencode,'?',true);
                if($pi!==false){
                    $pi_urlencode=$pi;
                }

                /**
                 * REQUEST_URIから取得したPATH_INFOの値をURLデコード
                 */
                $pi_urldecode=implode(
                    '/',
                    array_map(
                        function($pp){
                            return urldecode($pp);
                        },
                        explode('/',$pi_urlencode)
                    )
                );
            }

            $query_string=isset($_SERVER['QUERY_STRING']) ? $_SERVER['QUERY_STRING'] : http_build_query($_GET);

            /**
             * 先頭のスラッシュを統一
             */
            $slash_start_query=strncmp($query_string,'/',1)===0;
            if($slash_start_query){
                $pi_urlencode='/'.$pi_urlencode;
                $pi_urldecode='/'.$pi_urldecode;
            }

            /**
             * 相対パスを変換
             * @link http://webdesign-dackel.com/2014/06/12/php-relativepath-to-absolutepath/
             */
            list($pi_urlencode_absolute,$pi_urldecode_absolute)=array_map(function($pi){
                $new_pi=array();
                foreach(explode('/',$pi) as $pp){
                    if($pp==='.'){
                        array_shift($new_pi);
                        array_unshift($new_pi,'');
                    }elseif($pp==='..'){
                        array_pop($new_pi);
                        if(count($new_pi)===0){
                            $new_pi=array('');
                        }
                    }else{
                        $new_pi[]=$pp;
                    }
                }
                return implode('/',$new_pi);
            },array($pi_urlencode,$pi_urldecode));

            /**
             * $_SERVER['QUERY_STRING']からPATH_INFOを削除
             */
            $pi_query=null;
            foreach(array(
                //URLエンコードされた相対パス
                $pi_urlencode,
                //URLエンコードされた絶対パス
                $pi_urlencode_absolute,
                //URLデコードされた相対パス
                $pi_urldecode,
                //URLデコードされた絶対パス
                $pi_urldecode_absolute,
            ) as $pi_query){
                foreach(array(
                    $pi_query.'&',
                    $pi_query
                ) as $pi_query_amp){
                    $pi_q_len=strlen($pi_query_amp);
                    if(strncmp($query_string,$pi_query_amp,$pi_q_len)===0){
                        $query_string=substr($query_string,$pi_q_len);
                        break 2;
                    }
                }
            }
            $_SERVER['QUERY_STRING']=$query_string;

            /**
             * $_GETからPATH_INFOを削除
             */
            parse_str($query_string,$new_get);
            if($pi_query===$pi_urlencode || $pi_query===$pi_urlencode_absolute){
                $pi_query=urldecode($pi_query);
            }
            if(!isset($new_get[$pi_query]) && isset($_GET[$pi_query]) && $_GET[$pi_query]===''){
                unset($_GET[$pi_query]);
            }

            /**
             * PATH_INFOを取得
             * PATH_INFOはURLデコードされ、絶対パスに変換された、先頭にスラッシュが付いている値
             */
            $path_info=$pi_urldecode_absolute;
            if(!$slash_start_query){
                $path_info='/'.$path_info;
            }
        }
        $_SERVER['PATH_INFO']=$path_info;
    }
});

引用:リライトで「index.php?/$1」とした場合でもPATH_INFOを取得するフォールバックコード - Qiita

 

これで$_SERVER['PATH_INFO']からPATHINFOを取り出すことができるはずだ。