simple http client(Windows)

説明

シンプルな HTTPクライアントです。for Windows

たとえば

C:\ultra> hctest http://img.yahoo.co.jp/images/main6.gif
とすれば、ファイル main6.gif が取得できます。
DEBUGコンパイルした場合は、「httpRawData.bin」という HTTPヘッダが削除されてない ファイルも一緒に取得できます。

コンパイルの仕方は、 Borland C++ Compiler 5.5.1 (フリー) の場合、hctest.c, hctest.h, wsock.c, makefile のあるところをカレントディレクトリにして、

C:\ultra\hctest01> make
でいいです。

wsock.c には関数が数個ありますけど、 getHttp() この関数だけ使えばいいです。 wsockClose() も使えますけど使わなくても不都合はない(はず)。 この getHttp() をとなえるだけで、ファイルが取得できます。

この関数にパラメータとして、

  1. URL構造体と
  2. 取得ファイルを格納するメモリ領域 (あらかじめ FILESIZEMAXバイト確保する必要あり)
を与えれば、取得データのバイト数を返します。 dst (取得ファイルが格納されているメモリ領域の先頭) から、取得データのバイト数ぶん ファイルに書き込んでやればいいです。

このプログラムの制限

ソースコード

hctest.h
#define VERSIONNUMBER "0.1"
#define PROGNAME "hctest"

#include <stdio.h>
#include <stdlib.h>
#include <string.h> //strcmp()

#define URLCHARMAX 256
#define FILENAMECHARMAX 256
#define FILESIZEMAX (1024*1024) //1024KB = 1MB
#define LINECHARMAX 1024

#define exiterror(msg,arg) {printf("\n" PROGNAME ": " msg "\n", arg); exit(EXIT_FAILURE);}
#ifdef DEBUG
#define d_printf(msg,arg) printf(msg "\n",arg)
#else /* not DEBUG */
#define d_printf(msg,arg)
#endif /* DEBUG */

typedef struct {
	char host[URLCHARMAX];
	char path[URLCHARMAX];
	char fname[FILENAMECHARMAX]; //filename
} url_t; //URL type
typedef enum { false, true } bool;

/*
 * wsock.c
 */
void wsockClose(void);
int getHttp(url_t u, unsigned char *dst);
hctest.c
// HTTP Client Test
// Sun Mar 11 2001
// itouh

#include "hctest.h"

int sleepsec = 10;
int recontime = 10;

int main(int argc, char *argv[])
{
	unsigned char *dst;
	size_t fsize; //filesize
	FILE *outfP;
	url_t u;

	void splitUrl(char *urls, url_t *u);

	if(argc!=2){
		printf("Usage:  hctest URL\n");
		exit(EXIT_FAILURE);
	}
	if((dst=malloc(FILESIZEMAX))==NULL)
		exiterror("unable to allocate mem; filesize(%d)", FILESIZEMAX);
	splitUrl(argv[1], &u);
	fsize = getHttp(u, dst);

	/*
	 * ファイルを書き出す
	 */
	if((outfP=fopen(u.fname, "wb"))==NULL)
		exiterror("unable to write '%s'", u.fname);
	if(fwrite(dst, 1, fsize, outfP)!=fsize)
		exiterror("unable to write '%s'", u.fname);
	fclose(outfP);
	d_printf("wrote file %dbytes", fsize);

	wsockClose(); //しなくても構わないかも
	return EXIT_SUCCESS;
}


/*
 * URL文字列から、domainと pathと filenameを得る
 */
