HTTPクライアントのしくみ

HTTPSへの対応,OpenSSLの利用方法,Webクライアント機能付きCGIプログラム

戻る

HTTPクライアントは,通常WebクライアントであるInternetExplorerやNetscape があれば必要ないと思われるかもしれません.
しかし,CGIから別のサーバにアクセスを行って結果を得る必要がある場合, 検索エンジンが情報を収集するためのロボット,個人で利用しているパソコンでも Webサーバの情報を丸ごと取得するためのロボットもあります.
これらは,Webサーバから見ればすべてWebクライアントです.最近,コンテンツの 実体を隠すために,CGIが他のサーバに対してアクセスを行い,結果を中継する ような,「プロキシCGI」をよく見かけます.このような機能は, 指定されたコンテンツの本当のURLを隠してアクセスさせたり,利用者のリクエスト をトレースし,どのようなコンテンツが人気があるか?Refererヘッダを活用して どのような遷移でコンテンツを参照したか?といった二次的な情報を得る場合にも利用 されます.

3.1 HTTPでリクエストを発行するには

HTTPはリクエストを送信し,そのレスポンスを受信するという簡単な動作で実現 できます.Webブラウザは,HTMLを受信したあとに,そのHTMLに記述されている画像 などのURLを繰り返しアクセスしながら画面に表示される処理行っています.
Webクライアントでは,URLでアクセスを行いますが,Socketでプログラミングを 行う場合は,URLをサーバ名(IPアドレス),ポート番号,URI(パス+クエリー文字列 など)に分離する必要があります
サーバ名が指定された場合は,gethostbyname で,IPアドレスを取得します. プロキシサーバを利用する場合は,指定されたURLをそのままリクエストに利用し, サーバへの接続は,プロキシサーバのIPアドレスとポート名に対して行います. レスポンスは,ヘッダ部とボディー部に分かれますが,それらの区別を行うのが 面倒であれば,そのままファイルに保存してしまうということも可能ですが, ヘッダ部とボディー部を明確に分離しながら受信するには,ヘッダ部は 1バイトづつ受信し,[CRLF][CRLF]の組み合わせが出現するまで繰り返します. それ以降に受信したデータはすべてボディーですので,1バイトづつ受信する必要は ありません.
ここで注意しなければならないのは,ヘッダ部のみを必要としている場合でも, サーバが送信したボディー部のデータすべてを受信しなければならないということ です.途中で受信を止めてしまうと,サーバ側は通信がタイムアウトが起こり強制的 に切断するまで受信するまで待ちつづける場合があります.
CGIに対して短期間にこのようなリクエストが多く発生すると,システムリソースを 多く消費し,システムを停止させる原因になってりまう恐れがあります.

3.2 HTTPSに対応したWebクライアント

HTTPを暗号化するために,HTTPSをよく利用されます.
最近は,HTTPSサーバも増えてきていますので,そのような構成を想定されている Webシステム内で,自動的に情報を収集するエージェント機能を作成する場合は, WebクライアントもSSLに対応する必要があります.
HTTPSは,SSL(Secure Sockets Layer)と呼ばれる暗号化プロトコルを利用して通信 を行います.SSLは,Netscape社によって提唱されている暗号化プロトコルで, ソケットでの通信を暗号化を行うときに利用します.
WindowsでSSLで暗号化通信を行うには,WinInet API やWinSock API の内部に 埋め込まれている機能を利用することもできますが,WindowsとUNIX系OSであるLinuxの どちらでも動作するプログラムを作成することですので,OSに依存した機能を 利用せず,多くのOSで利用可能な OpenSSL ライブラリを利用することにしますが,Windows上で動作するアプリケーションを WinInet API を利用して実現する場合の注意点に関しても,簡単に触れておくこと にします.
OpenSSLは,ソースも公開されたフリーのライブラリですが,利用にあたっては サポートページを参照し,利用することに問題がないことを確認しておく必要が あります.


