¿Cómo hacer una solicitud de obtención de HTTP en C sin libcurl?

11 minutos de lectura

avatar de usuario
asudhak

Quiero escribir un programa en C para generar una solicitud de obtención sin usar bibliotecas externas. ¿Es esto posible usando solo bibliotecas C, usando sockets? Estoy pensando en crear un paquete http (usando el formato adecuado) y enviarlo al servidor. ¿Es esta la única manera posible o hay una mejor manera?

  • No. Primero debe aprender la API de socket BSD, luego empaquetar manualmente todos los datos sin procesar.

    usuario529758

    26 de junio de 2012 a las 13:27

avatar de usuario
Viktor Latipov

Usando sockets BSD o, si está algo limitado, digamos que tiene algún RTOS, alguna pila TCP más simple, como lwIP, puede formar la solicitud GET/POST.

Hay una serie de implementaciones de código abierto. Vea el “happyhttp” como muestra ( http://scumways.com/felizhttp/felizhttp.html ). Lo sé, es C++, no C, pero lo único que es “dependiente de C++” es una gestión de cadenas/matrices, por lo que es fácil de trasladar a C puro.

Tenga 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. Dado que las solicitudes http generalmente se realizan de manera de conexión, envío y desconexión, en realidad se podría llamar a esto un “paquete”.

Básicamente, una vez que tiene un socket abierto (sockfd), “todo” lo que tiene que hacer es algo 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)
        {
            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);
    }          
}

  • Gracias ! ¡Esto hizo lo que necesitaba que hiciera!

    – asudhak

    26 de junio de 2012 a las 15:52

  • @asudhak: funciona muy bien, hasta que este código tiene que ejecutarse en un entorno de trabajo corporativo donde el único acceso a Internet es a través de un servidor proxy. El protocolo para obtener URL a través de un proxy HTTP es ligeramente diferente al de TCP directo.

    – selbie

    27 de junio de 2012 a las 7:03

  • @selbie: claro, las respuestas HTTP con el código 300 (redirecciones) y las cosas de proxy son exactamente las cosas que dificultan HTTP. Por lo tanto, personalizar libCurl para excluir cosas misceláneas relacionadas con criptografía puede ser el camino a seguir en lugar de una solicitud HTTP hecha a mano.

    – Viktor Latipov

    27 de junio de 2012 a las 7:14

  • @ViktorLatypov: sé que lo sabes. No estaba golpeando tu respuesta. Quería que el OP que hizo la pregunta original lo supiera.

    – selbie

    27 de junio de 2012 a las 7:19

  • Estoy de acuerdo en ambos lados de una buena respuesta. En primer lugar, no juegue con ruedas cuadradas cuando tenga cURL, que es una BUENA biblioteca altamente probada, adaptada a los principales idiomas. Pero, a veces, no tiene sentido usar una biblioteca de 1 Mb cuando puede lograr lo mismo en ~ 40 líneas de código. Gracias por asegurarse de que el código funcione. (Me refiero estrictamente a la prueba antes de la publicación). GRACIAS.

    – m3nda

    23 de marzo de 2015 a las 7:11

avatar de usuario
Ciro Santilli Путлер Капут 六四事

Ejemplo ejecutable mínimo de POSIX 7

vamos a buscar http://ejemplo.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);
}

GitHub ascendente.

Compilar:

gcc -ggdb3 -std=c99 -Wall -Wextra -o wget wget.c

Obtener http://ejemplo.com y salida a stdout:

./wget example.com

Vemos algo como:

debug: before first read
debug: after a read
HTTP/1.1 200 OK
Age: 540354
Cache-Control: max-age=604800
Content-Type: text/html; charset=UTF-8
Date: Tue, 02 Feb 2021 15:21:14 GMT
Etag: "3147526947+ident"
Expires: Tue, 09 Feb 2021 15:21:14 GMT
Last-Modified: Thu, 17 Oct 2019 07:18:26 GMT
Server: ECS (nyb/1D11)
Vary: Accept-Encoding
X-Cache: HIT
Content-Length: 1256

<!doctype html>
<html>
...
</html>

