Entendendo o Protocolo HTTP: Arquitetura, Métodos e Implementação

O HTTP (HyperText Transfer Protocol) é um dos protocolos mais importantes da camada de aplicação, sendo responsável pela comunicação entre clientes (tipicamente navegadores) e servidores web. Sua função principal é viabilizar a troca de documentos de hipertexto, como páginas HTML, imagens e outros recursos da web.

Características Principais

O protocolo HTTP foi projetado com duas características fundamentais que definem seu comportamento:

  • Sem conexão persistente por padrão: Cada requisição é tratada de forma independente. Na versão original, após o servidor responder, a conexão TCP era encerrada. O HTTP/1.1 introduziu conexões persistentes (keep-alive), permitindo múltiplas requisições na mesma conexão TCP.
  • Sem estado (Stateless): O servidor não mantém informações sobre requisições anteriores. Cada solicitação é processada sem conhecimento do histórico de interações do cliente. Esta característica simplifica a implementação do servidor, mas exige mecanismos auxiliares como Cookies e Sessions para manter contexto.

É importante distinguir: o HTTP opera na camada de aplicação definindo formato de mensagens, enquanto o TCP, na camada de transporte, garante entrega confiável. O HTTP utiliza o TCP como canal de transporte confiável, sando a natureza "sem conexão" do HTTP uma decisão de design do protocolo, não uma limitação técnica.

URI, URL e Codificação de Caracteres

URL (Uniform Resource Locator) é o endereço que identifica unicamente um recurso na web. A estrutura básica segue o padrão:

protocolo://dominio:porta/caminho/recurso

Exemplo prático: https://www.exemplo.com:443/docs/artigo.html

Quando caracteres especiais como /, ?, & precisam ser transmitidos como dados (não como delimitadores), aplica-se a codificação percentual (percent-encoding). O caractere é convertido para hexadecimal, prefixado com %. Por exemplo, o espaço em branco torna-se %20 e o sinal de mais %2B.

Estrutura das Mensagens HTTP

Requisição (Request)

Uma requisição HTTP possui a seguinte estrutura:

  1. Linha de Requisição: Contém método, URI e versão do protocolo. Exemplo: GET /index.html HTTP/1.1
  2. Cabeçalhos: Pares chave-valor separados por dois pontos, terminados por \r\n. Fornecem metadados sobre a requisição.
  3. Linha em Branco: Delimitador obrigatório que separa cabeçalhos do corpo da mensagem.
  4. Corpo (Body): Dados da requisição, opcional. Se presente, o header Content-Length indica seu tamanho.

Resposta (Response)

A resposta segue estrutura análoga:

  1. Linha de Status: Versão, código de status e descrição. Exemplo: HTTP/1.1 200 OK
  2. Cabeçalhos: Metadados da resposta, incluindo Content-Type e Content-Length.
  3. Linha em Brenco: Separador entre cabeçalhos e corpo.
  4. Corpo: Conteúdo solicitado (HTML, JSON, binário, etc.).

Métodos HTTP Principais

Método Finalidade Características
GET Obter recurso Parâmetros na URL, cacheável, idempotente
POST Enviar dados Parâmetros no corpo, pode modificar estado
PUT Substituir recurso Idempotente, usado em APIs REST
DELETE Remover recurso Idempotente
HEAD Obter apenas cabeçalhos Útil para verificar se recurso foi modificado
OPTIONS Descobrir métodos permitidos Usado em CORS

GET vs POST

O método GET transmite parâmetros concatenados à URL (/busca?q=termo&pagina=1), com limitação de tamanho e exposição dos dados. É adequado para consultas que não alteram estado do servidor.

O POST envia dados no corpo da requisição, suportando maior volume e mantendo os parâmetros ocultos da URL. É indicado para envio de formulários, uploads e operações que modificam dados.

<!-- Formulário com método GET -->
<form action="/pesquisar" method="GET">
    <input type="text" name="termo" placeholder="Buscar...">
    <button type="submit">Pesquisar</button>
</form>

<!-- Formulário com método POST -->
<form action="/autenticar" method="POST">
    <input type="text" name="usuario" required>
    <input type="password" name="senha" required>
    <button type="submit">Entrar</button>
</form>

Códigos de Status HTTP

Os códigos de status são classificados em cinco categorias:

  • 1xx (Informativo): Requisição recebida, processamento em andamento
  • 2xx (Sucesso): Requisição processada com êxito (200 OK, 201 Created)
  • 3xx (Redirecionamento): Ação adicional necessária (301 Moved Permanently, 302 Found)
  • 4xx (Erro do Cliente): Requisição inválida (400 Bad Request, 403 Forbidden, 404 Not Found)
  • 5xx (Erro do Servidor): Falha no processamento (500 Internal Server Error, 503 Service Unavailable)

O status 301 indica redirecionamento permanente, onde o cliente deve atualizar sua referência para a nova URL. O 302 sinaliza redirecionamento temporário, mantendo a URL original para requisições futuras.

Cabeçalhos HTTP Importantes

