略してはいけない

かつての話題の本みたいな見出しですが :-) 近年, 新聞やテレビ・ラジオ等の広告などでも多々見掛けるURL。しかし, 実際にみるとフルパスで表記されているものより, どこか一部が省略されたものが多いのです。例えば, スキームを略してしまったwww.hogehoge.co.jp/product/index.htmlなどです。

確かに, ウェブブラウザのLocationボックスにURLを入力する際, www.hogehoge.co.jp/product/index.htmlとしても, 大抵の場合目的のページが取得できるでしょう。しかし, それは本当に略していいものなのでしょうか。略して問題になることはないのでしょうか。


前提知識

URLの書式

URLとは"Uniform Resource Locator"の略で, WWWにおけるコンテンツへのアクセス手段と所在を一意に定める文字列です。HTML4.0以降ではより広い概念である"URI"が採用されていますが, ここではURLで統一します。

URLは, スキーム, ホスト名, 仮想パスの3つで構成されます。

http: (←スキーム) //www.hogehoge.co.jp (←ホスト名) /product/index.html (←仮想パス)

スキームは, 相手のコンピュータへのアクセス手段を示す文字列です。上の例では"http:"となっていますので, HTTPでアクセスすることになります。

ホスト名は, 相手のコンピュータを指し示す文字列です。先頭に"/"を2つ書き, IPアドレスまたはDNSなどでIPアドレスに変換できるホスト名のいずれかを続けます。TCPポート番号も指定できますが, ここでは省略します。

仮想パスは, 相手のコンピュータにおいてコンテンツの位置を指し示す文字列です。

すなわち上の例であれば, 「"www.hogehoge.co.jp"で示されるコンピュータのHTTPサーバが公開する"/product/index.html"というコンテンツを取得する」ということになります。

より正確には, 仮想パス先頭の"/"は仮想パスに含まれるものではなく, ホスト名と仮想パスの区切りの意味なのですが, 説明の都合上仮想パスに含まれるものとします。


ディレクトリ

ディレクトリは, ファイルを格納するための箱のようなものです(実際には, ファイル名や開始セクタ番号などを記録した特別なファイル)。すべてのファイルはすべからく何らかのディレクトリの中にあります。ディレクトリの中にさらにディレクトリを作ることもできます。

仮想ディレクトリ構成の図。本文参照

まず一番外側に根本となるディレクトリ, すなわちルートディレクトリがあります(本当はDocumentRoot)。ルートディレクトリは"/"で表されます。

上の図では, ルートディレクトリの中にはinformation, productの2つのディレクトリ(サブディレクトリ)があり, index.html, copyright.html, logo.png, TODOの4つのファイルがあります。それらは「/index.html」のように"/"を付けることで, 「ルートディレクトリ(/)の中にあるindex.html」と明示することができます。他のファイルや, ディレクトリでも一緒です。

また, ディレクトリinformationの中には, index.html, works.html, sinobi.html, scheme.bz2の4つのファイルがあります。これらも「information/index.html」とすることで, 「informationディレクトリの中にあるindex.html」ということを明確にできます。informationindex.htmlの間に"/"がありますが, これはディレクトリの区切りです。"/"単体ではルートディレクトリを指す記号ですが, 一般にはディレクトリの区切りとして使われるのです。

ディレクトリproductの中には, サブディレクトリsakuraがあり, またindex.html, mailm.tar.gz, xtoride.tar.gzの3つのファイルがあります。もちろん, 「product/index.html」と表すことができます。

ディレクトリsakuraの中には, tomoyo.mng, xiaolang.jpegの2つのファイルがあります。ここでも「sakura/tomoyo.mng」とすることで「ディレクトリsakuraの中のtomoyo.mng」という意味になります。

ここで「product/sakura/xiaolang.jpeg」とすれば, 「ディレクトリproductの中のディレクトリsakuraにあるファイルxiaolang.jpeg」の意味になり, 多重構造になったディレクトリ階層も表現できます。もちろん「/product/sakura/xiaolang.jpeg」と"/" から始めれば, ルートディレクトリから表した「絶対パス表記」となります。パスとは, ファイルに至る道筋の意味です。

この図は, 後述するいくつかの説明にも使用します。


HTTPトランザクション

では前述のURLを例に, コンテンツを取得する際に相手のコンピュータとの間で行われるやりとりを覗いてみましょう。このやりとりのことを「トランザクション」(transaction)と呼びます。

Locationボックスにhttp://www.hogehoge.co.jp/product/index.htmlと入力すると, ウェブブラウザは"www.hogehoge.co.jp"で示されるコンピュータに接続します。接続ができたら, 以下のようなHTTPリクエストを発します(かなり省略してます)。

