执行 HTTPPOST 并使用响应的简单 C 示例

我想创建一个非常简单的 C 应用程序,它执行 HTTP 发布。它将采用一些参数,并使用这些参数构造一个 URL。我只想做一个简单的 HTTP POST 并且不使用 curl 获得响应(这些库不会也不会安装在需要运行它的机器上)。

伪代码:

  1. 进程2 args

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

  3. 对生成的 URL 执行 POST

  4. 消费反应

我在 Google 和 SO 上的搜索没有得到任何关于这个问题的结果。

322646 次浏览

消息具有以空行分隔的标题部分和消息正文。即使没有消息正文,也总是需要空行。标头以命令开始,并且具有由冒号和空格分隔的另外几行键值对。如果有一个消息主体,它可以是任何您希望它是的东西。

标题中的行和标题末尾的空行必须以 carraige return 和 linefeed 对结束(参见 HTTP header line break style) ,所以这就是为什么这些行的末尾有 r n。

A URL has the form of http://host:port/path?query_string

There are two main ways of submitting a request to a website:

  • GET: 查询字符串是可选的,但是如果指定,则必须相当短。因为这个原因,头部可以只是 GET 命令,没有其他东西。一个示例信息可以是:

      GET /path?query_string HTTP/1.0\r\n
    \r\n
    
  • POST: 通常在查询字符串中的内容是在消息的正文中。因此,头部需要包含 Content-Type: 和 Content-Llength: 属性以及 POST 命令。一个示例信息可以是:

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

所以,回答你的问题: 如果你感兴趣的 URL 是 http://api.somesite.com/apikey=ARG1&command=ARG2,那么没有主体或查询字符串,因此,没有理由 POST,因为没有任何东西放在消息的主体,所以没有东西放在内容类型: 和内容长度:

我想如果你真的想发帖的话,你也可以发。如果是这样的话,你的信息看起来应该是这样的:

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

所以 C 程序要传递的信息是:

  • 创建一个插座
  • 查找 IP 地址
  • 打开插座
  • 发送请求
  • 等待回应
  • close the socket

The send and receive calls won't necessarily send/receive ALL the data you give them - they will return the number of bytes actually sent/received. It is up to you to call them in a loop and send/receive the remainder of the message.

What I did not do in this sample is any sort of real error checking - when something fails I just exit the program. Let me know if it works for you:

#include <stdio.h> /* printf, sprintf */
#include <stdlib.h> /* exit */
#include <unistd.h> /* read, write, close */
#include <string.h> /* memcpy, memset */
#include <sys/socket.h> /* socket, connect */
#include <netinet/in.h> /* struct sockaddr_in, struct sockaddr */
#include <netdb.h> /* 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: <apikey> <command>"); 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 the number of received bytes is the total size of the
* array then we have run out of space to store the response
* and it hasn't all arrived yet - so that's a bad thing
*/
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-Llength: 头,然后动态分配足够的内存来保存整个响应。
  • write the response to a file as the pieces arrive

回答评论中提出的问题的补充资料:

What if you want to POST data in the body of the message? Then you do need to include the Content-Type: and Content-Length: headers. The Content-Length: is the actual length of everything after the blank line that separates the header from the body.

下面的示例使用以下命令行参数:

  • host
  • 左舷
  • 命令(GET 或 POST)
  • 路径(不包括查询数据)
  • Query data (将其放入 GET 的查询字符串和 POST 的主体中)
  • 标题列表(如果使用 POST,内容长度: 是自动的)

因此,对于最初的问题,你会运行:

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"

Here is the code:

#include <stdio.h> /* printf, sprintf */
#include <stdlib.h> /* exit, atoi, malloc, free */
#include <unistd.h> /* read, write, close */
#include <string.h> /* memcpy, memset */
#include <sys/socket.h> /* socket, connect */
#include <netinet/in.h> /* struct sockaddr_in, struct sockaddr */
#include <netdb.h> /* 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: <host> <port> <method> <path> [<data> [<headers>]]"); 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;i<argc;i++)                                    /* headers        */
message_size+=strlen(argv[i])+strlen("\r\n");
message_size+=strlen("\r\n");                          /* blank line     */
}
else
{
message_size+=strlen("%s %s HTTP/1.0\r\n");
message_size+=strlen(argv[3]);                         /* method         */
message_size+=strlen(argv[4]);                         /* path           */
for(i=6;i<argc;i++)                                    /* headers        */
message_size+=strlen(argv[i])+strlen("\r\n");
if(argc>5)
message_size+=strlen("Content-Length: %d\r\n")+10; /* content length */
message_size+=strlen("\r\n");                          /* blank line     */
if(argc>5)
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;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\n",
strlen(argv[3])>0?argv[3]:"POST",                  /* method         */
strlen(argv[4])>0?argv[4]:"/");                    /* 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",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 the number of received bytes is the total size of the
* array then we have run out of space to store the response
* and it hasn't all arrived yet - so that's a bad thing
*/
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;
}

Jerry 的回答很棒。但是,它不能处理大的响应。一个简单的改变来处理这个:

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);

把手已加入。
新增主机头。
增加了对 linux/windows 的支持,经过测试(XP,WIN7)。
警告: 错误: “内存区段错误”如果没有主机,路径或端口作为参数。