Cabeçalhos de Requisição

  • Host: Domínio e porta do recurso solicitado (obrigatório em HTTP/1.1)
  • User-Agent: Identificação do cliente (sistema operacional, navegador)
  • Accept: Tipos de conteúdo aceitos pelo cliente
  • Cookie: Dados de sessão enviados pelo cliente
  • Authorization: Credenciais de autenticação

Cabeçalhos de Resposta

  • Content-Type: Tipo MIME do conteúdo retornado
  • Content-Length: Tamanho em bytes do corpo
  • Set-Cookie: Define cookie no cliente
  • Location: URL de redirecionamento (usado com status 3xx)
  • Cache-Control: Diretivas de cache
  • Connection: Controle de conexão persistente

Gerenciamento de Estado: Cookies e Sessions

Como o HTTP é stateless, aplicações web necessitam de mecanismos para manter contexto do usuário:

Cookies são pequenos dados armazenados no navegador cliente. O servidor envia via header Set-Cookie, e o cliente reenvia automaticamente em requisições subsequentes. Podem conter dados de preferências ou identificadores de sessão.

Sessions armazenam dados do usuário no servidor. Um identificador único (Session ID) é gerado e enviado ao cliente via Cookie. O servidor consulta o armazenamento interno usando este ID para recuperar informações da sessão.

A combinação Cookie + Session oferece melhor segurança: dados sensíveis permanecem no servidor, apenas o ID transita pela rede. Atributos como HttpOnly e Secure aumentam a proteção contra ataques XSS e garantem transmissão apenas via HTTPS.

Implementação de Servidor HTTP

A seguir, uma implementação simplificada de servidor HTTP em C++:

Classe Utilitária

#pragma once
#include <fstream>
#include <string>

class FileUtils {
public:
    static bool read_content(const std::string& path, std::string& output) {
        long size = get_file_size(path);
        if (size <= 0) return false;
        
        std::ifstream file(path, std::ios::binary);
        if (!file.is_open()) return false;
        
        output.resize(size);
        file.read(&output[0], size);
        file.close();
        return true;
    }
    
    static bool extract_line(std::string& buffer, std::string& line, 
                            const std::string& delimiter = "\r\n") {
        size_t pos = buffer.find(delimiter);
        if (pos == std::string::npos) return false;
        
        line = buffer.substr(0, pos);
        buffer.erase(0, pos + delimiter.length());
        return true;
    }
    
    static long get_file_size(const std::string& path) {
        std::ifstream file(path, std::ios::binary | std::ios::ate);
        if (!file.is_open()) return -1;
        
        long size = file.tellg();
        file.close();
        return size;
    }
};

Estruturas de Requisição e Resposta

#pragma once
#include <string>
#include <unordered_map>
#include <sstream>

const std::string WEB_ROOT = "./public";
const std::string INDEX_PAGE = "index.html";
const std::string ERROR_404 = "404.html";

struct RequestInfo {
    std::string method;
    std::string uri;
    std::string version;
    std::string body;
    std::string query_params;
    bool has_params = false;
    
    std::unordered_map<std::string, std::string> headers;
    
    void parse_request_line(const std::string& line) {
        std::istringstream stream(line);
        stream >> method >> uri >> version;
    }
    
    bool deserialize(std::string& raw_request) {
        std::string first_line;
        if (!FileUtils::extract_line(raw_request, first_line)) return false;
        
        parse_request_line(first_line);
        
        // Resolver caminho do arquivo
        if (uri == "/") {
            uri = WEB_ROOT + "/" + INDEX_PAGE;
        } else {
            uri = WEB_ROOT + uri;
        }
        
        // Verificar se há parâmetros de query
        size_t query_pos = uri.find('?');
        if (query_pos != std::string::npos) {
            query_params = uri.substr(query_pos + 1);
            uri = uri.substr(0, query_pos);
            has_params = true;
        }
        
        return true;
    }
};

struct ResponseInfo {
    std::string version = "HTTP/1.1";
    int status_code = 200;
    std::string status_text = "OK";
    std::string content;
    std::string target_file;
    
    std::unordered_map<std::string, std::string> headers;
    
    void set_status(int code) {
        status_code = code;
        switch (code) {
            case 200: status_text = "OK"; break;
            case 301: status_text = "Moved Permanently"; break;
            case 302: status_text = "Found"; break;
            case 403: status_text = "Forbidden"; break;
            case 404: status_text = "Not Found"; break;
            default: status_text = "Unknown"; break;
        }
    }
    
    void add_header(const std::string& key, const std::string& value) {
        headers[key] = value;
    }
    
    std::string get_mime_type(const std::string& path) {
        size_t dot_pos = path.rfind('.');
        if (dot_pos == std::string::npos) return "text/plain";
        
        std::string ext = path.substr(dot_pos);
        if (ext == ".html" || ext == ".htm") return "text/html";
        if (ext == ".css") return "text/css";
        if (ext == ".js") return "application/javascript";
        if (ext == ".json") return "application/json";
        if (ext == ".jpg" || ext == ".jpeg") return "image/jpeg";
        if (ext == ".png") return "image/png";
        return "application/octet-stream";
    }
    