GET /product/index.html HTTP/1.0

最初の"GET"は「メソッド」(method)と言い, 相手のHTTPサーバに対する命令です。GETメソッドは「仮想パスで示すコンテンツを渡せ」という命令になります。

メソッドに続くのは仮想パスです。目的のコンピュータには既に接続してあり, HTTPリクエストを送っているわけですから, スキームやホスト名は送られません(バーチャルホストやプロクシが絡む場合は別)。

最後にHTTPのバージョンです。2000年10月現在, 0.9, 1.0, 1.1の 3つがあります。現状でほとんどのウェブブラウザが1.1に対応していますが, 例が複雑になるのでここでは1.0で説明します。

さて, このリクエストに対し, HTTPサーバは以下のようなHTTPレスポンスを返します(これもかなり省略しています)。

HTTP/1.0 200 OK
Content-Length: 6419
Content-Type: text/html

(以下, /product/index.html の内容が続く)

1行目には「HTTPのバージョン」「ステータスコード」「ステータスコードの意味」が書かれています。ステータスコードはHTTPサーバがリクエストを処理した結果です。200は「リクエストは正常に処理された。要求されたコンテンツを返す」という意味になります。

2行目は, HTTPサーバが返すコンテンツの大きさです。単位はByteです。

3行目は, コンテンツの種類です。"text/html"は, HTML文書であることを表します。ウェブブラウザは, この行を見てコンテンツの扱いを考えます。画像であれば画像として表示する, 文書であれば文書として表示する, 実行型プログラムであれば実行するかどうかユーザに尋ねる, など。

このレスポンスでは, 3行目までがレスポンスヘッダになります。この後1行分の空行を境として, リクエストされたコンテンツの本体が続きます。


スキームの省略

絶対URLと相対URL

スキーム, ホスト名, 仮想パスのすべてがそろったものを「絶対URL」と言います。絶対URLは, ウェブブラウザの状態に関わらず確実に一つのコンテンツを指し示します。

これに対し, あるコンテンツのURLを基準とした書き方があり, それを「相対URL」と呼びます。相対URLでは, アクセス手段やホスト名は元のコンテンツと同じものとみなされ, 仮想パスの相対的な記述だけになります。

前述の「ディレクトリ」の項の図を元に説明します。この図のディレクトリ構造を持つホストを"www.hogehoge.co.jp", スキームはHTTPであるとします。

たとえば, 「http://www.hogehoge.co.jp/index.html」というURLで表されるページを考えます。このページに「著作権」なるハイパーリンクを作り, "/copyright.html"に結びつけるとしたら, どのように表現すればよいでしょうか。目的のコンテンツの絶対URLは「http://www.hogehoge.co.jp/information/index.html」ですから,

<a href="http://www.hogehoge.co.jp/copyright.html">著作権</a>

としてももちろん構いません。しかし, これではあまりにも冗長です。もしホスト名に変更があった場合など, すべてのリンクが絶対URL表記されていたら, 全部書き換えなければならなくなります。

そこで相対URL表記です。

<a href="copyright.html">著作権</a>

基準となるURLは"http://www.hogehoge.co.jp/index.html"ですから, スキーム"http:", ホスト名"//www.hogehoge.co.jp", 仮想パスのディレクトリ部分"/"まで省略できます。こうすることで, ディレクトリ構成自体が変更されない限りリンクの張り替えは必要なくなります。

同様にして, "http://www.hogehoge.co.jp/product/index.html"から"http://www.hogehoge.co.jp/product/sakura/tomoyo.mng"にリンクを張る場合も,

<a href="sakura/tomoyo.mng">特選アニメ</a>

という風になります。

"http://www.hogehoge.co.jp/product/index.html"から"http://www.hogehoge.co.jp/index.html"にリンクを張る場合は, 「一つ上のディレクトリ」を表す特別な表記方法".."を使い,

<a href="../index.html">ホームページ</a>

のようになります。


Locationボックス以外で省略すると

多くのウェブブラウザでは, LocationボックスにURLを入力する際, スキームおよびホスト名先頭の"//"を省略することができるようになっています。その場合, どのスキームが使えるかを調べて適当なスキームを補完するようです。動作を見て想像するに, まず試しにHTTPで接続し, 接続できたら"http://"を補完する。接続できなかったら次はFTPを試みる……といったことをするようです。

例えば, Locationボックスに"www.hogehoge.co.jp/information/works.html"と入力すると, HTTPで接続できることを確かめ, 自動的に"http://"が付加されて"http://www.hogehoge.co.jp/information/works.html"となります。