#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


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


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


int i;
struct hostent *server;
struct sockaddr_in serv_addr;
int bytes, sent, received, total, message_size;
char *message, response[4096];
int portno = atoi(argv[2])>0?atoi(argv[2]):80;
char *host = strlen(argv[1])>0?argv[1]:"localhost";
char *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? */
message_size=0;
if(!strcmp(argv[3],"GET"))
{
printf("Process 1\n");
message_size+=strlen("%s %s%s%s HTTP/1.0\r\nHost: %s\r\n");        /* method         */
message_size+=strlen(argv[3]);                         /* path           */
message_size+=strlen(path);                         /* headers        */
if(argc>5)
message_size+=strlen(argv[5]);                     /* query string   */
for(i=6;i<argc;i++)                                    /* headers        */
message_size+=strlen(argv[i])+strlen("\r\n");
message_size+=strlen("\r\n");                          /* blank line     */
}
else
{
printf("Process 2\n");
message_size+=strlen("%s %s HTTP/1.0\r\nHost: %s\r\n");
message_size+=strlen(argv[3]);                         /* method         */
message_size+=strlen(path);                            /* path           */
for(i=6;i<argc;i++)                                    /* headers        */
message_size+=strlen(argv[i])+strlen("\r\n");
if(argc>5)
message_size+=strlen("Content-Length: %d\r\n")+10; /* content length */
message_size+=strlen("\r\n");                          /* blank line     */
if(argc>5)
message_size+=strlen(argv[5]);                     /* body           */
}
printf("Allocating...\n");
/* 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\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]));
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);
/* lookup the ip address */


total = strlen(message);
/* create the socket */
#ifdef _WIN32
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);
serv_addr.sin_addr.s_addr = inet_addr(server->h_addr);
serv_addr.sin_family = AF_INET;
serv_addr.sin_port = htons(portno);
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();
#endif
#ifdef __linux__
int sockfd;
server = gethostbyname(host);
if (server == NULL) error("ERROR, no such host");
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);
#endif




free(message);


return 0;
}

After weeks of research. I came up with the following code. I believe this is the bare minimum needed to make a secure connection with SSL to a web server.

#include <stdio.h>
#include <openssl/ssl.h>
#include <openssl/err.h>
#include <openssl/bio.h>


#define APIKEY "YOUR_API_KEY"
#define HOST "YOUR_WEB_SERVER_URI"
#define PORT "443"


int main() {


//
//  Initialize the variables
//
BIO* bio;
SSL* ssl;
SSL_CTX* ctx;


//
//   Registers the SSL/TLS ciphers and digests.
//
//   Basically start the security layer.
//
SSL_library_init();


//
//  Creates a new SSL_CTX object as a framework to establish TLS/SSL
//  or DTLS enabled connections
//
ctx = SSL_CTX_new(SSLv23_client_method());


//
//  -> Error check
//
if (ctx == NULL)
{
printf("Ctx is null\n");
}


//
//   Creates a new BIO chain consisting of an SSL BIO
//
bio = BIO_new_ssl_connect(ctx);


//
//  Use the variable from the beginning of the file to create a
//  string that contains the URL to the site that you want to connect
//  to while also specifying the port.
//
BIO_set_conn_hostname(bio, HOST ":" PORT);


//
//   Attempts to connect the supplied BIO
//
if(BIO_do_connect(bio) <= 0)
{
printf("Failed connection\n");
return 1;
}
else
{
printf("Connected\n");
}


//
//  The bare minimum to make a HTTP request.
//
char* write_buf = "POST / HTTP/1.1\r\n"
"Host: " HOST "\r\n"
"Authorization: Basic " APIKEY "\r\n"
"Connection: close\r\n"
"\r\n";


//
//   Attempts to write len bytes from buf to BIO
//
if(BIO_write(bio, write_buf, strlen(write_buf)) <= 0)
{
//
//  Handle failed writes here
//
if(!BIO_should_retry(bio))
{
// Not worth implementing, but worth knowing.
}


//
//  -> Let us know about the failed writes
//
printf("Failed write\n");
}


//
//  Variables used to read the response from the server
//
int size;
char buf[1024];


//
//  Read the response message
//
for(;;)
{
//
//  Get chunks of the response 1023 at the time.
//
size = BIO_read(bio, buf, 1023);


//
//  If no more data, then exit the loop
//
if(size <= 0)
{
break;
}


//
//  Terminate the string with a 0, to let know C when the string
//  ends.
//
buf[size] = 0;


//
//  ->  Print out the response
//
printf("%s", buf);
}


//
//  Clean after ourselves
//
BIO_free_all(bio);
SSL_CTX_free(ctx);


return 0;
}

上面的代码将详细解释如何与远程服务器建立 TLS 连接。

重要提示 : 此代码不检查公钥是否由有效的授权机构签名。意味着我不使用根证书进行验证。不要忘记执行这个检查,否则你将不知道你是否连接正确的网站

当涉及到请求本身时,仅仅是手工编写 HTTP 请求而已。

您还可以在此 链接下找到如何在系统中安装 openSSL 的说明,以及如何编译代码以使其使用 安全图书馆