Después de imprimir la respuesta, este comando se bloquea para la mayoría de los servidores hasta que se agota el tiempo de espera, y eso es lo que se espera:

  • el servidor o el cliente deben cerrar la conexión
  • nosotros (cliente) no lo estamos haciendo
  • la mayoría de los servidores HTTP dejan la conexión abierta hasta que se agota el tiempo de espera de más solicitudes, por ejemplo, JavaScript, CSS e imágenes después de una página HTML
  • podríamos analizar la respuesta y cerrar cuando se leen los bytes de Content-Length, pero no lo hicimos por simplicidad. Qué encabezados de respuesta HTTP se requieren dice que si Content-Length
    no se envía, el servidor puede simplemente cerrarse para determinar la duración.

Sin embargo, podríamos hacer que el host se cierre agregando el encabezado estándar HTTP 1.1 Connection: close al servidor:

char request_template[] = "GET / HTTP/1.1\r\nHost: %s\r\nConnection: close\r\n\r\n";

La parte de conexión también funciona con la IP:

host example.com

da:

example.com has address 93.184.216.34
example.com has IPv6 address 2606:2800:220:1:248:1893:25c8:1946

y asi hacemos:

./wget 93.184.216.34

sin embargo, la respuesta es un error, porque no estamos configurando el Host: correctamente en nuestro programa, y ​​eso es requerido en HTTP 1.1.

Probado en Ubuntu 18.04.

Ejemplos de servidor

  • Ejemplo mínimo de POSIX C: enviar y recibir un archivo en programación de socket en Linux con C/C++ (GCC/G++)
  • Ejemplo mínimo de Android Java: ¿cómo crear una conexión Socket en Android?

  • El código cuelga en read(socket_file_descriptor, buffer, BUFSIZ).

    – Cocodrilo

    5 oct 2017 a las 22:21

  • @CroCo vea el comentario de la fuente: “la segunda lectura se cuelga durante unos segundos. […]”. Tanto el servidor como el cliente deben cerrar la conexión. No vamos a cerrar, por lo que tampoco lo hará el servidor. Es probable que esto optimice varias solicitudes HTTP realizadas en una conexión, que es un caso común (obtener HTML, obtener CSS, obtener imágenes) Los clientes generalmente tienen que analizar la salida y verificar que la respuesta haya terminado y cerrar usando Content-Length: en el caso de HTTP, pero no quería analizar HTTP en este ejemplo simple.

    – Ciro Santilli Путлер Капут 六四事

    6 de octubre de 2017 a las 0:41


  • Gracias @CiroSantilli新疆改造中心996ICU六四事件 – ¡es increíble! Solo agrega Connection: close al encabezado de su solicitud para cerrar automáticamente la conexión después de la primera solicitud; de lo contrario, de forma predeterminada, en http1.1, las conexiones se mantienen activas hasta que se cierra el servidor o el cliente.

    – vidadeguente

    21 sep 2019 a las 22:34

  • @lifeofguenter increíble! Yo no estaba al tanto de eso. Mencionado en la respuesta.

    – Ciro Santilli Путлер Капут 六四事

    22 de septiembre de 2019 a las 8:25

avatar de usuario
MvG

“Sin bibliotecas externas” estrictamente hablando también excluiría libc, por lo que tendría que escribir todas las llamadas al sistema usted mismo. Sin embargo, dudo que lo digas tan estricto. Si no desea vincular a otra biblioteca y no desea copiar el código fuente de otra biblioteca en su aplicación, su mejor enfoque es tratar directamente con el flujo de TCP utilizando la API de socket.

Creando el HTTP solicitud y enviarla a través de un Conexión de enchufe TCP es fácil, como lo es leer la respuesta. Es analizar la respuesta, lo que será realmente complicado, especialmente si su objetivo es admitir una parte razonablemente grande del estándar. Cosas como las páginas de error, los redireccionamientos, la negociación de contenido, etc., pueden hacernos la vida bastante difícil si estás hablando con servidores web arbitrarios. Si, por otro lado, se sabe que el servidor se está comportando bien y un simple mensaje de error está bien para cualquier respuesta inesperada del servidor, entonces eso también es razonablemente simple.

avatar de usuario
A5H1Q

Pruebe la programación de sockets, el siguiente código C++ emite una solicitud GET simple al host especificado e imprime el encabezado y el contenido de la respuesta

Probado en Windows 10

#include <windows.h>
#include <string>
#include <stdio.h>
#include <winsock2.h>

using std::string;

SOCKET conn;
WSADATA wsaData;
struct hostent *hp;
unsigned int addr;
struct sockaddr_in server;
long fileSize;
const int bufSize = 512;
char readBuffer[bufSize], sendBuffer[bufSize], tmpBuffer[bufSize];
char *memBuffer=NULL;
char *headerBuffer=NULL;
long totalBytesRead, thisReadSize, headerLen;
char *tmpResult=NULL, *result;

