简单的C语言例子,做一个HTTP POST并消耗响应。

我想创建一个非常简单的C语言应用程序,做一个HTTP帖子。 它将接受一些参数,并使用这些参数来构建一个URL。 我只想做一个简单的HTTP POST,并在不使用curl的情况下得到响应(在需要运行的机器上没有也不会安装这些库)。

伪代码:

1.处理2个args

2.将args放入模板URL:http://api.somesite.com/apikey=ARG1&command=ARG2

3.对生成的URL进行POST操作

4.吸收响应

我在Google和SO的搜索中没有得到任何关于这个问题的信息。

解决办法

一条信息有一个标题部分和一个信息主体,中间有一个空行。 即使没有消息正文,也总是需要空行。 头部以一个命令开始,并有另外几行键值对,用冒号和空格隔开。 如果有一个信息主体,它可以是你想要的任何内容。

标题中的行和标题末尾的空白行必须以回车和换行对结束(见https://stackoverflow.com/questions/5757290/http-header-line-break-style),所以这就是为什么这些行的末尾有 \r\n。

一个URL的形式是http://host:port/path?query_string

向一个网站提交请求有两种主要方式:

  • GET:查询字符串是可选的,但如果指定的话,必须是合理的简短。正因为如此,标题可以只是GET命令,而没有其他内容。一个示例信息可以是:

      GET /path?query_string HTTP/1.0\r\n
      \r\n
  • POST:通常情况下,查询字符串中的内容会在信息的正文中出现。正因为如此,标题需要包括Content-Type: 和Content-Length: 属性以及POST命令。 一个示例信息可以是:

      POST /path HTTP/1.0/r/n
      Content-Type: text/plain\n
      Content-Length: 12\r\n
      \query_string
      query_string

因此,为了回答你的问题:如果你感兴趣的URL是http://api.somesite.com/apikey=ARG1&command=ARG2,那么就没有正文或查询字符串,因此也就没有理由进行POST,因为没有任何东西可以放在消息的正文中,所以也没有任何东西可以放在Content-Type:和Content-Length中:

我想如果你真的想,你可以POST。 在这种情况下,你的信息会看起来像:

POST /apikey=ARG1&command=ARG2 HTTP/1.0\r\n
\r\n

因此,为了发送消息,C程序需要:

  • 创建一个套接字
  • 查找 IP 地址
  • 打开套接字
  • 发送请求
  • 等待响应
  • 关闭套接字

发送和接收调用不一定会发送/接收你给它们的所有数据--它们将返回实际发送/接收的字节数。 你可以在一个循环中调用它们并发送/接收剩余的信息。

在这个例子中,我没有做任何真正的错误检查--当某些东西失败时,我只是退出程序。 让我知道它是否对你有用:

#include  /* printf, sprintf */
#include  /* exit */
#include  /* read, write, close */
#include  /* memcpy, memset */
#include  /* socket, connect */
#include  /* struct sockaddr_in, struct sockaddr */
#include  /* struct hostent, gethostbyname */

void error(const char *msg) { perror(msg); exit(0); }

int main(int argc,char *argv[])
{
    /* first what are we going to send and where are we going to send it? */
    int portno =        80;
    char *host =        "api.somesite.com";
    char *message_fmt = "POST /apikey=%s&command=%s HTTP/1.0\r\n\r\n";

    struct hostent *server;
    struct sockaddr_in serv_addr;
    int sockfd, bytes, sent, received, total;
    char message[1024],response[4096];

    if (argc < 3) { puts("Parameters:  "); exit(0); }

    /* fill in the parameters */
    sprintf(message,message_fmt,argv[1],argv[2]);
    printf("Request:\n%s\n",message);

    /* create the socket */
    sockfd = socket(AF_INET, SOCK_STREAM, 0);
    if (sockfd < 0) error("ERROR opening socket");

    /* lookup the ip address */
    server = gethostbyname(host);
    if (server == NULL) error("ERROR, no such host");

    /* 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 */
    total = strlen(message);
    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;
    do {
        bytes = read(sockfd,response+received,total-received);
        if (bytes < 0)
            error("ERROR reading response from socket");
        if (bytes == 0)
            break;
        received+=bytes;
    } while (received < total);

    if (received == total)
        error("ERROR storing complete response from socket");

    /* close the socket */
    close(sockfd);

    /* process response */
    printf("Response:\n%s\n",response);

    return 0;
}

就像其他答案指出的那样,4096字节并不是一个很大的响应。 我随机挑选了这个数字,假设对你的请求的响应会很短。 如果它能大,你有两个选择:

  • 从响应中读取Content-Length: header,然后动态地分配足够的内存来容纳整个响应。
  • 在响应到达时将其写到一个文件中

回答评论中提出的问题的额外信息:

如果你想在消息的正文中POST数据怎么办? 那么你确实需要包括Content-Type:和Content-Length:头文件。 Content-Length:是指在分隔头和正文的空行之后的所有内容的实际长度。

下面是一个样本,它需要以下命令行参数:

  • 主机
  • 端口
  • 命令(GET或POST)
  • 路径(不包括查询数据)
  • 查询数据(GET时放在查询字符串中,POST时放在正文中)。
  • 头信息列表(如果使用POST,Content-Length: 是自动的)。

因此,对于原来的问题,你会运行:

a.out api.somesite.com 80 GET "/apikey=ARG1&command=ARG2"

而对于评论中提出的问题,你应该运行:

a.out api.somesite.com 80 POST / "name=ARG1&value=ARG2" "Content-Type: application/x-www-form-urlencoded"

以下是代码:

#include  /* printf, sprintf */
#include  /* exit, atoi, malloc, free */
#include  /* read, write, close */
#include  /* memcpy, memset */
#include  /* socket, connect */
#include  /* struct sockaddr_in, struct sockaddr */
#include  /* struct hostent, gethostbyname */

void error(const char *msg) { perror(msg); exit(0); }

int main(int argc,char *argv[])
{
    int i;

    /* first where are we going to send it? */
    int portno = atoi(argv[2])>0?atoi(argv[2]):80;
    char *host = strlen(argv[1])>0?argv[1]:"localhost";

    struct hostent *server;
    struct sockaddr_in serv_addr;
    int sockfd, bytes, sent, received, total, message_size;
    char *message, response[4096];

    if (argc < 5) { puts("Parameters:    <path> [ []]"); exit(0); }

    /* How big is the message? */
    message_size=0;
    if(!strcmp(argv[3],"GET"))
    {
        message_size+=strlen("%s %s%s%s HTTP/1.0\r\n");        /* method         */
        message_size+=strlen(argv[3]);                         /* path           */
        message_size+=strlen(argv[4]);                         /* headers        */
        if(argc>5)
            message_size+=strlen(argv[5]);                     /* query string   */
        for(i=6;i5)
            message_size+=strlen(argv[5]);                     /* body           */
    }

    /* allocate space for the message */
    message=malloc(message_size);

    /* fill in the parameters */
    if(!strcmp(argv[3],"GET"))
    {
        if(argc>5)
            sprintf(message,"%s %s%s%s HTTP/1.0\r\n",
                strlen(argv[3])>0?argv[3]:"GET",               /* method         */
                strlen(argv[4])>0?argv[4]:"/",                 /* path           */
                strlen(argv[5])>0?"?":"",                      /* ?              */
                strlen(argv[5])>0?argv[5]:"");                 /* query string   */
        else
            sprintf(message,"%s %s HTTP/1.0\r\n",
                strlen(argv[3])>0?argv[3]:"GET",               /* method         */
                strlen(argv[4])>0?argv[4]:"/");                /* path           */
        for(i=6;i0?argv[3]:"POST",                  /* method         */
            strlen(argv[4])>0?argv[4]:"/");                    /* path           */
        for(i=6;i5)
            sprintf(message+strlen(message),"Content-Length: %d\r\n",strlen(argv[5]));
        strcat(message,"\r\n");                                /* blank line     */
        if(argc>5)
            strcat(message,argv[5]);                           /* body           */
    }

    /* What are we going to send? */
    printf("Request:\n%s\n",message);

    /* create the socket */
    sockfd = socket(AF_INET, SOCK_STREAM, 0);
    if (sockfd < 0) error("ERROR opening socket");

    /* lookup the ip address */
    server = gethostbyname(host);
    if (server == NULL) error("ERROR, no such host");

    /* 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 */
    total = strlen(message);
    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;
    do {
        bytes = read(sockfd,response+received,total-received);
        if (bytes < 0)
            error("ERROR reading response from socket");
        if (bytes == 0)
            break;
        received+=bytes;
    } while (received < total);

    if (received == total)
        error("ERROR storing complete response from socket");

    /* close the socket */
    close(sockfd);

    /* process response */
    printf("Response:\n%s\n",response);

    free(message);
    return 0;
}
评论(14)

杰里的回答很好。然而,它并不能处理大型的回应。一个简单的改变就可以处理这个问题:

memset(response, 0, sizeof(response));
total = sizeof(response)-1;
received = 0;
do {
    printf("RESPONSE: %s\n", response);
    // HANDLE RESPONSE CHUCK HERE BY, FOR EXAMPLE, SAVING TO A FILE.
    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); 
评论(4)