HTTPクライアントの作成(2)

ここでは、前述したインチキHTTPクライアントをもうちょっと改造してみます。 前述したインチキHTTPクライアントは何も考えずにサーバからの結果を表示していましたが、今回はHTTPリクエストが成功したのか失敗したのか、までの解析をしたいと思います。 ただし、かなり解析(パース)部分はインチキです。 真面目にHTTPクライアントを書きたい場合には、そのまま使わないことをお勧めいたします。

前回サンプルとの違い

今回は、前回と違って最初の一行を解析しています。 最初の1行とは'\n'があるまでです。

HTTPでは、リクエストに対しての結果が成功なのか失敗なのかは、最初の一行に書いてあります。 具体的には、例えば以下のように帰ってきます。


HTTP/1.0 200 OK

これは、HTTPバージョン1.0による回答で、結果は200(成功)で、「OK」というのは詳細な結果メッセージです。 最後の「OK」の部分はメッセージなので何が入っていても大丈夫です。 例えば、以下のようなメッセージでも大丈夫です。


HTTP/1.0 200 成功だよーーん。

HTTPでリクエストしたURLが無い場合には、以下のようになる場合があります。


HTTP/1.0 404 NOT FOUND

今回は真ん中のステータスコードが404であると書いてあります。 このステータスコードには色々なものがあります。

HTTPクライアントサンプル

以下が、改造版インチキHTTPクライアントです。 改造箇所の説明はソースコードにコメントとして書き込んでいます。


#include <stdio.h>
#include <winsock2.h>

