at posts/single.html

よりCoolなURLを作るには

ギークなお姉さんは好きですか経由で、Web屋のネタ帳 - CoolなURLを作るにはを読んだ。 前に、URLは短く鋭く愛を込めてというtDiaryへのオマージュを書いたことがあるので、こういう話は好き。

いつまでたってもmod_rewriteだのPATH_INFOだのの小手先テクニックの話が出てくるだけで、Webサイト自体の拡張性をも見据えた全体設計の中のひとつとしての「coolなURI」の作り方はどうあるべきなんだろう?という話は一向に出てこない。

と書いてあって期待したんだけど、

目標

http://geekdb.jp/geek.php?id=1

http://geekdb.jp/x/geek/foo

にする。(fooは1でもかまわない)

「x」がポイントだ。いや、確かに邪魔かもしれないが、シンプルさと作りやすさとわかりやすさとセキュリティを与える意味で実は重要な妥協である。

この「x」がかなり残念な感じ。 mod_rewrite を「多用」すべきじゃないのには同意。 でも、「Webサイト自体の拡張性をも見据えた全体設計」を考える上で意味不明の「x」を受け入れるのはいただけない。 元記事は、はてなブックマークでかなり注目されているようなので、他に方法があるよって意味で書いてみる。

元記事の問題点

元記事での解決方法はこれ。

AllowEncodedSlashes On
<FilesMatch "^x$" >
  ForceType application/x-httpd-php
</FilesMatch>

URLに「x」を含むリクエストを、「x」というPHPスクリプトに割り振るように指定している。 その「x」というスクリプトから、「geek.php」や「book.php」を include している。 この問題点は2つ。

  • 「x」という無意味な文字がURLに含まれる。クールじゃない。
  • PHP以外のスクリプトを呼び出せない。

とくに後者が致命的。 あとでギークDBだけじゃなくてスイーツDBを作りたくなったときに、「/x/sweets」という使おうとする。 そのときに、今度はPHPじゃなくてPerlで書いてみよう!ということができない。 せっかく、拡張子から.phpを消したのに、これじゃ意味がない。

先に解決策 … 「x」を含まない実現方法

ほとんど元記事のままなんだけど、 FilesMatch と ForceType を使うのなら「x」を挟まずにシンプルにこう書けばいい。

<FilesMatch "^geek$">
  ForceType application/x-httpd-php
</FilesMatch>

これで、「http://geekdb.jp/geek/foo」へアクセスすると geek.php が起動される。 将来的にスイーツDBを追加したくなったら、以下のように.htaccessに追加すればいい。

<FilesMatch "^sweets$">
  ForceType application/x-httpd-php
</FilesMatch>

もし、PHPじゃなくて他の言語を使いたくなっても、設定が分かれているので、sweets側の中身を書き換えればいい。 たとえば、CGI経由でスクリプトを起動する場合には、以下のように書ける。 ちなみに、この方法はtDiary や WikiFarm 設置の時に試行錯誤したときのもの。

<FilesMatch "^sweets$">
  DefaultType text/html
  Action text/html /home/machu/bin/sweets.cgi ← CGIスクリプトが置かれているパスを指定
</FilesMatch>

結局のところ、元記事との違いは.htaccessで振り分けるかPHPで振り分けるかの違いになる。 拡張性を考えるのなら、PHPに依存するよりもWebサーバ側に設定したほうがいい。

なぜ拡張子やクエリをつけない方が Cool なのか

  • あとで言語を変えることができるから

に尽きるかな。「geek.php」だと10年後に他の言語に変えたくなってもそうはいかない。 それこそ、無駄にmod_rewriteのお世話になるハメになる。 昔なら、「ブックマークはトップページにお願いします!」と言えたけど、はてなブックマークや検索エンジンなので直接ページに飛ぶ機会が増えた今ではそうも言ってられない。

でも、拡張子をつけなければいいかというと、そうでもない。 言語やフレームワークによってURLの付け方は変わるので、あとで乗り換えるときにはそれを一致させるコストはどうしても発生する。

だから、URLに「本質的な意味」を与えておくことが重要になる。 余計なものがついていないということは、それだけ乗り換えるときのコストが小さくなること。 逆に、本質的であれば、拡張子やクエリがついていても構わない。 静的な「.gif」、「.js」、「.css」まで拡張子をとっちゃうのはやりすぎだろう。

ちなみにRESTful 本では、URLは名詞にすべきってかかれてる。

SEOの話はよく知らない。あんまり興味もない。

クールなURLと.htaccessとの関係

HTMLでいわゆる「ホームページ」を作る → CGIやPHPでスクリプトを書いてみる、と進んできた時に、最初にぶつかる壁が「URLとファイルを分ける」という考え方じゃないかな。