    std::string serialize() {
        std::ostringstream response;
        
        // Linha de status
        response << version << " " << status_code << " " << status_text << "\r\n";
        
        // Cabeçalhos
        for (const auto& header : headers) {
            response << header.first << ": " << header.second << "\r\n";
        }
        
        // Linha em branco + corpo
        response << "\r\n" << content;
        
        return response.str();
    }
    
    bool build_response() {
        if (!FileUtils::read_content(target_file, content)) {
            // Arquivo não encontrado
            set_status(404);
            target_file = WEB_ROOT + "/" + ERROR_404;
            FileUtils::read_content(target_file, content);
        } else {
            set_status(200);
        }
        
        long size = FileUtils::get_file_size(target_file);
        add_header("Content-Type", get_mime_type(target_file));
        add_header("Content-Length", std::to_string(size));
        
        return true;
    }
};

Servidor HTTP

#pragma once
#include <memory>
#include <functional>
#include "Socket.hpp"
#include "RequestInfo.hpp"
#include "ResponseInfo.hpp"

using RequestHandler = std::function<void(RequestInfo&, ResponseInfo&)>;

class HttpServer {
private:
    std::unique_ptr<TcpServer> server;
    std::unordered_map<std::string, RequestHandler> routes;
    
    void handle_request(std::shared_ptr<Socket>& client_sock) {
        std::string raw_request;
        int bytes = client_sock->receive(raw_request);
        
        if (bytes <= 0) return;
        
        RequestInfo request;
        ResponseInfo response;
        
        if (!request.deserialize(raw_request)) return;
        
        // Verificar se é rota dinâmica
        if (request.has_params) {
            auto handler = routes.find(request.uri);
            if (handler != routes.end()) {
                handler->second(request, response);
            }
        } else {
            response.target_file = request.uri;
            response.build_response();
        }
        
        std::string response_str = response.serialize();
        client_sock->send(response_str);
    }
    
public:
    HttpServer(uint16_t port) : server(std::make_unique<TcpServer>(port)) {}
    
    void register_route(const std::string& path, RequestHandler handler) {
        routes[WEB_ROOT + path] = handler;
    }
    
    void start() {
        server->start([this](std::shared_ptr<Socket>& sock, InetAddr& client) {
            handle_request(sock);
        });
    }
};

Programa Principal

#include "HttpServer.hpp"

void handle_login(RequestInfo& req, ResponseInfo& resp) {
    // Parsear parâmetros: usuario=xxx&senha=yyy
    std::string usuario = "admin";
    std::string senha = "123456";
    
    // Validação simples
    if (req.query_params.find("usuario=" + usuario) != std::string::npos &&
        req.query_params.find("senha=" + senha) != std::string::npos) {
        
        resp.set_status(200);
        resp.content = "Login realizado com sucesso";
        resp.add_header("Content-Type", "text/plain");
        resp.add_header("Set-Cookie", "session_id=abc123; Path=/; HttpOnly");
    } else {
        resp.set_status(401);
        resp.content = "Credenciais inválidas";
    }
    resp.add_header("Content-Length", std::to_string(resp.content.length()));
}

void handle_search(RequestInfo& req, ResponseInfo& resp) {
    resp.set_status(200);
    resp.content = "Resultados para: " + req.query_params;
    resp.add_header("Content-Type", "text/plain");
    resp.add_header("Content-Length", std::to_string(resp.content.length()));
}

int main(int argc, char* argv[]) {
    if (argc != 2) {
        std::cerr << "Uso: " << argv[0] << " <porta>" << std::endl;
        return 1;
    }
    
    uint16_t port = std::stoi(argv[1]);
    
    auto http_server = std::make_unique<HttpServer>(port);
    
    http_server->register_route("/login", handle_login);
    http_server->register_route("/search", handle_search);
    
    http_server->start();
    
    return 0;
}

Evolução das Versões HTTP

O protocolo passou por significativas evoluções:

HTTP/1.0 (1996): Introduziu métodos POST e HEAD, cabeçalhos, códigos de status e suporte a diferentes tipos de conteúdo via MIME. Cada requisição exigia nova conexão TCP.

HTTP/1.1 (1999): Padronizou conexões persistentes (keep-alive), introduziu chunked transfer encoding e o header Host para virtual hosting. Pipeline permitiu múltiplas requisições sem aguardar respostas anteriores.

HTTP/2 (2015): Multiplexação sobre uma única conexão TCP, compressão de cabeçalhos (HPACK), frames binários e server push. Eliminou o problema de head-of-line blocking ao nível HTTP.

HTTP/3 (2022): Substitui TCP por QUIC (sobre UDP), eliminando head-of-line blocking ao nível de transporte. Reduz latência de estabelecimento de conexão e melhora performance em redes com perda de pacotes.

Tags: HTTP TCP/IP Web Development Networking Status Codes

Publicado em 8-31 16:25