a C HTTP Client

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"]

 

Leave a Reply

Your email address will not be published. Required fields are marked *