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

前へ << HTTP クライアントを作ってみよう(2) HTTP クライアントを作ってみよう(4) >> 次へ

改良版を作ってみよう

前節で説明したサンプルは、一応は HTTP クライアントとして最低限の動作はしますが、 もう少し機能を追加してみましょう。
  • ダウンロードする URL を指定できるようにする
  • User-Agent 情報を送る
  • Proxy に対応する
  • HEAD に対応する
の4点を改善してみます。以下に詳しく説明します。

URL を指定できるようにする

さきほどのサンプルでは http://www.cs.gunma-u.ac.jp/ にしかアクセスできませんでした。 コマンドラインで任意のURLを指定できるようにしてみます。

User-Agent 情報を送る

User-Agent については User Agent統計 を参照してください。要は HTTP クライアントの自己紹介です。 User-Agent は「クライアント名/バージョン (OSなどの情報)」 という形式にすることが推奨されています。

クライアント名は…まぁ何でもいいのですが、とりあえず 「httptalker」としましょう。 勉強用のサンプルなのでバージョンは 0.10 とします。 結局 User-Agent として「httptalker/0.10 (HTTP client sample)」を送ることにします。

サーバへの User-Agent の渡し方ですが、

% telnet www.cs.gunma-u.ac.jp 80
Trying 133.8.2.7...
Connected to www.cs.gunma-u.ac.jp.
Escape character is '^]'.
GET / HTTP/1.0(リターン)
User-Agent: httptalker/0.10 (HTTP client sample)(リターン)
(リターン)
と、メソッドの後につづけて User-Agent を送信します。

Proxy に対応する

Proxy サーバを経由して WWW サーバにアクセスできるようにします。 多くの場合、Proxy サーバはポート 8080 で動いているようですが、 8080 以外のポートを使う Proxy サーバも存在します (普通 Proxy サーバの設定で変えることができます)。

telnet を使って、proxy.example.com:8080 を経由して http://www.cs.gunma-u.ac.jp/ にアクセスしてみましょう。

% telnet proxy.example.com 8080
Trying 10.11.12.13...
Connected to proxy.example.com
Escape character is '^]'.
GET http://www.cs.gunma-u.ac.jp/ HTTP/1.0(リターン)
(リターン)
これまでと違うところは、目的の WWW サーバ (この場合はwww.cs.gunma-u.ac.jp) に 直接繋げるのではなく、proxy サーバに接続している点です。ポート番号も 80 ではなく 8080 にします。そして、送信するリクエストの内容が
GET / HTTP/1.0(リターン)
ではなく
GET http://www.cs.gunma-u.ac.jp/ HTTP/1.0(リターン)
と、URL をそのまま渡しています。proxy.example.com で動いている proxy サーバは、あなたの代わりに www.cs.gunma-u.ac.jp のポート 80 に接続して、
GET / HTTP/1.0(リターン)
というリクエストを送るわけです。そして返ってきたデータを あなたのところに送ってくるのです。proxy サーバは「代理サーバ」と 訳される理由がわかりますね。 あなたの代わりに目的のホストに接続してくれるのが proxy サーバなのです。

HEAD に対応する

WWW サーバは、クライアントから GET コマンドが送られてくると、 ヘッダ・空行・ボディを返します。一方、HEAD コマンドが送られて くるとヘッダしか返しません。

ヘッダ部分の一番先頭の行はステータスと呼ばれる情報となっており、 そこを見れば「Not Found」「Permission Denied」「Internal Server Error」などの状態を 判断できます。よく知られているのは

HTTP/1.1 200 OK
HTTP/1.1 301 Moved Permanently
HTTP/1.1 404 Not Found
HTTP/1.1 500 Internal Server Error
といったものですが、みなさんもだいたいの意味はわかるでしょう。

GET に比べて HEAD はボディを転送しない分だけ、送受信する データ量は少なくなり、その結果処理にかかる時間も短くてすみます。 そのため、リンク先がつながっているかどうかを調べるための リンクチェッカなどが HEAD メソッドをよく使います。

http://www.cs.gunma-u.ac.jp/ のヘッダ情報だけを受け取るには以下のようにします。

% telnet www.cs.gunma-u.ac.jp 80
Trying 133.8.2.7...
Connected to www.cs.gunma-u.ac.jp.
Escape character is '^]'.
HEAD / HTTP/1.0(リターン)
(リターン)

まとめて指定してみる

最後に、User-Agent を送信しつつ、 http://www.cs.gunma-u.ac.jp/ のヘッダ情報だけを proxy サーバ (proxy.example.com:8080) を 通じて受け取ってみましょう。要は、これまでやったことを組み合わせるだけでいいのです。
% telnet proxy.example.com 8080
Trying 10.11.12.13...
Connected to proxy.example.com
Escape character is '^]'.
HEAD http://www.cs.gunma-u.ac.jp/ HTTP/1.0(リターン)
User-Agent: httptalker/0.10(リターン)
(リターン)

改良版HTTP クライアント

