C#やJavaでは簡単にhttp Clientプログラムができる。C言語ではソケットを作成するところから必要です。またWindowsとLinux両方対応するように書いたのでプログラムが長くなる。
(bcc32で動作確認済み)
プログラムは、最初にsocketを生成してWebサーバに接続し、テキスト形式でHTTPリクエストメッセージを作成してWebサーバに送信する。
HTTPリクエストメッセージは、複数行から成り立つ一連のデータ列。ここでいう1行とは、終端にCR(キャリッジリターン、16進の0x0d)とLF(ラインフィード、16進の0x0a)を持つデータの単位を示す。ほぼ、通常のテキスト・データの1行と等しくなる。メッセージ・ヘッダとメッセージボディ部に分かれ、両者は空行(単独のCR+LF)で分割される。
httpを理解するには、telnetで手入力でHTTPをしゃべってみるがいい方法。
https://www.softel.co.jp/blogs/tech/archives/263
まずプログラムをリストする:
#include <stdio.h> /* printf, sprintf */ #include <stdlib.h> /* exit, atoi, malloc, free */ // #include <unistd.h> /* read, write, close */ #include <string.h> /* memcpy, memset */ #ifdef __linux__ #include <sys/socket.h> /* socket, connect */ #include <netdb.h> /* struct hostent, gethostbyname */ #include <netinet/in.h> /* struct sockaddr_in, struct sockaddr */ #elif _WIN32 #include <winsock2.h> #include <ws2tcpip.h> #include <windows.h> #pragma comment(lib,"ws2_32.lib") //Winsock Library #else #endif struct hostent *server; struct sockaddr_in serv_addr; int bytes, sent, received; char response[4096]; int portno; char *host; char *path; void error(const char *msg) { perror(msg); exit(0); } #ifdef _WIN32 win_send_http(char *message){ WSADATA wsa; SOCKET s; printf("\nInitialising Winsock..."); if (WSAStartup(MAKEWORD(2,2),&wsa) != 0) { printf("Failed. Error Code : %d",WSAGetLastError()); return 1; } printf("Initialised.\n"); //Create a socket if((s = socket(AF_INET , SOCK_STREAM , 0 )) == INVALID_SOCKET) { printf("Could not create socket : %d" , WSAGetLastError()); } printf("Socket created.\n"); server = gethostbyname(host); memset(&serv_addr,0,sizeof(serv_addr)); serv_addr.sin_family = AF_INET; serv_addr.sin_port = htons(portno); memcpy(&serv_addr.sin_addr.s_addr,server->h_addr,server->h_length); //Connect to remote server if (connect(s , (struct sockaddr *)&serv_addr , sizeof(serv_addr)) < 0) { printf("connect failed with error code : %d" , WSAGetLastError()); return 1; } puts("Connected"); if( send(s , message , strlen(message) , 0) < 0) { printf("Send failed with error code : %d" , WSAGetLastError()); return 1; } puts("Data Send\n"); //Receive a reply from the server if((received = recv(s , response , 2000 , 0)) == SOCKET_ERROR) { printf("recv failed with error code : %d" , WSAGetLastError()); } puts("Reply received\n"); //Add a NULL terminating character to make it a proper string before printing response[received] = '\0'; puts(response); closesocket(s); WSACleanup(); return 0; // everything OK } #endif #ifdef __linux__ linx_send_http(message) { int sockfd; int total; total = strlen(message); /* lookup the ip address */ server = gethostbyname(host); if (server == NULL) error("ERROR, no such host"); /* create the socket */ sockfd = socket(AF_INET, SOCK_STREAM, 0); if (sockfd < 0) error("ERROR opening socket"); /* fill in the structure */ memset(&serv_addr,0,sizeof(serv_addr)); serv_addr.sin_family = AF_INET; serv_addr.sin_port = htons(portno); memcpy(&serv_addr.sin_addr.s_addr,server->h_addr,server->h_length); /* connect the socket */ if (connect(sockfd,(struct sockaddr *)&serv_addr,sizeof(serv_addr)) < 0) error("ERROR connecting"); /* send the request */ sent = 0; do { bytes = write(sockfd,message+sent,total-sent); if (bytes < 0) error("ERROR writing message to socket"); if (bytes == 0) break; sent+=bytes; } while (sent < total); /* receive the response */ memset(response, 0, sizeof(response)); total = sizeof(response)-1; received = 0; printf("Response: \n"); do { printf("%s", response); memset(response, 0, sizeof(response)); bytes = recv(sockfd, response, 1024, 0); if (bytes < 0) printf("ERROR reading response from socket"); if (bytes == 0) break; received+=bytes; } while (1); if (received == total) error("ERROR storing complete response from socket"); /* close the socket */ close(sockfd); return 0; // everything OK } #endif int main(int argc,char *argv[]) { int i; char message[4096]; portno = atoi(argv[2])>0?atoi(argv[2]):80; host = strlen(argv[1])>0?argv[1]:"localhost"; path = strlen(argv[4])>0?argv[4]:"/"; if (argc < 5) { puts("Parameters: <host> <port> <method> <path> [<data> [<headers>]]"); exit(0); } /* How big is the message? */ if(!strcmp(argv[3],"GET")) { if(argc>5) sprintf(message,"%s %s%s%s HTTP/1.0\r\nHost: %s\r\n", strlen(argv[3])>0?argv[3]:"GET", /* method */ path, /* path */ strlen(argv[5])>0?"?":"", /* ? */ strlen(argv[5])>0?argv[5]:"",host); /* query string */ else sprintf(message,"%s %s HTTP/1.0\r\nHost: %s\r\n", strlen(argv[3])>0?argv[3]:"GET", /* method */ path,host); /* path */ for(i=6;i<argc;i++) /* headers */ {strcat(message,argv[i]);strcat(message,"\r\n");} strcat(message,"\r\n"); /* blank line */ } else { sprintf(message,"%s %s HTTP/1.0\r\nHost: %s\r\n", strlen(argv[3])>0?argv[3]:"POST", /* method */ path,host); /* path */ for(i=6;i<argc;i++) /* headers */ {strcat(message,argv[i]);strcat(message,"\r\n");} if(argc>5) { sprintf(message+strlen(message),"Content-Length: %d\r\n",(int)strlen(argv[5])); sprintf(message+strlen(message),"Content-Type: application/x-www-form-urlencoded\r\n"); } strcat(message,"\r\n"); /* blank line */ if(argc>5) strcat(message,argv[5]); /* body */ sprintf(message,"%s %s HTTP/1.0\r\nHost: %s\r\n", strlen(argv[3])>0?argv[3]:"POST", /* method */ path,host); /* path */ for(i=6;i<argc;i++) /* headers */ {strcat(message,argv[i]);strcat(message,"\r\n");} if(argc>5) { sprintf(message+strlen(message),"Content-Length: %d\r\n",(int)strlen(argv[5])); sprintf(message+strlen(message),"Content-Type: application/x-www-form-urlencoded\r\n"); } strcat(message,"\r\n"); /* blank line */ if(argc>5) strcat(message,argv[5]); /* body */ } printf("Processed\n"); /* What are we going to send? */ printf("Request:\n%s\n",message); #ifdef _WIN32 win_send_http(message); #endif #ifdef __linux__ linx_send_http(message); #endif return 0; }
このプログラムを利用して、例のTinyWebDBを読み書きしてみる。
まずPostで、データを書き込む
C:\Users\chen\Documents\C\http-client>http-client-3 tinydb.work 80 POST /api/storeavalue/ “tag=presentationtimer&value=Just a TEST from C3”
Process 2 Allocating... Processed Request: POST /api/storeavalue/ HTTP/1.0 Host: tinydb.work Content-Length: 47 Content-Type: application/x-www-form-urlencoded tag=presentationtimer&value=Just a TEST from C3 Initialising Winsock...Initialised. Socket created. Connected Data Send Reply received HTTP/1.1 200 OK Server: nginx Date: Tue, 12 Dec 2017 06:56:12 GMT Content-Type: application/json Content-Length: 52 Connection: close X-Powered-By: PHP/5.6.31 Vary: Cookie Set-Cookie: wfvt_2029330401=5a2f7d8c36e15; expires=Tue, 12-Dec-2017 07:26:12 GMT; Max-Age=1800; path=/; httponly Expires: Mon, 26 Jul 1997 05:00:00 GMT Cache-Control: no-cache, must-revalidate ["STORED","presentationtimer","Just a TEST from C3"]
次は、Getで、書き込んだデータを取得
C:\Users\chen\Documents\C\http-client>http-client-3 tinydb.work 80 POST /api/getvalue/ “tag=presentationtimer”
Process 2 Allocating... Processed Request: POST /api/getvalue/ HTTP/1.0 Host: tinydb.work Content-Length: 21 Content-Type: application/x-www-form-urlencoded tag=presentationtimer Initialising Winsock...Initialised. Socket created. Connected Data Send Reply received HTTP/1.1 200 OK Server: nginx Date: Tue, 12 Dec 2017 06:57:10 GMT Content-Type: application/json Content-Length: 51 Connection: close X-Powered-By: PHP/5.6.31 Vary: Cookie Set-Cookie: wfvt_2029330401=5a2f7dc61d61c; expires=Tue, 12-Dec-2017 07:27:10 GMT; Max-Age=1800; path=/; httponly Expires: Mon, 26 Jul 1997 05:00:00 GMT Cache-Control: no-cache, must-revalidate ["VALUE","presentationtimer","Just a TEST from C3"]