それまでは、Webから「index.html」にアクセスすればサーバ上の「index.html」ファイルが表示され、「geek.php」にアクセスすればサーバ上の「geek.php」が実行される。 拡張子の違いでファイルが表示されるだけかスクリプトが実行されるのかが変わるってのも、Windowsでファイルアイコンをダブルクリックしたときと同じなので分かりやすい。

でも、WebブラウザがリクエストしたURLと、サーバ上のファイルには本来は何も関係が無い。 たまたま、ApacheなどのWebサーバが両者を結びつけてくれるだけ。 「/」で終わるURLで「index.html」が表示されるのも、Webサーバがそういう機能を持っているから。

なので、Webサーバにちょっとお願いをすれば、URLと実行されるファイルの関係を、自分好みにいろいろとカスタマイズできるようになる。 それが、「.htaccess」に書くFilesMatchやForceTypeやActionだったりする。

逆に言うと、Webサーバにお願いしない限りは、URLとファイルのマッピングを変えられない(.phpという拡張子を無くせない)ということ。ここは重要。

あとは、どこまでWebサーバにお願いして、どこまでを自分でやるかの違いになる。 ネタ帳さんは、「x」をPHPスクリプトに割り振って、あとはスクリプトで振り分ける方法を提示された。 僕はPHPスクリプトで振り分けるのも、.htaccessで振り分けるのも保守性は変わらないと思うから、より拡張性があってURLがクールになる方法を推奨するよ。 Webサーバにお願いするのだから、多少はWebサーバのことも知っておかないとね。

「x」は「シンプルさと作りやすさとわかりやすさとセキュリティを与える意味」で必要なのか?

どうしても以下の一文が気になる。

「x」がポイントだ。いや、確かに邪魔かもしれないが、シンプルさと作りやすさとわかりやすさとセキュリティを与える意味で実は重要な妥協である。

なので、その後に書かれているポイントに沿って考えてみる。

1. (apacheの)設定ファイルは短いほうがいい

確かにURLが増えればApacheの設定ファイルは長くなる。 でも、同じ設定の繰り返してロジックというほど複雑じゃない。 さっきも書いたけど、「x」というPHPに書くか.htaccessに書くかの違い。

2. DocumentRootに余計なものを置かないほうがいい

DocumentRootにgeek, sweetsというファイルが増えていくけど、「x」方式でDocumentRoot以外のスクリプトを呼び出す場合とセキュリティリスクは変わらないんじゃないかな。

まぁ、たとえばgeekやsweetsにDBのパスワードをそのまま書いておいて、.htaccessから設定を消しちゃうとPHPスクリプトが丸見えになるという危険はあるか。 でも、それはアプリの作りの問題。 geekやsweetsからDocumentRoot外においた設定ファイルを読むように作るべき。

3. どうせならMVCを根本から実践してみるのもいい

Webサイト = 1アプリなら「x」を起点にMVCを作るのもいいけど、Webサイトに複数のアプリを設置するのなら、geekやsweetsなどのアプリごとにMVCを作れる方がいい。

僕が見逃していることがあるかもしれないので、補足してもらえると嬉しいんだけどなぁ。

おまけ … PATH_INFO と REQUEST_URI の違い

元記事では、「PATH_INFOだのの小手先テクニック」と書かれていて、代わりに「x」の中で「REQUEST_URI」を使っている。 この違いを調べてみた。 Ruby を使って sweets.cgi を作って、環境変数の REQUEST_URI, PATH_INFO, QUERY_STRING を表示する。 .htaccess はさっきのActionを設定している。

#!/usr/bin/env ruby
require 'cgi'

puts CGI.new.header
puts '<html><body><pre>'

['REQUEST_URI', 'PATH_INFO', 'QUERY_STRING'].each do |key|
  puts CGI.escapeHTML "#{key}: #{ENV[key]}"
end

puts '</pre></body></html>'

これで、

http://www.machu.jp/sweets/machu?key1=value1&key2=value2

にアクセスすると、画面には以下のように表示される。

REQUEST_URI: /sweets/machu?key1=value1&key2=value2
PATH_INFO: /sweets/machu
QUERY_STRING: key1=value1&key2=value2

つまり、「REQUEST_URI = PATH_INFO + '?' + QUERY_STRING」ってことか。

追記

ブクマでも指摘していただいたけど、入力値をそのまま出力していてXSS脆弱性に繋がっていた。 大失敗。

puts "#{key}: #{ENV[key]}"

ではなく、

puts CGI.escapeHTML "#{key}: #{ENV[key]}"

としなきゃいけなかった。

関連する日記