如何在没有libcurl的情况下在C中发出HTTP get请求?

我想编写一个C程序来生成Get Request而不使用任何外部库。 这可能只使用C库,使用套接字吗? 我正在考虑制作一个http数据包(使用正确的格式)并将其发送到服务器。 这是唯一可能的方式还是有更好的方法?

使用BSD套接字,或者如果你有点限制,说你有一些RTOS,一些更简单的TCP堆栈,比如lwIP,你可以形成GET / POST请求。

有许多开源实现。 请参阅“happyhttp”作为示例( http://scumways.com/happyhttp/happyhttp.html )。 我知道,它是C ++,而不是C,但唯一的是“C ++依赖”,它有一个字符串/数组管理,所以它很容易移植到纯C.

请注意,没有“数据包”,因为HTTP通常通过TCP连接进行传输,因此从技术上讲,只有RFC格式的符号流。 由于http请求通常以connect-send-disconnect方式完成,因此实际上可能将其称为“数据包”。

基本上,一旦你有一个开放的套接字(sockfd),你需要做的就是“全部”

char sendline[MAXLINE + 1], recvline[MAXLINE + 1]; char* ptr; size_t n; /// Form request snprintf(sendline, MAXSUB, "GET %s HTTP/1.0\r\n" // POST or GET, both tested and works. Both HTTP 1.0 HTTP 1.1 works, but sometimes "Host: %s\r\n" // but sometimes HTTP 1.0 works better in localhost type "Content-type: application/x-www-form-urlencoded\r\n" "Content-length: %d\r\n\r\n" "%s\r\n", page, host, (unsigned int)strlen(poststr), poststr); /// Write the request if (write(sockfd, sendline, strlen(sendline))>= 0) { /// Read the response while ((n = read(sockfd, recvline, MAXLINE)) > 0) { recvline[n] = '\0'; if(fputs(recvline,stdout) == EOF) { cout << ("fputs erros"); } /// Remove the trailing chars ptr = strstr(recvline, "\r\n\r\n"); // check len for OutResponse here ? snprintf(OutResponse, MAXRESPONSE,"%s", ptr); } } 

POSIX 7最小可运行示例

我们来取http://example.com 。

wget.c

 #define _XOPEN_SOURCE 700 #include  #include  #include  /* getprotobyname */ #include  #include  #include  #include  #include  #include  #include  int main(int argc, char** argv) { char buffer[BUFSIZ]; enum CONSTEXPR { MAX_REQUEST_LEN = 1024}; char request[MAX_REQUEST_LEN]; char request_template[] = "GET / HTTP/1.1\r\nHost: %s\r\n\r\n"; struct protoent *protoent; char *hostname = "example.com"; in_addr_t in_addr; int request_len; int socket_file_descriptor; ssize_t nbytes_total, nbytes_last; struct hostent *hostent; struct sockaddr_in sockaddr_in; unsigned short server_port = 80; if (argc > 1) hostname = argv[1]; if (argc > 2) server_port = strtoul(argv[2], NULL, 10); request_len = snprintf(request, MAX_REQUEST_LEN, request_template, hostname); if (request_len >= MAX_REQUEST_LEN) { fprintf(stderr, "request length large: %d\n", request_len); exit(EXIT_FAILURE); } /* Build the socket. */ protoent = getprotobyname("tcp"); if (protoent == NULL) { perror("getprotobyname"); exit(EXIT_FAILURE); } socket_file_descriptor = socket(AF_INET, SOCK_STREAM, protoent->p_proto); if (socket_file_descriptor == -1) { perror("socket"); exit(EXIT_FAILURE); } /* Build the address. */ hostent = gethostbyname(hostname); if (hostent == NULL) { fprintf(stderr, "error: gethostbyname(\"%s\")\n", hostname); exit(EXIT_FAILURE); } in_addr = inet_addr(inet_ntoa(*(struct in_addr*)*(hostent->h_addr_list))); if (in_addr == (in_addr_t)-1) { fprintf(stderr, "error: inet_addr(\"%s\")\n", *(hostent->h_addr_list)); exit(EXIT_FAILURE); } sockaddr_in.sin_addr.s_addr = in_addr; sockaddr_in.sin_family = AF_INET; sockaddr_in.sin_port = htons(server_port); /* Actually connect. */ if (connect(socket_file_descriptor, (struct sockaddr*)&sockaddr_in, sizeof(sockaddr_in)) == -1) { perror("connect"); exit(EXIT_FAILURE); } /* Send HTTP request. */ nbytes_total = 0; while (nbytes_total < request_len) { nbytes_last = write(socket_file_descriptor, request + nbytes_total, request_len - nbytes_total); if (nbytes_last == -1) { perror("write"); exit(EXIT_FAILURE); } nbytes_total += nbytes_last; } /* Read the response. */ fprintf(stderr, "debug: before first read\n"); while ((nbytes_total = read(socket_file_descriptor, buffer, BUFSIZ)) > 0) { fprintf(stderr, "debug: after a read\n"); write(STDOUT_FILENO, buffer, nbytes_total); } fprintf(stderr, "debug: after last read\n"); if (nbytes_total == -1) { perror("read"); exit(EXIT_FAILURE); } close(socket_file_descriptor); exit(EXIT_SUCCESS); } 

GitHub上游 。

编译:

 gcc -ggdb3 -std=c99 -Wall -Wextra -o wget wget.c 

获取http://example.com并输出到stdout:

 ./wget example.com 

对于大多数服务器,此命令会挂起,直到超时,这是预期的:

  • 服务器或客户端必须关闭连接
  • 我们(客户)没有做
  • 大多数HTTP服务器保持连接打开,直到超时期望进一步请求,例如JavaScript,CSS和HTML页面后面的图像
  • 我们可以解析响应,并在读取Content-Length字节时关闭,但我们并不简单。 需要什么HTTP响应标头说如果没有发送Content-Length ,服务器可以关闭以确定长度。

连接部分也适用于IP:

 host example.com 

得到:

 example.com has address 93.184.216.34 example.com has IPv6 address 2606:2800:220:1:248:1893:25c8:1946 

所以我们这样做:

 ./wget 93.184.216.34 

但是,回复是一个错误,因为我们没有在我们的程序中正确设置Host:这在HTTP 1.1中是必需的 。

可以找到服务器示例: 使用C / C ++(GCC / G ++)在Linux中使用套接字编程发送和接收文件

在Ubuntu 18.04上测试过。

“没有任何外部库”严格来说也会排除libc,所以你必须自己编写所有的系统调用。 我怀疑你的意思是严格的。 如果您不想链接到另一个库,并且不希望将源代码从另一个库复制到您的应用程序中,那么使用套接字API直接处理TCP流是您最好的方法。

正如阅读答案一样,创建HTTP请求并通过TCP套接字连接发送它很容易。 它解析的答案真的很棘手,特别是如果你的目标是支持相当大的标准部分。 如果你正在与任意网络服务器交谈,那么错误页面,重定向,内容协商等等都会让我们的生活变得非常困难。 另一方面,如果已知服务器表现良好,并且简单的错误消息可以用于任何意外的服务器响应,那么这也是相当简单的。