+-----------+----------+---------+-----------------------------+ | | | | | OSI 6/7層 | H T T P | TELNET | F T P | ...... | |(H T T P S)| | | | +-----------+----------+---------+-----------------------------+ +--------------------------------------------------------------+ | S S L | OSI 5層 +--------------------------------------------------------------+ | S O C K E T | +--------------------------------------------------------------+ +------------------------------+ +-----------------------------+ OSI 4層 | T C P | | U D P | +------------------------------+ +-----------------------------+

図1:OSI階層でのSSLの位置


3.3 OpenSSLを使う

まず,OpenSSL Projectのサイトから,ソースファイルをダウンロードします. 圧縮ファイルを展開すると,INSTALL, INSTALL.w32 などの手順書がありますので, それにしたがってコンパイルを行います.
Windows版をコンパイルする場合は, ActivePerlが必要です.
また,MASM(マイクロソフトマクロアセンブラ)があれば,利用可能な場所に置いて おきましょう,このモジュールは,MSDNのDDK(デバイスドライバキット)内にあります が,入手できない場合は,NASM(フリーのアセンブラ)を
http://www.kernel.org/pub/software/devel/nasm/binaries/win32/
からダウンロードする必要があります.
INSTALL.w32 には,BOLAND C や,GNU C など,VisualC++以外でコンパイルを行う 方法が書いてあります.
ここでは,以下の手順は 0.9.6a をベースにVasual C++ と,Linuxでの手順を 示します.

●Visual C++ の場合

まず,Configureを実行します.
>perl Configure VC-WIN32

環境に合わせて,以下のコマンドを実行します.
MASM を使う場合
>ms\do_masm
NASMを使う場合
>ms\do_nasm
アセンブラを利用しない場合
>ms\do_ms
ここまででエラーが発生したら,以降のビルドは正しく 実行できませんので,障害を取り除いて再度実行してください.
DLLをビルドする場合(openssl-0.9.6a\out32dll に作成)
>nmake -f ms\ntdll.mak
スタティックライブラリ版をビルドする場合(openssl-0.9.6a\out32 に作成)
>nmake -f ms\nt.mak
コンパイルが終了したら,DLLの実行テストを行います.
> cd out32dll
> ..\ms\test

●Linux の場合

UNIX系システムでのインストール方法は,INSTALL に詳細な情報がありますが, 以下の手順でコマンドを実行すると,簡単に環境を構築できます.
$ ./config
$ make
$ make test
$ make install
コンパイルが終了したら,インクルードファイル,ライブラリ(アーカイブ)ファイル を,コンパイル時に参照できる場所にコピーを行います.
例えば,Windows の場合, "C:\Program Files\Microsoft Visual Studio\VC98\Include\openssl" また,DLL版を利用する場合,c:\Windows\system32のように, 各アプリケーションが参照できるディレクトリにコピーしておきます.
Linuxの場合,/usr/local/ssl 配下にインストールされます.

UNIX/Linuxlibcrypto.a, libssl.a
Windowslibeay32.dll(libeay32.lib), ssleay32.dll(ssleay32.lib)
表1:ライブラリ名

HTTPでは,標準のポート番号を80に設定しますが, HTTPSは,443とします.
基本的には,以下のような流れで処理を埋め込むことで利用が可能になります.
OpenSSLのAPIを組み込んでSSLで接続した場合,送信・受信は,SSL_read, SSL_write 関数で行うことに注意してください.
実際にHTTPアクセスを行う処理に,OpenSSLを埋め込む場合は,SSLを利用する リクエストとそうでないリクエストを,指定されたURLから動的に判断して処理を 切り替える必要があります.
OpenSSLをスタティックライブラリとして組み込むとモジュールのサイズが, かなり大きくなってしまいます.OpenSSLをsoまたはDLLのようなダイナミックリンク ライブラリとして組み込むことで,モジュールのサイズを小さくすることができます し,アプリケーション本体を再コンパイルすることなく,OpenSSLのみをバージョン アップすることができるようになります.しかし,新しいバージョンのダイナミック リンクライブラリを利用してアプリケーションを実行したとき,すべての アプリケーションがいままでとおり動作する保証があるという訳ではありません.
システム内に存在する複数のアプリケーションでOpenSSLのDLLを利用する場合, ライブラリのバージョンアップによって,問題なく動作していたアプリケーションが, 正しく動作しなくなる可能性があるということです.ライブラリのバージョンアップを 行う度にすべてのアプリケーションをテストして問題がないことが確認できないので あれば,トラブルを避けるためにスタティックリンクを行ったほうがよいでしょう.
なお,Windows版でスタティックリンクを行う場合,アプリケーションが利用する Cランタイムルーチンは,OpenSSLのLIBファイルを作成するときに指定するものと 同じでなければモジュールを作成することができません.


