HTTP クライアントを作ってみよう(4)

前へ << HTTP クライアントを作ってみよう(3) HTTP クライアントを作ってみよう(5) - Basic 認証編 - >> 次へ

さらなる改善点・バーチャルホストに対応

ホスト名と IP アドレスの対応は基本的には1対1なのですが、 必ずしもそうする必要はありません。 DNS サーバの設定次第で、 複数のホスト名に対して同じ IP アドレスを割り当てることができます。

HTTP クライアントが inet_aton を使ってホスト名を IP アドレスに変換しようとすると、 OS が自動的に DNS サーバに問い合わせを行います。 そのとき、www.host1.com・www.host2.com・www.host3.com のいずれに対しても、同じ xxx.xxx.xxx.xxx という IP アドレスを返すように DNS サーバを設定しておけばいいのです。

余談ですがこれとは逆に、DNS サーバに細工をすることで、ホスト名から IP アドレスに変換する際に毎回違う IP アドレスを返すこともできます。 この目的は負荷軽減です。 例えば www.yahoo.co.jp がそうです。dig コマンドや nslookup コマンドで調べてみてください。 こういう動作をラウンドロビンと言います。
ユーザからは www.host1.com と www.host2.com と www.host3.com は、 それぞれ3つの異なるホスト名に見えます。 しかし HTTP コネクション自体は inet_aton で変換した IP アドレスを利用しますので、 3つのホストは、全て xxx.xxx.xxx.xxx という同じ IP アドレスに見えます。

つまりユーザからは 3つの別のホストに接続しているつもりでも、 結果的には同じホストに接続していることになるのです。 こうすると何がうれしいかというと、WWW サーバを xxx.xxx.xxx.xxx にだけ立ち上げておけば、 一つのホストで WWW サーバ 3 つ分の役割を果たすことができます。

これをバーチャルホストと言い、DNS の仕組みを利用して 1つのホストで複数の WWW サーバ分の働きをするものです。 このことをソフトウェアマルチホーミングとも言うようです。

では、IPアドレス xxx.xxx.xxx.xxx のマシンで動いている WWW サーバが

GET /index.html HTTP/1.0
というリクエストを受けた場合、どう解釈すべきでしょうか。このままでは
  • http://www.host1.com/index.html
  • http://www.host2.com/index.html
  • http://www.host3.com/index.html
の3つのうちどのファイルの内容を返せばいいのかわかりません。

そこで、WWW クライアントは「自分は接続先のサーバ名を〜であると認識している」 という意味の「Hostヘッダ」を送ります。例えば

GET /index.html HTTP/1.0
Host: www.host2.com:80
というリクエストを送ることで、WWW サーバは 「このクライアントは http://www.host2.com/index.html の内容が欲しいんだな」 ということがわかります。

逆に言うと、Host ヘッダを送信しない WWW クライアント (IE2 がそうです) は、 http://www.host3.com/index.html をリクエストしたつもりでも、 http://www.host1.com/index.html に相当するページが送られてきたりします。

なお、HTTP/1.1 を使う場合は Host ヘッダを必ず付けなくてはいけません。

さらなる改善点・POST メソッド

GET・HEAD メソッド以外に、POST メソッドというものもあります。 CGI にデータを渡す場合、
http://www.foo.com/bar.cgi?data1=hoge&data2=fuga
といったふうに、? の後に渡したい文字列を書き連ねます。 HTTP クライアントは、www.foo.com に接続し、GET メソッドを使って
GET /bar.cgi?data1=hoge1&data2=fuga HTTP/1.0
と、そのまま引数を渡します。

しかしこの方法は環境変数経由で文字列が渡されるので、あまりにも長い文字列は 渡すことができません (どれくらいの長さの文字列なら OK か、というのは処理系依存です)。 また、URL として引数が表示されてしまうので、ユーザにデータを見せたくない 場合には不適当です。

そういう場合は POST メソッドを使います。書式は以下の通りです。

POST /bar.cgi HTTP/1.0
Content-Length: 渡したい文字列の長さ
(空行)
hoge=fuga&hoge2=fuga2&....
もし、データが「hoge=fuga&hoge2=fuga2」ならば、長さは 21 なので、
Content-Length: 21 
となります。

