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:
- Linha de Requisição: Contém método, URI e versão do protocolo. Exemplo:
GET /index.html HTTP/1.1 - Cabeçalhos: Pares chave-valor separados por dois pontos, terminados por
\r\n. Fornecem metadados sobre a requisição. - Linha em Branco: Delimitador obrigatório que separa cabeçalhos do corpo da mensagem.
- Corpo (Body): Dados da requisição, opcional. Se presente, o header
Content-Lengthindica seu tamanho.
Resposta (Response)
A resposta segue estrutura análoga:
- Linha de Status: Versão, código de status e descrição. Exemplo:
HTTP/1.1 200 OK - Cabeçalhos: Metadados da resposta, incluindo
Content-TypeeContent-Length. - Linha em Brenco: Separador entre cabeçalhos e corpo.
- 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.