char* antenna(string host,string path);
SOCKET connectToServer(char *szServerName, WORD portNum);
int getHeaderLength(char *content);


int main(){  
  
    if(WSAStartup(0x101, &wsaData) != 0){printf("startup failure");}
    memBuffer = antenna("www.spreadsheets.google.com", "/feeds/list/{Published_Sheet_ID-1}/1/public/values?alt=json");
    printf("Response content:\n%s\n\n", memBuffer);

    memBuffer = antenna("www.spreadsheets.google.com", "/feeds/list/{Published_Sheet_ID-2}/1/public/values?alt=json");
    printf("Response content:\n%s", memBuffer);

    WSACleanup();
}


char *antenna(string host, string path){

    fileSize=0;
    totalBytesRead=0;
    memBuffer=NULL;
    headerBuffer=NULL;
    tmpResult=NULL,

    conn = connectToServer((char*)host.c_str(), 80);
    if(conn == 0){printf("No Internet connection");}

    sprintf(sendBuffer, "GET %s HTTP/1.0 \r\nHost: %s\r\nConnection: close\r\n\r\n", path.c_str(),host.c_str());
    send(conn, sendBuffer, strlen(sendBuffer), 0);
    printf("Request Format: \n%s",sendBuffer);
    while(1){

        memset(readBuffer, 0, bufSize);
        thisReadSize = recv (conn, readBuffer, bufSize, 0);

        if ( thisReadSize <= 0 ){break;}

        tmpResult = (char*)realloc(tmpResult, thisReadSize+totalBytesRead);

        memcpy(tmpResult+totalBytesRead, readBuffer, thisReadSize);
        totalBytesRead += thisReadSize;
    }

    headerLen = getHeaderLength(tmpResult);
    long contenLen = totalBytesRead-headerLen;
    result = new char[contenLen+1];
    memcpy(result, tmpResult+headerLen, contenLen);
    result[contenLen] = 0x0;
    char *myTmp;
    myTmp = new char[headerLen+1];
    strncpy(myTmp, tmpResult, headerLen);
    myTmp[headerLen] = 0;
    delete(tmpResult);
    headerBuffer = myTmp;

    printf("Response Header: \n%s",headerBuffer);
    fileSize = contenLen;
    closesocket(conn);

    if(fileSize != 0){

        delete(memBuffer);
        delete(headerBuffer);
    }
    return(result);
}

SOCKET connectToServer(char *szServerName, WORD portNum)
{
    conn = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
    if (conn == INVALID_SOCKET){return 0;}
    if(inet_addr(szServerName)==INADDR_NONE){hp=gethostbyname(szServerName);}
    else{
        addr=inet_addr(szServerName);
        hp=gethostbyaddr((char*)&addr,sizeof(addr),AF_INET);
    }

    if(hp==NULL){closesocket(conn);return 0;}

    server.sin_addr.s_addr=*((unsigned long*)hp->h_addr);
    server.sin_family=AF_INET;
    server.sin_port=htons(portNum);
    if(connect(conn,(struct sockaddr*)&server,sizeof(server)))
    {
        closesocket(conn);
        return 0;
    }
    return conn;
}

int getHeaderLength(char *content)
{
    const char *srchStr1 = "\r\n\r\n", *srchStr2 = "\n\r\n\r";
    char *findPos;
    int ofset = -1;

    findPos = strstr(content, srchStr1);
    if (findPos != NULL)
    {
        ofset = findPos - content;
        ofset += strlen(srchStr1);
    }

    else
    {
        findPos = strstr(content, srchStr2);
        if (findPos != NULL)
        {
            ofset = findPos - content;
            ofset += strlen(srchStr2);
        }
    }
    return ofset;
}


Para compilar (usando g++):

g++ -static test.cpp -o test.exe -lws2_32

-lws2_32 especifica el enlazador para enlazar con winsock dlls

¿Ha sido útil esta solución?

Esta web utiliza cookies propias y de terceros para su correcto funcionamiento y para fines analíticos y para mostrarte publicidad relacionada con sus preferencias en base a un perfil elaborado a partir de tus hábitos de navegación. Al hacer clic en el botón Aceptar, acepta el uso de estas tecnologías y el procesamiento de tus datos para estos propósitos. Configurar y más información
Privacidad