これは, Locationボックスには常に絶対URLが入力・表示されるためです。入力された文字列がスキームから始まっていない場合, 絶対URLに直そうとするのです。

従ってスキームは, 特に"http:"である場合, Locationボックスでは省略ができることになります。

では, Locationボックス以外ではどうでしょうか。例えばウェブページ中でハイパーリンクを張る際に, 同様の省略をしたとします。

<a href="www.hogehoge.co.jp/information/works.html">業務内容</a>

aタグのhref属性はLocationボックスとは違い, 相対URL表記が許されています。いかにもホスト名らしい文字列が先頭にあるので絶対URLと見間違えそうですが, これは明らかに相対URL表記です。このリンクが張られているページを基準として, ディレクトリ"www.hogehoge.co.jp"の中のディレクトリ"information"にあるファイル"index.html"を指し示しているのです。

実際にそのようなディレクトリ構成になっていればともかく, スキームを省略してこうなったのですから, このリンクが指し示す先は存在しないでしょう。従ってこれは「デッドリンク」(指し示す先のないリンク)となります。

このような省略が引き起こした問題の例を挙げます。某ウェブ掲示板に「リンクを作成する場合はここに入力」というフォームがあり, メールアドレスやURLを記入すると自動的にハイパーリンクを作成する機能があったのですが, ある人がスキームと"//"を省略したURLを記入したために, そのリンクは掲示板からの相対URLとなってしまったのです。

最近, 企業広告などでURLが書かれているものをよく見掛けますが, これもまたスキームなどを省略したものが多く見受けられます。例えば朝日新聞2000年11月3日付の掲載広告でも, 関西大学出版部(www.kwansai.ac.jp/press/), HONDA Stream(www.honda.co.jp/stream/), トリビュートリンク(www.tributelink.com), 野村不動産(www.nomu.com), 日産(www.nissan.co.jp/), ミズノクラシック(www.atum.ne.jp/m_classic), 原宿ビッグトップ(www.fujitv.co.jp/bigtop/), トヨタレンタカー(www.toyota.co.jp/rent), NTTコミュニケーションズ(www.ntt.com)とありました。これらはLocationボックスに入力することを前提としているのでしょうが, 混乱を助長している可能性が高いと思われます。

絶対URLとしては, スキームなどは決して省略できないものです。絶対表記か, 相対表記かを考えた上で, 省略してよいものかどうか, 今一度考えてみて下さい。


"/"の省略

basenameとは

basenameとは, パスの最終要素となるファイル名(またはディレクトリ名)のことです。対するに, パスからbasenameを取り除いたもの(厳密には違うのですが)を"dirname"と言います。

前述の「ディレクトリ」の項の図で説明します。例えばパスが/index.htmlの場合, basenameはindex.html, dirnameは/です。

/information/works.htmlの場合, basenameはworks.html, dirnameは/informationとなります。

basenameは, 最終要素であればディレクトリであっても区別しないので, /product/sakuraのbasenameはsakuraです。これは, ディレクトリであることを明示するために末尾に"/"を付加し, /product/sakura/というふうにしていても変わりません。

パスが/の場合, そもそも要素が一つしかありませんので, basenameは/です。この場合はdirnameも/となります。


basenameがファイルの場合

まず, http://www.hogehoge.co.jp/product/index.htmlを取得するトランザクションを考えてみましょう。www.hogehoge.co.jpに接続した後, HTTPリクエストは以下のようになります。

GET /product/index.html HTTP/1.0

対するHTTPレスポンスは以下のようになります。

HTTP/1.0 200 OK
Content-Length: 6419
Content-Type: text/html

(以下, /product/index.html の内容が続く)

ここで, このページに次のような相対URLのハイパーリンクがあった場合,

<a href="sakura/xiaolang.jpeg">思い出の写真</a>

このリンクを辿ったときに基準となる仮想パスは/product/ですから, HTTPリクエストは以下のようになります。

GET /product/sakura/xiaolang.jpeg HTTP/1.0

対するHTTPレスポンスは,

HTTP/1.0 200 OK
Content-Length: 20624
Content-Type: image/jpeg

(以下, /product/sakura/xiaolang.jpeg の内容が続く)

このような感じになります。


basenameがディレクトリの場合

basenameが普通のファイルではなくディレクトリになっているものをリクエストすることもできます。"http://www.hogehoge.co.jp/product/"を取得するトランザクションを考えて見ましょう。www.hogehoge.co.jpに接続した後, HTTPリクエストは以下のようになります。

GET /product/ HTTP/1.0

対するHTTPレスポンスは以下のようになります。

