Cliente HTTP em C

Aplique programação com sockets para conversar com um servidor web real e buscar HTML.

Pré-requisito: Cliente & servidor TCP simples para os fundamentos de socket/connect/send/recv.

O que é Comunicação via Socket?

Um socket é o mecanismo que dois programas usam para trocar dados por uma rede. Navegadores, clientes de e-mail, aplicativos de chat — todo programa com rede usa sockets por baixo dos panos.
Cliente
Seu programa

TCP/IP
Internet
Servidor
ex.: servidor web

TCP vs UDP

TCP (confiável)
Garante entrega e ordenação.
Usado por: web, e-mail, transferência de arquivos.
Vamos usar este.
UDP (rápido, leve)
Sem garantias de entrega ou ordenação.
Usado por: streaming de vídeo, jogos online, DNS.

Headers necessários

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>        // close()
#include <arpa/inet.h>     // inet_addr(), htons()
#include <netdb.h>         // gethostbyname()
#include <sys/socket.h>    // socket(), connect(), send(), recv()
Nota: estes headers são para Linux e macOS. No Windows, use winsock2.h.

Fluxo da API de Sockets (Lado Cliente)

Um cliente TCP conecta, troca dados e desconecta — tudo em cinco passos.
1. socket() — cria um socket
2. connect() — conecta ao servidor
3. send() — envia dados
4. recv() — recebe dados
5. close() — fecha o socket
Analogia do telefone: socket = tirar o fone do gancho, connect = discar, send/recv = falar e ouvir, close = desligar.

Construa um Cliente TCP

Agora vamos de fato conectar a um servidor web, enviar uma requisição HTTP e ler a resposta.
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <netdb.h>
#include <sys/socket.h>

#define BUF_SIZE 4096

int main(void) {
    const char *host = "example.com";
    int port = 80;

    // ===== 1. Resolve hostname para IP =====
    struct hostent *server = gethostbyname(host);
    if (server == NULL) {
        fprintf(stderr, "Falha na resolução do host\n");
        return 1;
    }

    // ===== 2. Cria um socket =====
    int sockfd = socket(AF_INET, SOCK_STREAM, 0);
    if (sockfd < 0) {
        perror("socket");
        return 1;
    }

    // ===== 3. Configura o endereço do servidor =====
    struct sockaddr_in addr;
    memset(&addr, 0, sizeof(addr));
    addr.sin_family = AF_INET;
    addr.sin_port = htons(port);  // porta em ordem de byte de rede
    memcpy(&addr.sin_addr.s_addr, server->h_addr, server->h_length);

    // ===== 4. Conecta ao servidor =====
    if (connect(sockfd, (struct sockaddr*)&addr, sizeof(addr)) < 0) {
        perror("connect");
        close(sockfd);
        return 1;
    }
    printf("Conectado: %s:%d\n\n", host, port);

    // ===== 5. Envia requisição HTTP =====
    char request[BUF_SIZE];
    snprintf(request, BUF_SIZE,
        "GET / HTTP/1.1\r\nHost: %s\r\nConnection: close\r\n\r\n", host);
    send(sockfd, request, strlen(request), 0);

    // ===== 6. Recebe e imprime a resposta =====
    char buf[BUF_SIZE];
    int n;
    while ((n = recv(sockfd, buf, BUF_SIZE - 1, 0)) > 0) {
        buf[n] = '\0';
        printf("%s", buf);
    }

    // ===== 7. Desconecta =====
    close(sockfd);
    printf("\n\nConexão fechada\n");
    return 0;
}

Compilar e Executar

$ gcc http_client.c -o http_client $ ./http_client Conectado: example.com:80 HTTP/1.1 200 OK Content-Type: text/html; charset=UTF-8 ... <html>...</html> Conexão fechada

Detalhes das Funções

FunçãoPropósitoArgumentosRetorna
socket()Cria um socketAF_INET, SOCK_STREAM, 0fd do socket (int)
gethostbyname()Hostname → IP"example.com"ponteiro para hostent
connect()Conecta ao servidorsockfd, addr, addrlen0 = ok, -1 = falha
send()Envia dadossockfd, buf, len, flagsbytes enviados
recv()Recebe dadossockfd, buf, len, flagsbytes recebidos (0 = fechado)
close()Fecha o socketsockfd0 = ok
htons()Host-to-network short (portas)número da portaporta convertida

Struct sockaddr_in

struct sockaddr_in {
    short          sin_family;  // família do endereço (AF_INET)
    unsigned short sin_port;    // porta (use htons() para converter)
    struct in_addr sin_addr;    // endereço IP
    char           sin_zero[8]; // preenchimento
};
htons = Host TO Network Short. A ordem de byte da CPU (geralmente little-endian) difere da ordem de byte de rede (big-endian), então a conversão é necessária.

Desafios

Desafio 1: Extraia uma função connect
Refatore o setup de conexão acima para int connect_to_server(const char *host, int port). Retorne o fd do socket, ou -1 em caso de erro.
Desafio 2: Helpers genéricos de send/recv
Implemente send_line e recv_line que enviam e recebem uma linha por vez. send_line acrescenta \r\n; recv_line lê até \r\n.
Desafio 3: Passe o host pela linha de comando
Aceite host e porta via argv, para que você possa rodar ./http_client example.com 80. Veja a aula de argc/argv.
Desafio 4: Salve a resposta em arquivo
Adicione uma opção para salvar a resposta HTTP em um arquivo. Veja a aula de E/S de arquivos.
Próximo: Construa um cliente SMTP de e-mail →

Quiz de Revisão

Teste seu entendimento desta aula!

Q1. Qual header é obrigatório em uma requisição HTTP/1.1?

Host
User-Agent
Connection

HTTP/1.1 exige o header Host para que o servidor consiga distinguir múltiplos hosts virtuais compartilhando um IP.

Q2. Qual sequência de bytes marca o fim de linha no HTTP?

\r\n (CRLF)
só \n
\t

Protocolos baseados em texto como HTTP e SMTP tradicionalmente separam linhas com os dois bytes CR + LF.

Q3. O que significa o 200 no começo de uma resposta HTTP?

Um código de status indicando sucesso
O comprimento do conteúdo
O número de versão do HTTP

2xx significa sucesso, 3xx significa redirecionamento, 4xx é erro do cliente e 5xx é erro do servidor.

Compartilhe este artigo
Compartilhar no X