さらなる改善点・URL エンコード

http://X68000.q-e-d.net/%7E68user/ という URL の表記方法を見た方も多いと思います。これは http://X68000.q-e-d.net/~68user/ と等価ですが、 本来の HTTP の仕様からすると、後者は間違いです。

HTTP では、そのまま送信していい文字は `0-9' `a-z' `A-Z' `_' `-' `.' `*' だけで、その他の文字列は URL エンコードを行わなくてはなりませんが、 これは HTTP クライアントの仕事です。URL エンコードというのは 「%」の後に、その文字の16進数表記を続けたものです。

`~'を16進数のASCIIコードで表すと「7E」となるので、 「~68user」をURLエンコードすると「%7E68user」になるわけです。

また、「 」(空白)は「+」に変換しないといけません (「+」を「%20」に再変換する必要はありません)。

例えば、http://foo.com/~user/hoge.cgi?fuga=ABC!"$ DEF+

GET /%7euser/hoge.cgi?fuga%3DABC%21%22%5C%24+DEF%20 HTTP/1.0
としなければいけないのです。POST メソッドで送るデータも URL エンコードの対象になります。

URL エンコードを perl で行うには

$str =~ s/([^\.\*\-_a-zA-Z0-9 ])/sprintf("%%%02lX",unpack("C",$1))/eg;
$str =~ s/ /+/g;
とします。
上記の説明は古いです。以前 URL を規定していたのは RFC 1738 で、 そこではチルダを URL エンコードしなければならないと定められていました。 しかし現在では RFC 2396 が URL を包含する概念として URI を規定しており、 そこではチルダはエンコードしなくてよい、というふうに変更されました。

参考: 掲示板での 当ページ管理人の発言

さらなる改善点・301 Moved Permanently

http://X68000.q-e-d.net/~68user http://X68000.q-e-d.net/~68user/ は同じ URL を指す、 と思っている方もいるかもしれません。しかし URL の最後に `/' が付いているかどうかで、 HTTP クライアントの動作はかなり違ってきます。

最後に「/」が付いていない URL を、実際に telnet で試してみましょう。

% telnet X68000.q-e-d.net 80
Trying 210.155.201.169...
Connected to X68000.q-e-d.net.
Escape character is '^]'.
GET /~68user HTTP/1.0(リターン)
Host: X68000.q-e-d.net:80
(続けてリターン)

HTTP/1.1 301 Moved Permanently
Date: Sat, 20 Jun 1998 22:31:59 GMT
Server: Apache/2.0.52
Location: http://X68000.q-e-d.net/~68user/
Connection: close
Content-Type: text/html
(以下略)
注目してほしいのはヘッダの先頭行の「HTTP/1.1 301 Moved Permanently」と 「Location: http://X68000.q-e-d.net/~68user/」です。

WWW サーバが「GET http://X68000.q-e-d.net/~68user HTTP1.0」を受け取ると、 「~68user」はディレクトリなのに最後の「/」が省略されていると判断し、 ステータスコード 301 を返してきます。そして、正しい URL (最後に「/」が付いた URL) を Location ヘッダで知らせてきます。

その時点でコネクションは切断されるので、クライアントは再度サーバに接続しなおして、 Location ヘッダで示された URL にアクセスしなければなりません。 つまり http://X68000.q-e-d.net/~68user は間違いで、 http://X68000.q-e-d.net/~68user/ が正しい URL なのです。

こういう場合、ユーザは知ることができませんが、 ブラウザは WWW サーバに2回 connect しているわけです。 ですから web にリンクを張るときは、 http://X68000.q-e-d.net/~68user などという 表記をすると、それだけwebが表示されるまでに時間がかかってしまいます。必ず http://X68000.q-e-d.net/~68user/と、 最後に `/' を付けるようにしましょう。
この HTTP クライアントでは「301 Moved Permanently」に対応していませんが、 やはり自動的に再接続した方が便利でしょう。
前へ << HTTP クライアントを作ってみよう(3) HTTP クライアントを作ってみよう(5) - Basic 認証編 - >> 次へ

ご意見・ご指摘は Twitter: @68user までお願いします。