执行 HTTP POST 并使用响应的简单 C 示例
Posted
技术标签:
【中文标题】执行 HTTP POST 并使用响应的简单 C 示例【英文标题】:Simple C example of doing an HTTP POST and consuming the response 【发布时间】:2014-03-31 10:19:58 【问题描述】:我想创建一个非常简单的 C 应用程序来执行 HTTP 发布。它需要一些参数,并使用这些参数来构造一个 URL。我只想做一个简单的 HTTP POST 并在不使用 curl 的情况下获取响应(这些库没有也不会安装在需要运行的机器上)。
伪代码:
进程 2 参数
将 args 放入模板 URL:http://api.somesite.com/apikey=ARG1&command=ARG2
在生成的 URL 上执行 POST
消费响应
我的 Google 和 SO 搜索在这件事上没有任何结果。
【问题讨论】:
您使用任何类型的网络框架吗?你用什么操作系统? 它只是一个基本的 Fedora 或 Cent 盒子。网络框架是通常的 sys/socket、netdb、arpa/inet。只是不是 libcurl。 不是 libcurl。您愿意使用任何其他库还是必须是全 POSIX。 不幸的是所有 POSIX。它必须在任何系统上完全独立。 我为您制作了一个示例,但如果没有消息正文,我不明白您为什么要使用 POST。如果所有参数都在查询字符串中,为什么不执行 GET 操作? 【参考方案1】:消息有一个标题部分和一个由空行分隔的消息正文。即使没有消息正文,也始终需要空白行。标头以命令开头,并具有由冒号和空格分隔的键值对的附加行。如果有消息正文,它可以是您想要的任何内容。
标题中的行和标题末尾的空白行必须以回车和换行对结束(请参阅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\r\n
Content-Length: 12\r\n
\r\n
query_string
所以,回答您的问题:如果您有兴趣发布到的 URL 是 http://api.somesite.com/apikey=ARG1&command=ARG2,那么没有正文或查询字符串,因此,没有理由发布,因为没有任何内容可放入消息,因此无需在 Content-Type: 和 Content-Length: 中放入任何内容:
如果你真的想的话,我想你可以发布。在这种情况下,您的消息将如下所示:
POST /apikey=ARG1&command=ARG2 HTTP/1.0\r\n
\r\n
所以要发送 C 程序需要的消息:
创建一个套接字 查找 IP 地址 打开套接字 发送请求 等待回复 关闭套接字发送和接收调用不一定会发送/接收您提供给它们的所有数据 - 它们将返回实际发送/接收的字节数。您可以循环调用它们并发送/接收消息的其余部分。
我在这个示例中没有做的是任何类型的真正错误检查 - 当出现故障时,我只是退出程序。让我知道它是否适合您:
#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-Length: 标头,然后动态分配足够的内存来保存整个响应。 在片段到达时将响应写入文件回答 cmets 中提出的问题的其他信息:
如果您想在消息正文中发布数据怎么办?然后您确实需要包含 Content-Type: 和 Content-Length: 标头。 Content-Length: 是分隔标题和正文的空白行之后所有内容的实际长度。
这是一个采用以下命令行参数的示例:
主持人 端口 命令(GET 或 POST) 路径(不包括查询数据) 查询数据(GET 放入查询字符串,POST 放入正文) 标题列表(内容长度:如果使用 POST,则为自动)因此,对于您将运行的原始问题:
a.out api.somesite.com 80 GET "/apikey=ARG1&command=ARG2"
对于您将运行的 cmets 中提出的问题:
a.out api.somesite.com 80 POST / "name=ARG1&value=ARG2" "Content-Type: application/x-www-form-urlencoded"
代码如下:
#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;
【讨论】:
调用时应该传递哪些参数? 您需要传递将用作 apikey 的内容作为第一个参数,并在第二个参数中传递将用作命令的内容。如果要使用完全不同的查询字符串,则需要更改格式字符串、参数数量和使用消息。 此代码发出格式错误的 HTTP 请求。 HTTP 指定请求行必须以回车/换行对 (\r\n
) 终止,但此代码使用纯换行。
@JohnBollinger 确实如此。感谢您指出。希望编辑后的答案更好。
这个帖子信息有什么问题? "POST /variableName=%s&value=%s HTTP/1.1\r\nContent-Type: application/x-www-form-urlencoded\r\nContent-Length: 4\r\n\r\n\r\n"
我想像 name=reaz 一样发帖。它响应 400 Bad Request【参考方案2】:
经过数周的研究。我想出了以下代码。我相信这是通过 SSL 安全连接到 Web 服务器所需的最低要求。
#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 请求。
您还可以在link 下找到如何在您的系统中安装 openSSL 以及如何编译代码以使其使用secure library 的说明。
【讨论】:
很好的解释!【参考方案3】:杰瑞的回答很棒。但是,它不能处理大量响应。一个简单的改变来处理这个:
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);
【讨论】:
您可以将我的示例中的响应数组变大。我假设他只是取回一些 json 并且没有下载一个大文件,但当然,根据查询,即使是 json 也可能是兆字节...... 我是 C 初学者,您的回答可能是正确的。但是你能在你的答案中添加一个解释吗? 这实际上只是对已接受答案的评论,不应单独尝试回答。 只需在此处添加一件事,这很有效,但是您应该读取缓冲区的大小 - 1 个字节。为了正确查看它,我不会在该打印语句中使用换行符。应该是这样的:bytes = recv(sockfd, response, 1023, 0)
这里接收到的数据将是一个原始响应,我们如何单独解析消息/内容?【参考方案4】:
已添加手柄。 添加了主机标头。 添加 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;
【讨论】:
以上是关于执行 HTTP POST 并使用响应的简单 C 示例的主要内容,如果未能解决你的问题,请参考以下文章
GuzzleHttp:如何从 POST 响应中保存 cookie 并在下一个 POST 中使用它?