HTTP/1.0 200 OK
Content-Length: 6419
Content-Type: text/html

(以下, /product/index.html の内容が続く)

/product/をリクエストしたのですが, 送られてきたのは/product/index.htmlの内容になっています。これは, 仮想パスのbasenameがディレクトリである場合, HTTPサーバはそのディレクトリにある特定の名前のファイルの内容を返すようになっているためです。設定によって違うのですが, 大抵はindex.htmlを返します。

ただし, レスポンスヘッダには「返したのは/product/index.htmlである」という情報は含まれていませんので, ウェブブラウザはあくまでも/product/に対応するコンテンツとして扱います(キャッシュや履歴などで別扱いされます)。

ここで, このページに次のような相対URLのリンクがあった場合,

<a href="sakura/">C.C.SAKURA</a>

これはディレクトリへのハイパーリンクです。相対URLの基準となるのは"/product/"ですから, HTTPリクエストは以下のようになります。

GET /product/sakura/ HTTP/1.0

この場合, ディレクトリ/product/sakura/の中にはindex.htmlというファイルがありませんので, レスポンスはHTTPサーバの設定によります。大抵はエラーを返す, ディレクトリ内のファイルの一覧を返す, などです。


basenameがルートディレクトリの場合

次にディレクトリの特別な場合として, http://www.hogehoge.co.jp/を取得するトランザクションを考えてみましょう。www.hogehoge.co.jpに接続した後, HTTPリクエストは以下のようになります。

GET / HTTP/1.0

対するHTTPレスポンスは以下のようになります。

HTTP/1.0 200 OK
Content-Length: 1358
Content-Type: text/html

(以下, /index.html の内容が続く)

さて, ときどき見受けられる省略に, 「仮想パスのないURL」があります。"http://www.hogehoge.co.jp"のようなものです。これをウェブブラウザのLocationボックスに入力した場合はどうなるでしょうか。

先に示した通り, HTTPリクエストでは仮想パスは必須です。受け取りたいコンテンツを明記しなければいけません。まかり間違って仮想パスを抜いたリクエストを発しても, HTTPサーバは"400 Bad Request"というエラーを返します。

そのため, ウェブブラウザが自分で仮想パスを補完します。相手のコンピュータにどのようなコンテンツがあるかは分かりませんが, 少なくとも確実に存在する仮想パス……そう, "/"(ルートディレクトリ)です。

GET / HTTP/1.0

従って, 結局は前項のものと同じリクエストを発することになります。実際にLocationボックスに入力してみれば, 適当なタイミングでURLの末尾に"/"が補完されるのが分かるはずです。

ここで, このページに次のような相対URLのリンクがあった場合,

<a href="information/index.html">会社案内</a>

相対URLの基準となるのは"/"ですから, HTTPリクエストは以下のようになります。

GET /information/index.html HTTP/1.0

対するHTTPレスポンスは,

HTTP/1.0 200 OK
Content-Length: 23332
Content-Type: text/html

(以下, /information/index.html の内容が続く)

このようになります。


無用なトラフィックを避けるために

先にbasenameがディレクトリである場合として, /product/という仮想パスでリクエストを発しました。この仮想パスの末尾には"/"が付いています。もちろん, basenameの"product"がディレクトリであることを明示するためです。

末尾の"/"を取り除き"/product"としても, productがディレクトリであることに変わりはないのですから, /product/index.htmlの内容が返されるように思えます。実際に試してみましょう。

GET /product HTTP/1.0

返ってきたレスポンスは次の通りです(例によって激しく省略しています)。

HTTP/1.0 301 Moved Permanently
Location: http://www.hogehoge.co.jp/product/

ステータスコード301は「リクエストされたコンテンツはLocationフィールドで示すURLに移転した」という意味です。「移転」というと分かり難いですが, 「今使ったURLではなく, Locationフィールドで示すURLを使うようにしなさい」ということです。要するに末尾に"/"を付けてやり直しなさい, と。

これはどういうことでしょう。HTTPサーバにしてみれば自分の動作するコンピュータ上のファイルなのですから, productがディレクトリであることは確実にわかるはずです。なぜわざわざ"/"を明記しなければならないのでしょうか。

実はproductがディレクトリであることを知らなければならないのは, HTTPサーバの方ではなくウェブブラウザの方なのです。なぜならば, 相対URLの基準が違ってくるからです。

もし, 末尾の"/"を省略したままHTTPリクエストを発し, サーバが"200 OK"を返してしまうと, どうなるでしょう。

GET /product HTTP/1.0
HTTP/1.0 200 OK
Content-Length: 6419
Content-Type: text/html