int
main(int argc, char *argv[])
{
 WSADATA wsaData;
 struct sockaddr_in server;
 SOCKET sock;
 char buf[4096];
 char *ptr;
 char *deststr;
 unsigned int **addrptr;

 if (argc != 2) {
	 printf("Usage : %s dest\n", argv[0]);
	 return 1;
 }
 deststr = argv[1];

 if (WSAStartup(MAKEWORD(2,0), &wsaData) != 0) {
	 printf("WSAStartup failed\n");
	 return 1;
 }

 sock = socket(AF_INET, SOCK_STREAM, 0);
 if (sock == INVALID_SOCKET) {
	 printf("socket : %d\n", WSAGetLastError());
	 return 1;
 }

 server.sin_family = AF_INET;
 server.sin_port = htons(80); // HTTPのポートは80番です

 server.sin_addr.S_un.S_addr = inet_addr(deststr);
 if (server.sin_addr.S_un.S_addr == 0xffffffff) {
	 struct hostent *host;

	 host = gethostbyname(deststr);
	 if (host == NULL) {
		 if (WSAGetLastError() == WSAHOST_NOT_FOUND) {
			 printf("host not found : %s\n", deststr);
		 }
		 return 1;
	 }

	 addrptr = (unsigned int **)host->h_addr_list;

	 while (*addrptr != NULL) {
		 server.sin_addr.S_un.S_addr = *(*addrptr);

		 // connect()が成功したらloopを抜けます
		 if (connect(sock,
				(struct sockaddr *)&server,
				sizeof(server)) == 0) {
			break;
		 }

		 addrptr++;
		 // connectが失敗したら次のアドレスで試します
	 }

	 // connectが全て失敗した場合
	 if (*addrptr == NULL) {
		 printf("connect : %d\n", WSAGetLastError());
		 return 1;
	 }
 } else {
	 if (connect(sock,
                     (struct sockaddr *)&server,
                     sizeof(server)) != 0) {
		 printf("connect : %d\n", WSAGetLastError());
		 return 1;
	 }
 }

 // HTTPで「/」をリクエストする文字列を生成
 memset(buf, 0, sizeof(buf));
 _snprintf(buf, sizeof(buf), "GET / HTTP/1.0\r\n\r\n");

 // HTTPリクエスト送信
 int n = send(sock, buf, (int)strlen(buf), 0);
 if (n < 0) {
	 printf("send : %d\n", WSAGetLastError());
	 return 1;
 }

 // サーバからのHTTPメッセージ受信
 // まずは結果(status line)を取得
 //
 // 注意)説明のためかなり手抜きで書いています
 //    本来ならばもっと真面目にパースすべきです
 //
 memset(buf, 0, sizeof(buf));
 n = recv(sock, buf, sizeof(buf), 0);
 if (n < 0) {
	 printf("recv : %d\n", WSAGetLastError());
	 return 1;
 }

 // 最初が "HTTP/" ではじまるかどうかを確認
 if (strncmp("HTTP/", buf, 5) != 0) {
	 printf("status line error !\n");
	 fwrite(buf, n, 1, stdout);
     return 1;
 }

 if (n < 5) {
	 // エラー処理
	 // 本当はもっと真面目に書くべき
	 return 1;
 }

 // HTTP versionを確認
 ptr = buf + 5;
 if (strncmp("1.0 ", ptr, 4) == 0) {
	 printf("is HTTP/1.0\n");
	 ptr += 4;
 } else if (strncmp("1.1 ", ptr, 4) == 0) {
	 printf("is HTTP/1.1\n");
	 ptr += 4;
 } else {
	 printf("unknown HTTP version\n");
	 return 1;
 }

 // 次の項目までスペースをスキップ
 while (ptr < buf + n && *ptr == ' ') {
	 ptr++;
 }

 // HTTPの結果を解析。
 // 本当はもっと真面目に数値を取得して
 // switchとかすべきです
 if (strncmp("200 ", ptr, 4) == 0) {
	 printf("is 200 OK\n");
 } else if (strncmp("302 ", ptr, 4) == 0) {
	 printf("is 302 moved\n");
 } else if (strncmp("404 ", ptr, 4) == 0) {
	 printf("is 404 not found\n");
 } else {
	 printf("other status code\n");
 }

 // status lineの行末まで移動
 while (ptr < buf + n && *ptr != '\n') {
	 ptr++;
 }

 // 残りの部分を表示
 if (ptr < buf + n) {
	 fwrite(ptr, n - (ptr - buf), 1, stdout);
 }

 // さらに残りの部分を表示
 while (n > 0) {
	 memset(buf, 0, sizeof(buf));
	 n = recv(sock, buf, sizeof(buf), 0);
	 if (n < 0) {
		 printf("recv : %d\n", WSAGetLastError());
		 return 1;
	 }

	 // 受信結果を表示
	 fwrite(buf, n, 1, stdout);
 }

 closesocket(sock);

 WSACleanup();

 return 0;
}


折角なので、作成したHTTPクライアントを使ってみたいと思います。 www.google.co.jpに接続すると以下のようになります。 (ただし、下記例では表示スペースに押し込むために一部結果を削ってあります。何となくこんな感じかなぁ程度に見てください。) 前回との違いは強調してあります。


C:\> client.exe www.google.co.jp
is HTTP/1.0
is 302 moved

Location: http://www.google.co.jp/cxfer?c=PREF%3D:TM%3D1105:S%3DrI6jLhtK7m&prev=/
Set-Cookie: PREF=ID=04f0a2d0a3400510:CR=1:TM=110515:LM=1105515:S=55PyTj
S-5evTiH; expires=Sun, 17-Jan-2038 19:14:07 GMT; path=/; domain=.google.com
Content-Type: text/html
Server: GWS/2.1
Content-Length: 217
Date: Mon, 10 Jan 2005 04:48:35 GMT
Connection: Keep-Alive

<HTML><HEAD>
<TITLE>302 Moved</TITLE></HEAD><BODY>
<H1>302 Moved</H1>
The document has moved
<A HREF="http://www.google.co.jp/cxfer?c=PREF%3D:TM%315:S%3D6Gm&prev=/">here</A>.
</BODY></HTML>