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

asu*_*hak 35 c sockets networking http http-headers

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

Vik*_*pov 29

使用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)
        {
            printf("fputs() error\n");
        }

        /// Remove the trailing chars
        ptr = strstr(recvline, "\r\n\r\n");

        // check len for OutResponse here ?
        snprintf(OutResponse, MAXRESPONSE,"%s", ptr);
    }          
}
Run Code Online (Sandbox Code Playgroud)

  • @asudhak - 这很有效,直到此代码必须在公司工作环境中运行,其中唯一的Internet访问是通过代理服务器进行的.通过HTTP代理获取URL的协议与直接TCP略有不同. (6认同)

Cir*_*四事件 14

POSIX 7最小可运行的例子

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

wget.c

#define _XOPEN_SOURCE 700
#include <arpa/inet.h>
#include <assert.h>
#include <netdb.h> /* getprotobyname */
#include <netinet/in.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <unistd.h>

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);
}
Run Code Online (Sandbox Code Playgroud)

GitHub上游.

编译:

gcc -ggdb3 -std=c99 -Wall -Wextra -o wget wget.c
Run Code Online (Sandbox Code Playgroud)

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

./wget example.com
Run Code Online (Sandbox Code Playgroud)

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

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

连接部分也适用于IP:

char request_template[] = "GET / HTTP/1.1\r\nHost: %s\r\nConnection: close\r\n\r\n";
Run Code Online (Sandbox Code Playgroud)

得到:

host example.com
Run Code Online (Sandbox Code Playgroud)

所以我们这样做:

example.com has address 93.184.216.34
example.com has IPv6 address 2606:2800:220:1:248:1893:25c8:1946
Run Code Online (Sandbox Code Playgroud)

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

在Ubuntu 18.04上测试过.

服务器示例

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

  • @CroCo 看到源评论:“第二次读取挂起几秒钟。[...]”。服务器或客户端必须关闭连接。我们没有关闭,所以服务器也没有关闭。这可能会优化在一个连接中完成的多个 HTTP 请求,这是一种常见情况(获取 HTML、获取 CSS、获取图像)。在 HTTP 的情况下,客户端通常必须解析输出并检查响应是否结束并使用 `Content-Length:` 关闭,但我不想在这个简单的例子中解析 HTTP。 (3认同)
  • 谢谢@CiroSantilli新疆改造中心996ICU六四事件 - 这太棒了!只需将“Connection: close”添加到请求标头即可在第一个请求后自动关闭连接,否则默认情况下,http1.1 连接将保持活动状态,直到服务器或客户端关闭。 (2认同)

MvG*_*MvG 5

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

创建HTTP请求并通过TCP套接字连接发送它很容易,就像阅读答案一样。它解析的答案将是非常棘手的,特别是如果您打算支持相当大一部分标准。错误页面,重定向,内容协商等操作可能使我们的生活变得非常艰难,如果您正在与任意Web服务器通信。另一方面,如果已知服务器行为良好,并且对于任何意外的服务器响应都可以使用简单的错误消息,那么这也相当简单。