(以下, /product/index.html の内容が続く)

重要なのは, "product"をウェブブラウザはディレクトリではなく普通のファイルと認識することです。レスポンスには, 「リクエストされた仮想パスのbasenameが普通のファイルかディレクトリか」を表す情報がありません(情報を含める方法がありません)。実際には/product/index.htmlHTML文書が返されているのですが, ウェブブラウザはこれを/productというHTML文書だと判断するしかないのです。

MS-DOSMS-Windowsなどの「拡張子」によってファイルの種類を判断するOSを使っている方には分かり難いかもしれませんが, HTTPではファイル(正しくはレスポンスのボディ)の種類を指定するのはあくまでもヘッダのContent-Typeフィールドであり, basenameの末尾ではありません。

ここで, /product/index.html中に以下のようにハイパーリンクが張られているとします。

<a href="xtoride.tar.gz">Xとりでの攻防(25,244Bytes)</a>

もし基準となるURLが, "http://www.hogehoge.co.jp/product/"と"/"を省略していない場合, ウェブブラウザはproductがディレクトリであると分かるので基準となる仮想パスは"/product/"であり, このリンクを辿る際には

GET /product/xtoride.tar.gz HTTP/1.0

というリクエストを発します。

もし"http://www.hogehoge.co.jp/product"と"/"を省略していた場合, ウェブブラウザはproductが普通のファイルであると見なすので, 基準となる仮想パスは"/となり, このリンクを辿る際には

GET /xtoride.tar.gz HTTP/1.0

というリクエストを発します。これではデッドリンクになってしまいます。

こうなることを防ぐため, HTTPサーバは「basenameがディレクトリなのに, 仮想パスの末尾に"/"が付かないリクエスト」を受け取った場合, 「"/"を付けてやり直しなさい」というレスポンスを返すのです。

GET /product HTTP/1.0
HTTP/1.0 301 Moved Permanently
Location: http://www.hogehoge.co.jp/product/
GET /product/ HTTP/1.0
HTTP/1.0 200 OK
Content-Length: 6419
Content-Type: text/html

(以下, /product/index.html の内容が続く)

よく考えて下さい。たった1文字の"/"を付けなかっただけで, HTTPトランザクションが二度手間になっているのです。やりとりするデータはほんのわずかですが, 通信混雑(トラフィック)増大の原因です。1文字(1Byte)のために1トランザクション(約3,000Byte)が無駄になる……これを世界中でやったとしたら?

basenameがディレクトリであるURLを記述・入力する際は, 絶対URL・相対URLに関わらず, 決して"/"を省略しないようにしましょう。


付記

このページの完成後, "/"の省略で問題が起こる新たな例を知りましたので報告します。情報元はfj.unixに投稿された記事(Message-ID: <3A54C7DD.CB41B037@nettaxi.com>)です。

この例は, HTTPサーバがApacheの場合に発生しました。

Apacheでは, 自分が動作しているマシンの名前を, 設定ファイルhttpd.conf中の「ServerName」という変数で設定します。例えばwww.hogehoge.lieと指定しておきます。

このホストはダイヤルアップでインターネットに接続されるため, IPアドレスは固定されていません。従ってホスト名も実はプライベートネットワークのものであり, 外部では名前解決できません。実際の運用では, 割り振られたIPアドレスをURLに使うつもりでした。割り振られたIPアドレスは, 256.256.256.256としておきます。

ところが, http://256.256.256.256は取得できるのに, http://256.256.256.256/foobarは取得できない, という事態になりました。この"foobar"はディレクトリです。

大体想像できるでしょうが, 後者の場合は以下のようなトランザクションになっています。

(256.256.256.256に接続)
GET /foobar HTTP/1.0
HTTP/1.0 301 Moved Permanently
Location: http://www.hogehoge.lie/foobar/
(www.hogehoge.lieは名前解決できない)

要するに, Apacheは自分の動作するホストがwww.hogehoge.lieという名前だと思っているので, 移転先のURLのホスト名にwww.hogehoge.lieを付けてしまったのです。

ダイヤルアップ接続など, 自ホスト名を明確にできない場合には, こういう設定はよくあることです。もちろん, DYNDNSを使うなどしてでも, ServerNameをきちんと指定するべきではありますが。もしプライベートネットワークのホスト名が, 偶然にもグローバルに実在するホスト名と一致してしまうと, かなりやっかいなことになるでしょう。


水野夢絵 <mwe@ccsf.homeunix.org>
Key fingerprint = 9BE6 B9E9 55A5 A499 CD51 946E 9BDC 7870 ECC8 A735