改良版 HTTP クライアントの書式は
httptalker -METHOD URL [Proxy]
です。METHOD には GET か HEAD を指定します。URL は http://www.hoge.com/fuga.html などです。Proxy には proxy.example.com:8080 などと指定しますが、省略可能です。 例えば
% ./httptalker -GET http://www.hoge.com/fuga.html 
% ./httptalker -GET http://www.hoge.com/fuga.html proxy.example.com:8080
% ./httptalker -HEAD http://www.hoge.com/fuga.html 
のように指定します。ソースは以下のようになります。

http-client-2.pl

    1: #!/usr/local/bin/perl -w
    2: 
    3: # $Id: http-client-2.pl,v 1.2 2002/02/05 17:53:09 68user Exp $
    4:                 
    5: use Socket;     # Socketモジュールを使う
    6: 
    7: #----------------引数解析--------------------
    8: 
    9:                 # メソッドを解析。-HEADか-GET以外ならエラー
   10: if ( $ARGV[0] eq "-HEAD" || $ARGV[0] eq "-head" ){
   11:     $method = "HEAD";
   12: } elsif ( $ARGV[0] eq "-GET" || $ARGV[0] eq "-get" ){
   13:     $method = "GET";
   14: } else {
   15:     print "methodはGETかHEADを指定してください。\n";
   16:     exit;
   17: }
   18: 
   19:                 # URLを解析。http://host/pathという形式でなければエラー
   20: if ( $ARGV[1] =~ m|^http://([-_\.a-zA-Z0-9]+)/?(.*)$| ){
   21:     $host = $1;
   22:     $path = $2;
   23: } else {
   24:     print "URLは http://host/path という形式で指定してください。\n";
   25:     exit;
   26: }
   27: 
   28:                 # 引数が3つあるならProxyを解析
   29: if ( $#ARGV == 2 ){
   30:     if ( $ARGV[2] =~ m|^([-_\.a-zA-Z0-9]+):(\d+)$| ){
   31:         $proxy = $1;
   32:         $port = $2;
   33:         $connect_host = $proxy;
   34:     } else {
   35:         print "Proxy は host:port という形式で指定してください。\n";
   36:         exit;
   37:     }
   38:     $connect_host = $proxy;
   39: 
   40:                 # 引数が2つしかないなら
   41: } else {
   42:     $connect_host = $host;
   43:     $port = getservbyname('http', 'tcp');
   44: }
   45: 
   46: 
   47: #----------------接続処理-------------------
   48: 
   49:                 # ホスト名を、IPアドレスの構造体に変換
   50: $iaddr = inet_aton($connect_host)
   51:       || die "$connect_hostは存在しないホストです。\n";
   52: 
   53:                 # portとIPアドレスとまとめて構造体に変換
   54: $sock_addr = pack_sockaddr_in($port, $iaddr);
   55: 
   56:                 # ソケット生成
   57: socket(SOCKET, PF_INET, SOCK_STREAM, 0)
   58:         || die "ソケットを生成できません。\n";
   59: 
   60:                 # 指定のホストの指定のportに接続
   61: connect(SOCKET, $sock_addr)
   62:         || die "$connect_host の ポート$portに接続できません。\n";
   63: 
   64:                 # ファイルハンドルSOCKETをバッファリングしない
   65: select(SOCKET); $|=1; select(STDOUT);
   66: 
   67: 
   68: #------------HTTPリクエスト送信-----------------
   69: 
   70:                 # Proxyサーバに接続するなら
   71: if ( defined $proxy ){
   72:     print SOCKET "$method http://$host/$path HTTP/1.0\r\n";
   73:                 # 直接WWWサーバに接続するなら
   74: } else {
   75:     print SOCKET "$method /$path HTTP/1.0\r\n";
   76: }
   77: 
   78:                 # User-Agentを送信
   79: print SOCKET "User-Agent: httptalker/0.10 (HTTP client sample)\r\n";
   80: print SOCKET "\r\n";
   81: 
   82: 
   83: #------------サーバからのデータを受信 -----------------
   84: 
   85: if ( $method eq "GET" ){
   86:                 # GETメソッドならヘッダ部分は表示せず
   87:     while (<SOCKET>){
   88:         m/^\r\n$/ && last;
   89:     }
   90:                 # ボディ部分だけを表示する
   91:     while (<SOCKET>){
   92:         print $_;
   93:     }
   94: } else {
   95:                 # HEADメソッドなら全文表示
   96:     while (<SOCKET>){
   97:         print $_;
   98:     }
   99: }
主な変更点は、引数解析、GET と HEAD の場合分け、Proxy を経由するかどうかの場合分けです。

解説は…書かなくても大丈夫ですよね? 前バージョンと見比べてみれば理解できると思います。

さて、改良はしたものの、まだまだHTTP クライアントとしては不完全です。 次節で問題点を上げますので、各自で改良してみてください。

前へ << HTTP クライアントを作ってみよう(2) HTTP クライアントを作ってみよう(4) >> 次へ

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