static void splitUrl(char *urls, url_t *u)
{
	char *top, *end; //文字列の最初と最後

	d_printf("url=%s", urls);
	memset(u->host, 0x00, URLCHARMAX);
	memset(u->path, 0x00, URLCHARMAX);
	memset(u->fname, 0x00, URLCHARMAX);

	/*
	 * domainを取得   「usrID:pass@domain/」
	 */
	//「http://」を飛ばす
	if(strncmp(urls, "http://", strlen("http://"))==0)
		urls += strlen("http://");
	top = urls; //最初の文字をマーク
	for(;;){
		if(*urls=='/' || *urls=='\0'){
			break;
		}else if(*urls==':' || *urls=='@')
			top = urls+1; //「:」「@」の次の文字を最初の文字にする
		urls++;
	}
	end = urls;
	strncpy(u->host, top, end-top);
	d_printf("host=%s", u->host);

	/*
	 *   path すなわち「/dir/dir/file」を取得
	 */
	if(*urls=='\0')
		strcpy(u->path, "/"); //「http://www.yahoo.co.jp」など
	else
		strcpy(u->path, urls);
	d_printf("path=%s", u->path);


	/*
	 * filenameを取得
	 */
	{
		char s[URLCHARMAX];
		int addext = true; //拡張子を付加するかどうか
		char *p = s;
		top = s;
		strcpy(s, u->path);

		while(*p!='\0'){
			if(*p=='/')
				top = p+1; //ファイル名の先頭文字
			if(*p=='.')
				addext = false; //拡張子が既にある
			if(*p=='?'){
				*p = '\0';
				break;
			}
			p++;
		}
		if(strcmp(s, "/")==0)
			strcpy(u->fname, "index.html");
		else if(addext==true)
			sprintf(u->fname, "%s.html", top);
		else
			strcpy(u->fname, top);
	}		
	d_printf("filename=%s", u->fname);
}
wsock.c
//winsock 関連

#include "hctest.h"
#include <winsock.h> //WSA..(), connect(), recv(), send(), close()

#define AGENTNAME PROGNAME " " VERSIONNUMBER " (Windows)"
#define SERVERPORT 80
#define BUFMAX 1500
#define HHS1 "\n\n" //http header stop
#define HHS2 "\r\n\r\n"

static void wsockInit(void)
{
	WSADATA winsockinfo;

	//Winsock version 1.1(0x0101)
	if(WSAStartup(0x0101, &winsockinfo)!=0)
		exiterror("WSAStartup",0);

	d_printf("winsock info:", 0);
	d_printf("  version=%d",winsockinfo.wVersion);
	d_printf("  description=%s",winsockinfo.szDescription);
	d_printf("  systemStatus=%s",winsockinfo.szSystemStatus);
	d_printf("  max sockets=%d",winsockinfo.iMaxSockets);
	d_printf("  max datagram size=%d",winsockinfo.iMaxUdpDg);
	//d_printf("  vender info=%s",winsockinfo.lpVendorInfo);
}

void wsockClose(void)
{
	if(WSACleanup()!=0)
		exiterror("WSACleanup", 0);
}

// IPアドレスを取得
// param:
//   ドメイン文字列
//   IPアドレス構造体
static void getIp(char *host, struct sockaddr_in *addr)
{
	static char prevhost[URLCHARMAX] = "\0";

	/*
	 * DNSにといあわせて IPアドレスを取得
	 */
	if(strcmp(prevhost, host)==0){
		//前回とdomainが同じなら何もしない
		return;
	}else{
		struct hostent *hst;

		//次回のためにコピーしておく
		strcpy(prevhost, host);

		//DNSから、IPアドレスを得る
		hst = gethostbyname(host);
		if(hst == NULL){
			exiterror("unable to locate the server(%s)",host);
		}else if(*(hst->h_addr_list)==NULL){
			exiterror("unable to locate the server(%s)",host);
		}

		/* addr に値をsetする */
		addr->sin_family = AF_INET;
		addr->sin_port = htons(SERVERPORT);
		addr->sin_addr = *((LPIN_ADDR)*hst->h_addr_list);
		memset(addr->sin_zero, 0x00, 8);
	}
	d_printf("got IP address", 0);
}



