2012-06-26 20 views
26

Quiero escribir un programa en C para generar una petición GET sin necesidad de utilizar ninguna biblioteca externa. ¿Es posible usar solo bibliotecas C, usando sockets? Estoy pensando en crear un paquete http (usando el formato adecuado) y enviarlo al servidor. ¿Es esta la única forma posible o hay una mejor manera?petición HTTP GET usando C SIN libCurl

+0

No. Usted tiene que aprender la API de sockets BSD primero, y luego empaquetar manualmente a todos los datos en bruto. –

Respuesta

22

El uso de sockets BSD o, si usted es un poco limitado, digamos que tiene un poco de RTOS, algunos pila TCP más simple, como lwIP, se puede formar la solicitud GET/POST.

Hay una serie de implementaciones de código abierto. Consulte "happyhttp" como muestra (http://scumways.com/happyhttp/happyhttp.html). Lo sé, es C++, no C, pero la única cosa que es "C++ - dependiente" hay una gestión de la cadena/matriz, por lo que es fácilmente portado a pura C.

Cuidado, no hay "paquetes" , ya que HTTP generalmente se transfiere a través de la conexión TCP, por lo que técnicamente solo hay una secuencia de símbolos en formato RFC. Como las solicitudes http generalmente se realizan en una forma de conexión-envío-desconexión, en realidad se podría llamar a esto un "paquete".

Básicamente, una vez que tenga una toma abierta (sockfd) "todo" lo que tiene que hacer es algo así como

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

Gracias! ¡Esto hizo Lo que necesitaba que HAGA! – asudhak

+3

@asudhak: funciona de maravilla, hasta que este código se ejecute en un entorno de trabajo corporativo en el que el único acceso a Internet sea a través de un servidor proxy. El protocolo para obtener URL a través de un proxy HTTP es ligeramente diferente que a través de TCP directo. – selbie

+0

@selbie - Claro, las respuestas HTTP con código 300 (redirecciones) y cosas de proxy son exactamente las cosas que hacen que HTTP sea difícil. Así que tayloring libCurl para excluir cosas relacionadas con cifrado misceláneo puede ser el camino a seguir en lugar de la solicitud HTTP hecha a mano. –

3

“Sin ninguna biblioteca externa” en sentido estricto excluiría libc, así, por lo que tendría tiene que escribir todos los syscalls usted mismo. Aunque dudo que lo diga así de estricto. Si no desea vincular a otra biblioteca, y no quiere copiar el código fuente de otra biblioteca en su aplicación, a continuación, tratar directamente con el flujo TCP mediante la API de socket es el mejor enfoque.

Crear la solicitud HTTP y enviarla a través de TCP socket connection es fácil, así como está leyendo la respuesta. Está analizando la respuesta que será realmente complicada, particularmente si intentas respaldar una porción razonablemente grande del estándar. Cosas como páginas de error, redirecciones, negociación de contenido, etc. pueden complicarnos la vida si hablamos con servidores web arbitrarios. Si por el contrario se conoce el servidor para ser buen comportamiento, y un mensaje de error simple es todo lo correcto para cualquier respuesta del servidor inesperada, entonces eso es bastante simple también.

7

POSIX 7 mínima ejemplo runnable

#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

de compilación:

gcc -o wget wget.c 

Obtener http://example.com y la salida a stdout:

./wget example.com 

IP:

./wget 104.16.118.182 

cuelga Este comando para la mayoría de los servidores hasta el tiempo de espera, y que se espera:

  • servidor o cliente debe cerrar la conexión
  • la mayoría de los servidores HTTP dejan la conexión abierto hasta un tiempo de espera esperando nuevas solicitudes, por ejemploJavaScript, CSS y las imágenes siguiendo una página HTML
  • podríamos analizar la respuesta, y se cierran cuando se leen los bytes de longitud del contenido, pero no lo hicimos por simplicidad

Probado en Ubuntu 15.10.

Un ejemplo en el lado del servidor: Send and Receive a file in socket programming in Linux with C/C++ (GCC/G++)

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

+0

El código se bloquea en 'leer (socket_file_descriptor, buffer, BUFSIZ)'. – CroCo

+0

@CroCo vea el comentario fuente: "la segunda lectura se cuelga por unos segundos. [...]". El servidor o el cliente debe cerrar la conexión. No estamos cerrando, así que tampoco lo está el servidor. Es probable que optimice múltiples solicitudes HTTP hechas en una conexión, que es un caso común (obtener HTML, obtener CSS, obtener imágenes). Los clientes generalmente tienen que analizar el resultado y verificar que la respuesta finaliza y se cierra utilizando 'Content-Length:' en el caso de HTTP, pero no quise analizar HTTP en este sencillo ejemplo. –