Jak zrobić żądanie HTTP get w C bez libcurl?

Chcę napisać program w języku C do generowania żądania Get bez użycia zewnętrznych bibliotek. Czy jest to możliwe przy użyciu tylko bibliotek C, używając gniazd ? Myślę o stworzeniu pakietu http (przy użyciu odpowiedniego formatowania) i wysłaniu go na serwer. Czy to jedyny możliwy sposób, czy jest lepszy sposób ?

3 answers

Używając gniazd BSD lub, jeśli jesteś nieco ograniczony, powiedzmy, że masz trochę RTO, jakiś prostszy stos TCP, jak lwIP, możesz utworzyć żądanie GET / POST.

Istnieje wiele implementacji open-source. Zobacz "happyhttp" jako przykład ( http://scumways.com/happyhttp/happyhttp.html ). Wiem, że jest to C++, a nie C, ale jedyną rzeczą, która jest " zależna od C++", jest zarządzanie łańcuchami/tablicami, więc łatwo ją przenieść do czystego C.

Uwaga, nie ma "pakietów", ponieważ HTTP zwykle jest przenoszony przez połączenie TCP, więc technicznie istnieje tylko strumień symboli w formacie RFC. Ponieważ żądania http są zwykle wykonywane w sposób connect-send-disconnect, można faktycznie nazwać to "pakietem".

Zasadniczo, gdy masz otwarte Gniazdo (sockfd) "wszystko", co musisz zrobić, to coś w stylu

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);
    }          
}
 24
Author: Viktor Latypov,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-06-26 13:50:46

POSIX 7 minimal runnable example

#define _XOPEN_SOURCE 700

#include <assert.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include <arpa/inet.h>
#include <netdb.h> /* getprotobyname */
#include <netinet/in.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.
     *
     * The second read hangs for a few seconds, until the server times out.
     *
     * Either server or client has to close the connection.
     *
     * We are not doing it, and neither is the server, likely to make serving the page faster
     * to allow fetching HTML, CSS, Javascript and images in a single connection.
     *
     * The solution is to parse Content-Length to see if the HTTP response is over,
     * and close it then.
     *
     * http://stackoverflow.com/a/25586633/895245 says that if Content-Length
     * is not sent, the server can just close to determine length.
     **/
    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);
}

Użycie

Kompilacja:

gcc -o wget wget.c

Get http://example.com i wyjście na stdout:

./wget example.com

IP:

./wget 104.16.118.182

To polecenie zawiesza się dla większości serwerów do czasu timeout, a to jest oczekiwane:

  • serwer lub klient muszą zamknąć połączenie
  • większość serwerów HTTP pozostawia połączenie Otwarte do czasu oczekiwania na dalsze żądania, np. JavaScript, CSS i obrazów po stronie HTML
  • możemy przeanalizować odpowiedź i zamknąć ją, gdy odczytywane są bajty o długości zawartości, ale nie zrobiliśmy tego dla uproszczenia

Testowane na Ubuntu 15.10.

Przykład po stronie serwera: wysyłanie i odbieranie pliku w socket programowanie w Linuksie z C / C++ (GCC / G++)

GitHub upstream: https://github.com/cirosantilli/cpp-cheat/blob/88d0c30681114647cce456c2e17aa2c5b31abcd0/posix/socket/wget.c

 8
Author: Ciro Santilli 新疆改造中心 六四事件 法轮功,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-10-18 20:06:08

" bez żadnych zewnętrznych bibliotek " ściśle mówiąc wykluczyłoby również libc, więc wszystkie syscalle musiałbyś pisać samodzielnie. Wątpię, żeby to było tak surowe. Jeśli nie chcesz łączyć się z inną biblioteką i nie chcesz kopiować kodu źródłowego z innej biblioteki do swojej aplikacji, najlepszym rozwiązaniem jest bezpośrednie radzenie sobie ze strumieniem TCP za pomocą interfejsu API gniazd.

Utworzenie http i wysłanie go przez połączenie z gniazdem TCP jest łatwe, ponieważ czyta odpowiedź. Analizuje odpowiedź, która będzie naprawdę trudna, szczególnie jeśli dążysz do wsparcia dość dużej części standardu. Rzeczy takie jak Strony błędów, przekierowania, negocjacje treści itp.mogą utrudnić nam życie, jeśli mówimy o dowolnych serwerach internetowych. Jeśli z drugiej strony serwer jest dobrze zachowany, a prosty komunikat o błędzie jest odpowiedni dla każdej nieoczekiwanej odpowiedzi serwera, to jest to również dość proste.

 4
Author: MvG,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-06-26 13:58:32