// http receive  全部受けとる
// param:
//      sockaddr(IPアドレス情報)
//      http requestヘッダ
//      書き込みエリア
// return value:
//      受けとり完了 .. ファイルサイズ
//      ソケットエラー(回線切断がおこった) .. 0
static int httpRecv(struct sockaddr_in *addr, const char *reqstr, unsigned char *dst)
{
	unsigned char *p = dst; //書き込みエリアのポインタ
	SOCKET sok;

	//create socket(addressFamily, typeOfSocket, protocol)
	if((sok=socket(AF_INET, SOCK_STREAM, 0))==INVALID_SOCKET)
		return 0; //exiterror("socket", 0);

	//connect(SOCKET, socketAddress, length)
	if(connect(sok, (struct sockaddr*)addr, sizeof(*addr))==SOCKET_ERROR)
		return 0; //exiterror("unable to connect", 0);
	//send
	if(send(sok, reqstr, strlen(reqstr), 0)==SOCKET_ERROR)
		return 0; //exiterror("unable to send data: httpRequest", 0);

	//receive
	memset(p, 0x00, FILESIZEMAX);
	for(;;){
		unsigned char buf[BUFMAX];
		int n; //number of byte
		n = recv(sok, (char *)buf, sizeof(buf), 0);
		if(n == SOCKET_ERROR)
			return 0; //exiterror("socket error.  Please retry.", 0);
		if(n == 0)
			break;
		if(p-dst > FILESIZEMAX)
			exiterror("received filesize(%dbytes) is too big", p-dst);
		memcpy(p, buf, n);
		p += n;
	}
	closesocket(sok);
	d_printf("receive http raw data %dbytes", p-dst);
#ifdef DEBUG
	{
		// http rawデータを ファイルに書き出し
		FILE *outfP;
		if((outfP=fopen("httpRawData.bin", "wb"))==NULL)
			exiterror("unable to write '%s'", "httpRawData.bin");
		if(fwrite(dst, 1, p-dst, outfP)!=(size_t)(p-dst))
			exiterror("unable to write '%s'", "httpRawData.bin");
		fclose(outfP);
		d_printf("wrote 'httpRawData.bin'", 0);
	}
#endif
	return p - dst;
}


//httpデータを取得する
// param: URL構造体、取得した httpデータの格納場所
// retvalue: 取得した httpデータの size(最大 FILESIZEMAX)
int getHttp(url_t u, unsigned char *dst)
{
	static struct sockaddr_in addr; //hostname から得る IPアドレス情報
	               //前回とIPaddrが同じとき、情報を再利用するので static
	char reqstr[LINECHARMAX*4]; //http request文字列
	size_t rawsize, newsize; //http rawデータ/httpヘッダ削除後の size
	static bool flaginit = false; //wsockInitを呼んだかどうか
	int recon = 0; //再接続回数
	unsigned char *src; //http rawデータを格納する
	unsigned char *p; //http rawデータのポインタ

	extern int sleepsec; //再接続までの秒数
	extern int recontime; //再接続を何回試みるか

	//func prototype
	void wsockInit(void);
	void getIp(char *host, struct sockaddr_in *addr);
	int httpRecv(struct sockaddr_in *addr, const char *reqstr, unsigned char *dst);
	//まだしてない場合、winsockを初期化
	if(flaginit == false){
		wsockInit();
		flaginit = true;
	}

	//IPアドレスを取得する
	getIp(u.host, &addr); //url.path、url.hostと addrを得る

	/*
	 * httpデータを取得するための SEND文字列
	 */
	d_printf("agent name=%s",AGENTNAME);
	sprintf(reqstr,
		"GET %s HTTP/1.0\r\n"
		"User-Agent: %s\r\n"
		"Host: %s\r\n"
		"Connection: close\r\n\r\n",
		u.path, AGENTNAME, u.host);
	/*
	 * httpデータを取得するための領域
	 */
	if((src=malloc(FILESIZEMAX))==NULL)
		exiterror("unable to allocate mem: filesize(%d)", FILESIZEMAX);
	/*
	 * httpデータを取得
	 */
	while((rawsize=httpRecv(&addr, reqstr, src))==0){
		//ソケットエラー(回線切断)のときは、再接続
		int i;
		recon++;
		d_printf("socket error %d times",recon);
		if(recon > recontime)
			exiterror("socket error %d times", recontime);
		//  数秒待つ
		for(i=0; i<sleepsec; i++){
			printf(".");
			Sleep(1000); //1秒
		}
		printf("\n");
	}

	//http rawデータを整形する (httpヘッダを削除)
	p = src;
	for(;;){
		if(memcmp(p,HHS1,strlen(HHS1))==0){
			p += strlen(HHS1);
			break;
		}else if(memcmp(p, HHS2,strlen(HHS2))==0){
			p += strlen(HHS2);
			break;
		}
		p++;
		if(p >= src+rawsize)
			exiterror("unable to find END of HTTP HEADER", 0);
	}

	//dstに httpデータをcopyする
	newsize = rawsize - (p - src);
	memcpy(dst, p, newsize);

	free(src); //src = NULL;
	return newsize;
}
makefile
all:
	implib wsock32.lib c:\windows\system\wsock32.dll
	bcc32 -O2 hctest.c wsock.c wsock32.lib
