Comment faire une requête HTTP get en C sans libcurl?

Je veux écrire un programme C pour générer une Requête Get, sans utiliser de bibliothèques externes. Est-ce possible en utilisant uniquement des bibliothèques C, en utilisant des sockets ? Je pense à créer un paquet http (en utilisant un formatage approprié) et à l'envoyer au serveur. Est-ce le seul moyen possible ou y a-t-il un meilleur moyen ?

28

3 réponses

En utilisant des sockets BSD ou, si vous êtes un peu limité, disons que vous avez des RTOS, une pile TCP plus simple, comme lwIP, vous pouvez former la requête GET/POST.

, Il existe un certain nombre d'implémentations open source. Voir le "happyhttp" comme un exemple ( http://scumways.com/happyhttp/happyhttp.html ). Je sais, c'est C++, Pas C, mais la seule chose qui est "C++-dépendant" il y a une gestion de chaîne / tableau, donc il est facilement porté en C. pur

Attention, il n'y a pas de "paquets", puisque HTTP est généralement transféré sur la connexion TCP, donc techniquement il n'y a qu'un flux de symboles au format RFC. Puisque les requêtes http sont généralement effectuées de manière connect-send-disconnect, on peut en fait appeler cela un "paquet".

Fondamentalement, une fois que vous avez un socket ouvert (sockfd) "tout" que vous avez à faire est quelque chose comme

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
répondu Viktor Latypov 2012-06-26 13:50:46

POSIX 7 minimum praticable exemple

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

L'Utilisation de

Compiler:

gcc -o wget wget.c

Obtenir http://example.com {[16] } et sortie vers stdout:

./wget example.com

IP:

./wget 104.16.118.182

Cette commande se bloque pour la plupart des serveurs jusqu'au délai d'attente, ce qui est attendu:

  • le serveur ou le client doit fermer la connexion
  • la plupart des serveurs HTTP laissent la connexion ouverte jusqu'à ce qu'un délai d'attente attende d'autres requêtes, par exemple JavaScript, CSS et images suite à une page HTML
  • nous pourrions analyser la réponse, et fermer lorsque les octets de longueur de contenu sont lus, mais nous ne l'avons pas fait pour simplifier

Testé sur Ubuntu 15.10.

Un exemple côté serveur à: envoyer et recevoir un fichier en programmation socket sous Linux avec C / C++ (GCC / G++)

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

8

" sans aucune bibliothèque externe " à proprement parler exclurait également la libc, donc vous devriez écrire tous les appels système vous-même. Je doute que tu le penses aussi strict. Si vous ne voulez pas créer de lien vers une autre bibliothèque et ne voulez pas copier le code source d'une autre bibliothèque dans votre application, alors traiter directement le flux TCP en utilisant l'API socket est votre meilleure approche.

Créer la requête HTTP et l'envoyer via une connexion de socket TCP est facile, car c'est la lecture de la réponse. C'est l'analyse de la réponse qui va être très délicate, surtout si vous visez à soutenir une partie raisonnablement importante de la norme. Des choses comme les pages d'erreur, les redirections, la négociation de contenu et ainsi de suite peuvent rendre notre vie assez difficile si vous parlez à des serveurs Web arbitraires. Si d'un autre côté le serveur est connu pour être bien élevé, et qu'un simple message d'erreur est correct pour toute réponse inattendue du serveur, alors c'est raisonnablement simple aussi.

4
répondu MvG 2012-06-26 13:58:32