#include <openssl/crypto.h> #include <openssl/x509.h> #include <openssl/pem.h> #include <openssl/ssl.h> #include <openssl/err.h> #include <openssl/rand.h> : : : int sock; SSL_CTX *ctx; SSL *ssl; SSL_METHOD method; const char *seed = "DHJSHD2HSDopHSFewqAJKAJKytFrJKAFuKytJAFJKAS4DFJ"; RAND_seed(seed, strlen(seed)); SSLeay_add_ssl_algorithms(); method = SSLv23_client_method(); SSL_load_error_strings(); ctx = SSL_CTX_new(method); SSL_CTX_set_mode(ctx, SSL_MODE_AUTO_RETRY); /* 接続 */ sock = socket(PF_INET, SOCK_STREAM, 0); connect(sock, (struct sockaddr *)&sockaddr, sizeof(sockaddr)); /* SSL接続 */ ssl = SSL_new(ctx); SSL_set_fd(ssl, sock); SSL_connect(ssl); /* 送信 */ SSL_write(ssl, buff, size); ← send(sock, buff, size, 0); から置き換える /* 受信 */ SSL_read(ssl, buff, size); ← recv(sock, buff, size, 0); から置き換える /* 終了 */ closes(sock); SSL_shutdown(ssl); SSL_free(ssl); SSL_CTX_free(ctx); : : :

リスト1:SSLで通信を行う

3.4 SSLの通信をプロキシサーバ経由で行うには

プロキシサーバ経由でHTTPSの通信を行う場合は,以下のようなリクエストを発行し, プロキシサーバに接続先のHTTPSサーバから公開鍵と証明書を転送するように, 指示を行います.

CONNECT server_name:443 HTTP/1.0[CRLF]

このリクエストの結果として,プロキシサーバはボディー部分に,サーバの証明書 と公開鍵を設定しクライアントに転送します.クライアントは,この情報を用いて リクエストに必要な情報を暗号化して送信を行います.


HTTP/1.1 200 OK[CRLF] ヘッダ[CRLF] ヘッダ[CRLF] : [CRLF] 証明書・公開鍵情報 : :

図2:CONNECTメソッドをリクエストした場合のレスポンス

3.5 WindowsでWinInetAPI を利用する場合の注意点