DEBUGコンパイルするときは、
	bcc32 -w -DDEBUG hctest.c wsock.c wsock32.lib
こう変えてください。

実際に実行すると

DEBUGコンパイルして実行したので、いろいろ情報がでてます。printfデバッグはよくないかな..。

C:\ultra>  hctest.exe http://img.yahoo.co.jp/images/main6.gif
url=http://img.yahoo.co.jp/images/main6.gif
host=img.yahoo.co.jp
path=/images/main6.gif
filename=main6.gif
winsock info:
  version=257
  description=Microsoft wsock32.dll, ver2.2, 32bit of May  6 1999, at 19:22:51.
  systemStatus=On Win95.
  max sockets=32767
  max datagram size=65467
got IP address
agent name=hctest 0.1 (Windows)
receive http raw data 5546bytes
wrote 'httpRawData.bin'
wrote file 5439bytes

C:\ultra>

「http://www.yahoo.co.jp」も処理できる。保存するときの ファイル名は index.html になるようにしてある。

C:\ultra> hctest http://www.yahoo.co.jp
url=http://www.yahoo.co.jp
host=www.yahoo.co.jp
path=/
filename=index.html
winsock info:
  version=257
  description=Microsoft wsock32.dll, ver2.2, 32bit of May  6 1999, at 19:22:51.
  systemStatus=On Win95.
  max sockets=32767
  max datagram size=65467
got IP address
agent name=hctest 0.1 (Windows)
receive http raw data 21527bytes
wrote 'httpRawData.bin'
wrote file 21408bytes

C:\ultra>

? を含んだのも処理できる。& は、ここでは MS-DOSプロンプトでやっているので動作してるけど、bashを使ってるとうまく渡せない (そのときは & の前にバックスラッシュを置けばよい)

C:\ultra> hctest http://piza.2ch.net/test/read.cgi?bbs=cook&key=968752127&ls=50
url=http://piza.2ch.net/test/read.cgi?bbs=cook&key=968752127&ls=50
host=piza.2ch.net
path=/test/read.cgi?bbs=cook&key=968752127&ls=50
filename=read.cgi
winsock info:
  version=257
  description=Microsoft wsock32.dll, ver2.2, 32bit of May  6 1999, at 19:22:51.
  systemStatus=On Win95.
  max sockets=32767
  max datagram size=65467
got IP address
agent name=hctest 0.1 (Windows)
receive http raw data 15821bytes
wrote 'httpRawData.bin'
wrote file 15650bytes

C:\ultra>

itouh