シンプルな HTTPクライアントです。for Windows
たとえば
C:\ultra> hctest http://img.yahoo.co.jp/images/main6.gifとすれば、ファイル main6.gif が取得できます。
コンパイルの仕方は、 Borland C++ Compiler 5.5.1 (フリー) の場合、hctest.c, hctest.h, wsock.c, makefile のあるところをカレントディレクトリにして、
C:\ultra\hctest01> makeでいいです。
wsock.c には関数が数個ありますけど、 getHttp() この関数だけ使えばいいです。 wsockClose() も使えますけど使わなくても不都合はない(はず)。 この getHttp() をとなえるだけで、ファイルが取得できます。
この関数にパラメータとして、
このプログラムの制限
#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.libDEBUGコンパイルするときは、
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>