シンプルな 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>