2012-06-26 23 views
26

Desidero scrivere un programma C per generare una richiesta di ricezione senza utilizzare alcuna libreria esterna. È possibile utilizzare solo le librerie C, usando le prese? Sto pensando di creare un pacchetto http (usando una formattazione corretta) e inviarlo al server. È questo l'unico modo possibile o c'è un modo migliore?HTTP get Richiesta utilizzando C WITHOUT libCurl

+0

No. devono imparare API presa BSD, poi imballare manualmente insieme tutti i dati grezzi. –

risposta

22

Utilizzando socket BSD o, se si è un po 'limitati, dire di avere qualche RTOS, un semplice stack TCP, come lwIP, si può formare la richiesta GET/POST.

Esistono numerose implementazioni open source. Vedere "happyhttp" come esempio (http://scumways.com/happyhttp/happyhttp.html). Lo so, è C++, non C, ma l'unica cosa che è "C++ - dipendente" esiste una gestione stringa/array, quindi è facilmente trasferibile su C. puro

Attenzione, non ci sono "pacchetti" , dal momento che HTTP viene solitamente trasferito sulla connessione TCP, quindi tecnicamente c'è solo un flusso di simboli nel formato RFC. Poiché le richieste HTTP vengono solitamente eseguite in modalità connect-send-disconnect, si potrebbe effettivamente chiamare "pacchetto".

In sostanza, una volta che hai una presa aperta (sockfd) "tutto" quello che dovete fare è qualcosa di simile

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); 
    }   
} 
+0

Grazie! Questo ha fatto ciò che dovevo fare! – asudhak

+3

@asudhak - Funziona alla grande, fino a quando questo codice non deve essere eseguito in un ambiente di lavoro aziendale in cui l'unico accesso a Internet avviene tramite un server proxy. Il protocollo per il recupero dell'URL tramite un proxy HTTP è leggermente diverso rispetto al TCP diretto. – selbie

+0

@selbie: certo, le risposte HTTP con codice 300 (reindirizzamenti) e roba proxy sono esattamente ciò che rende difficile l'HTTP. Quindi la distribuzione di libCurl per escludere misc royp-related stuff può essere la via da seguire invece di una richiesta HTTP fatta a mano. –

3

“Senza librerie esterne” in senso stretto escluderebbe libc come pure, in modo che ci devi scrivere tutte le syscall te stesso. Dubito che tu lo intenda così severo, però. Se non si desidera collegarsi a un'altra libreria e non si desidera copiare il codice sorgente da un'altra libreria nell'applicazione, quindi trattare direttamente con il flusso TCP utilizzando l'API socket è l'approccio migliore.

Creare la richiesta HTTP e inviarlo su un TCP socket connection è facile, come sta leggendo la risposta. Sta analizzando la risposta che sarà davvero complicata, in particolare se si mira a supportare una parte ragionevolmente ampia dello standard. Cose come le pagine di errore, i reindirizzamenti, la negoziazione dei contenuti e così via possono rendere la nostra vita piuttosto difficile se stai parlando con server web arbitrari. Se, d'altra parte, il server è noto per essere ben educato e un semplice messaggio di errore va bene per qualsiasi risposta imprevista del server, allora è anche abbastanza semplice.

7

POSIX 7 minimale esempio eseguibile

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

Uso

compilazione:

gcc -o wget wget.c 

garantita http://example.com e allo standard output:

./wget example.com 

IP:

./wget 104.16.118.182 

Questo comando si blocca per la maggior parte dei server, fino timeout, e che ci si aspetta:

  • sia server o client deve chiudere la connessione
  • maggior parte dei server HTTP lasciare la connessione aperto fino a un timeout in attesa di ulteriori richieste, ad esJavaScript, CSS e immagini a seguito di una pagina HTML
  • potremmo analizzare la risposta, e si chiudono quando byte Content-Length vengono letti, ma non l'abbiamo fatto per semplicità

testato su Ubuntu 15.10.

Un esempio lato server in: Send and Receive a file in socket programming in Linux with C/C++ (GCC/G++)

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

+0

Il codice si blocca su 'read (socket_file_descriptor, buffer, BUFSIZ)'. – CroCo

+0

@CroCo vedere il commento di origine: "la seconda lettura si blocca per alcuni secondi. [...]". Sia il server che il client devono chiudere la connessione. Non stiamo chiudendo, quindi nemmeno il server. È probabile che questo ottimizzi più richieste HTTP eseguite in un'unica connessione, il che è un caso comune (ottenere HTML, ottenere CSS, ottenere immagini). I client generalmente devono analizzare l'output e controllare che la risposta sia chiusa e chiusa usando 'Content-Length:' nel caso di HTTP, ma non volevo analizzare HTTP in questo semplice esempio. –

Problemi correlati