Windowsでは、OpenSSL を利用しなくてもInternetExplorer と共に配布される, WinInet API を利用して Webクライアントアプリケーションを作成することが できます.このAPIは,簡単に高機能なWebクライアントアプリケーションを作成する ことかでき,HTTPSにも対応しています.
このAPIを利用して,Webクライアントアプリケーションを作成する場合は, 以下の点に注意して利用する必要があります.

  • アプリケーションが動作する環境にInternetExplorerをインストールしてあるか?
  • IISなど,サービスプロセスから動作させると動作が保証されていない.
  • HTTPSで通信を行い,サーバが仮証明書を利用した場合などに,警告ダイアログボックス表示の禁止オプションを指定していない場合,自動的に表示され処理がロックする場合がある.
  • 特に設定を行わない場合,プロキシサーバの設定はIEのオプション設定に依存する.ただし,HTTPSでプロキシサーバ経由でアクセスを行うための制御(CONNECTメソッド)の発行は行わないため,独自に制御処理を作成する必要がある.
  • コンテンツのキャッシュ制御は,特に設定をしない限りInternetExplorerと同じ制御を行い,キャッシュファイルを保存する.

    3.6 Webクライアント機能を持つCGI

    CGIは,サーバ側の機能ですが,その処理中にWebクライアントの機能を持たせる ことも可能です.HTTPは,Webサーバのコンテンツを参照するだけでなく, 分散処理を実現するための通信プロトコルとしても,シンプルでわかりやすいため, 簡単に応用することができます.
    サンプルとして,画面に設定したパラメーターでリクエストを実行するCGIを作成 してみました.このCGIは,ソースレベルでWindowsとLinuxの互換性がありますので, Windowsでウィンドウを開いて処理を行うアプリケーションと比較すると,汎用性が あります.このように,マシンにWebサーバが起動してあればダイアログボックス ベースのアプリケーションが簡単に作成できるのもCGIの魅力です.
    このCGIは,一般のWebブラウザでは行えないリクエストを行うことができますので, Webサーバの動作を調査するときに利用します.
    ただし,リクエスト結果がテキストでないリクエストは行わないでください. 出力結果が表示できませんし,Content-Type に text/html を指定して出力をしている ためWebブラウザの動作が保証できません.
    HTTPSでサーバに接続した場合は,レスポンスヘッダの表示と共に,暗号化に 関する証明書などの情報も出力します.


    int GetWebContents(CGIHANDLE hCgi, const char *lpcszMethod, const char *lpcszUrl, const char *lpcszVer, const char *lpcszHeaders, unsigned int uHeaderSize, const char *lpcszOptions, unsigned int uOptionSize, const char *lpcszUserName, const char *lpcszPassword, const char *lpcszProxy, unsigned short nPort) { HTTPHANDLE hHttp = NULL; unsigned int uStatus = 0; const char *lpcszServerHeaders, *lpcszCertificate; unsigned int uReaded; int iResult = 0; CgiPrintf(hCgi, "Content-type: text/html\r\n" "Pragma: no-cache\r\nCache-Control: no-cache\r\n\r\n"); if((hHttp = HttpCreateHandle(NULL, lpcszProxy, nPort)) == 0){ return E_CREATEHANDLE; } iResult = HttpOpen(hHttp, lpcszUrl, lpcszUserName, lpcszPassword); if(iResult != 0){ goto finish; } iResult = HttpSendRequest(hHttp, lpcszVer, lpcszMethod, lpcszHeaders, uHeaderSize, lpcszOptions, uOptionSize, &uStatus); if(iResult != 0){ goto finish; } CgiPrintf(hCgi, "<html><table border=\"1\" width=\"640\">\r\n" "<tr><th bgcolor=\"#eeeeee\">" "Server Responce Headers and Certificate</th></tr>\r\n" "<tr><td bgcolor=\"#ffffff\"><pre>"); /* ヘッダ出力 */ if((lpcszServerHeaders = HttpGetAllHeaders(hHttp, &uReaded)) != NULL){ CgiPutStdOut(hCgi, lpcszServerHeaders, uReaded); } /* HTTPSアクセス時のサーバ証明書情報出力 */ if((lpcszCertificate = HttpGetServerCertificate(hHttp, &uReaded)) != NULL){ CgiPutStdOut(hCgi, lpcszCertificate, uReaded); } CgiPrintf(hCgi, "</pre></td></tr>\r\n" "<tr><th bgcolor=\"#eeeeee\">Body</th></tr>\r\n<tr><td>"); /* コンテンツ出力 */ do{ char szBuff[256]; iResult = HttpRecive(hHttp, szBuff, sizeof(szBuff), &uReaded); if(iResult != 0){ return iResult; } if(uReaded != 0){ CgiPutStdOut(hCgi, szBuff, uReaded); } }while(uReaded != 0); CgiPrintf(hCgi, "</td></tr>\r\n</table></html>"); finish: HttpClose(hHttp); HttpCloseHandle(hHttp); if(iResult != 0){ CgiPrintf(hCgi, "<h2>Request Error</h2><hr>code = %d", iResult); return iResult; } return 0; }

    リスト2:本特集のHTTPライブラリでリクエストを行い結果をCGIの出力とする

    3.7 ロボット排除規約

    ロボット排除規約(Robot Exclusion Standerd)とは,インターネット上のWebサーバ に対して自動的にアクセスを行い,情報収集を行うアプリケーションの総称である 「ロボット」に対してアクセスされると困る領域を知らせるための規約です.
    ロボットが,無作為にいろいろなサーバにアクセスすると,サーバの負荷の増大 につながり,Webサーバの管理者が本当にサービスを行いたい相手からのリクエスト を受けることができなくなるといった問題を防ぐためのルールとして作成されました. この規約は規則ではなく,あくまでも「マナー」のレベルですので,絶対守る必要が あるわけではありませんが,インターネットを多くの人が共有しているわけですから ロボットを作成する場合は,この「マナー」を守るように心がけましょう.
    詳細については,The Web Robots Pages も参考にするとよいでしょう.

    ●/robots.txt

    1994年に考案された仕組みです.ロボットに対してアクセスを制限したいサーバは, そのドキュメントルートに,robot.txt ファイルを作成しておく必要があります.
    この規約に準拠しているロボットは,まず http://server/robot.txt に対して, リクエストを行い,存在しない(404が返却される)場合は,無条件にアクセスを開始し, 存在した場合は,そのファイルに記述されている条件の範囲内でアクセスを 行います(表2).

    User-Agent制限するユーザーエージェント名
    Disallowアクセスを禁止する領域
    /path/ ... 指定されたパス以降すべてに有効
    /path ... 指定パス内のファイルに対して有効
    Allowアクセスを許可する
    /path/ ... 指定されたパス以降すべてに有効
    /path ... 指定パス内のファイルに対して有効
    表2:robots.txt の設定項目

    すべてのロボットのアクセスを禁止するには,

    User-Agent: *
    Disallow: /

    特定のロボットのアクセスを禁止するには,

    User-Agent: xxxx-robot
    User-Agent: yyyy-searcher
    Disallow: /

    特定のロボットだけ条件をつけてアクセスを許可するには,

    User-Agent: *
    Disallow: /
    User-Agent: yyyy-searcher
    Allow: /
    Disallow: /cgi-bin/
    Disallow: /pl/

    とします.

    ●ロボットタグ

    1996年に考案されたしくみです. /robot.txt を作成できないサーバの場合には,HTML内に<META>タグで, 動作を指定することができます.ロボット<META>タグの内容は,コンマにより 区切られた指令を含んでいます.
    現在定義されているコマンドは,[NO]INDEXと[NO]FOLLOWです.

  • "INDEX"はインデックス作成ロボットがページにインデックスを付けてよいかを指定します.
  • "FOLLOW"はロボットがページのリンクを追跡してよいかを指定します.

    デフォルトは"INDEX"と"FOLLOW"です.
    "ALL"または"NONE"は,すべてオンかすべてオフであるかを設定します

    <META NAME="ROBOTS" CONTENT="ALL,NONE">
    <META NAME="ROBOTS" CONTENT="INDEX,NOINDEX,NOFOLLOW,FOLLOW,FOLLOW">

    NAME=DESCRIPTIONを指定された情報は,検索サービスに利用されることを想定 しています.
    CONTENTには,フォーマット情報を含むことを想定していません.

    <META NAME="DESCRIPTION" CONTENT="...text...">


    HTMLへの設定例をリスト3に示します。


    <html> <head> <meta name="robots" content="noindex,nofollow"> <meta name="description" content="This page ...."> <meta http-equiv="Content-Type" content="text/html; charset=Shift_Jis"> <title>...</title> </head> <body> : : :

    リスト3:HTMLへの設定例