No artigo Extensão — Dados que Sobrevivem: Integrando SQLite com RAII demos persistência ao nosso conhecimento, envolvendo o SQLite em RAII. Hoje exploramos o outro território do mapa que traçamos no Extensão: a web. Vamos construir um servidor HTTP mínimo em C++ — um programa que escuta requisições da rede e responde, transformando aquele std::cout que sempre falou com o terminal num serviço que fala com navegadores e outros programas pelo mundo. Usaremos a biblioteca cpp-httplib, escolhida por ser de cabeçalho único (basta incluí-la, sem lutar com o sistema de build) e simples, deixando o foco nos conceitos. Faremos uma pequena API que responde a requisições e, costurando as duas extensões, esboçaremos como servir dados vindos de um banco. Será a materialização concreta da ideia de que "um servidor web em C++ usa tudo do curso".
O servidor mais simples possível
O cpp-httplib reduz a criação de um servidor a poucas linhas. Você cria um objeto Server, registra rotas (associando um caminho de URL a uma função que produz a resposta), e manda escutar numa porta. Eis o "Olá, mundo" da web em C++:
// servidor.cpp — requer o cabeçalho único httplib.h
#include "httplib.h"
#include <iostream>
int main() {
httplib::Server servidor; // o objeto servidor
// Registra uma ROTA: quando alguém acessar GET /, executa esta função.
// A lambda (Fase 5!) recebe a requisição e escreve na resposta.
servidor.Get("/", [](const httplib::Request& req, httplib::Response& res) {
res.set_content("Olá do C++!", "text/plain"); // corpo e tipo da resposta
});
std::cout << "servidor rodando em http://localhost:8080\n";
servidor.listen("localhost", 8080); // escuta na porta 8080 (bloqueia aqui)
return 0;
}
Compile (linkando a biblioteca de threads e sockets do sistema) e rode; depois abra http://localhost:8080 no navegador ou use curl http://localhost:8080. Você verá "Olá do C++!". O que aconteceu: seu programa virou um servidor — ele escuta na porta 8080, e quando uma requisição chega para o caminho /, a lambda que você registrou produz a resposta. Aquele res.set_content(...) é o parente web do std::cout: em vez de escrever no terminal, escreve na resposta HTTP que volta ao cliente. O terminal virou rede. E note a lambda (Fase 5) no coração da rota — os conceitos do curso já estão aqui.
Rotas com parâmetros e uma pequena API
Um servidor útil tem várias rotas e responde a dados na URL. Vamos fazer uma pequena API que cumprimenta por nome e faz uma soma — usando parâmetros de rota e devolvendo dados estruturados:
#include "httplib.h"
#include <string>
int main() {
httplib::Server servidor;
// Rota com PARÂMETRO na URL: /ola/Ana → captura "Ana".
servidor.Get(R"(/ola/(\w+))", [](const httplib::Request& req, httplib::Response& res) {
std::string nome = req.matches[1]; // o grupo capturado da URL
res.set_content("Olá, " + nome + "!", "text/plain");
});
// Rota que lê parâmetros de query: /soma?a=3&b=5 → 8.
servidor.Get("/soma", [](const httplib::Request& req, httplib::Response& res) {
// has_param/get_param_value acessam os parâmetros ?a=..&b=..
if (req.has_param("a") && req.has_param("b")) {
int a = std::stoi(req.get_param_value("a"));
int b = std::stoi(req.get_param_value("b"));
res.set_content(std::to_string(a + b), "text/plain");
} else {
res.status = 400; // Bad Request: faltam parâmetros
res.set_content("faltam parâmetros a e b", "text/plain");
}
});
servidor.listen("localhost", 8080);
return 0;
}
Acesse http://localhost:8080/ola/Maria e receberá "Olá, Maria!"; acesse /soma?a=3&b=5 e receberá "8"; acesse /soma sem parâmetros e receberá um erro 400. Note o tratamento de erro (Fase 6) definindo o status HTTP apropriado — o 400 que comunica "requisição malformada". Cada rota é uma lambda que recebe a requisição e monta a resposta; organizar isso em métodos de uma classe (Fase 3) seria o passo natural num servidor maior. Já dá para ver a arquitetura de uma API real emergindo.
Costurando tudo: servindo dados de um banco
Agora unimos as duas extensões, no espírito de "cada artigo reúne o que já foi ensinado". Imagine servir, por HTTP, os usuários guardados no SQLite do artigo Extensão — Dados que Sobrevivem: Integrando SQLite com RAII. Conceitualmente (esboço, unindo BancoSQLite, Consulta e o servidor):
#include "httplib.h"
#include "banco_sqlite.h" // do Artigo Extensão
int main() {
httplib::Server servidor;
// Rota que consulta o banco e devolve os usuários como texto.
servidor.Get("/usuarios", [](const httplib::Request& req, httplib::Response& res) {
try {
BancoSQLite banco("usuarios.db"); // RAII: abre e fechará sozinho (Fase 2)
Consulta c(banco, "SELECT nome, idade FROM usuarios"); // RAII (Fase 2)
std::string corpo;
while (c.proxima_linha()) { // itera resultados (Artigo Extensão)
corpo += c.coluna_texto(0) + ": "
+ std::to_string(c.coluna_int(1)) + " anos\n";
}
res.set_content(corpo, "text/plain");
}
catch (const std::exception& e) { // Fase 6: erro vira resposta 500
res.status = 500;
res.set_content(std::string("erro no banco: ") + e.what(), "text/plain");
}
});
servidor.listen("localhost", 8080);
return 0;
}
Contemple a costura: uma requisição HTTP chega, a rota abre o banco SQLite (RAII — Fase 2), consulta os dados (a Consulta do artigo anterior), monta a resposta, e — se algo falhar — o catch transforma a exceção (Fase 6) numa resposta HTTP 500 apropriada, enquanto o RAII garante que a conexão feche mesmo no erro (a aliança RAII-exceção do artigo Quando o Contrato se Quebra — Exceções e a Aliança com o RAII!). Isto é um serviço web de verdade em miniatura: recebe requisições, busca dados persistidos, responde, e trata falhas com elegância. As duas extensões se uniram, e nelas está o curso inteiro.
A honestidade sobre servidores de produção
Prometo a franqueza de sempre, e ela é importante para você não sair daqui com uma impressão falsa. O que construímos é um servidor didático, e um servidor de produção real tem camadas que deliberadamente omiti. Concorrência: o cpp-httplib atende requisições em threads, o que significa que, se suas rotas compartilharem dados mutáveis (um cache, um contador), você precisa de toda a sincronização da Fase 7 — abrir uma nova conexão de banco por requisição, como fiz, evita alguns problemas, mas um servidor sério usaria um pool de conexões gerenciado com cuidado. Segurança: servidores reais precisam de HTTPS (criptografia), validação rigorosa de entrada, proteção contra os prepared statements do artigo anterior, autenticação, e muito mais — o que mostrei não tem nenhuma dessas proteções e jamais deveria ser exposto à internet como está. Robustez e escala: produção envolve balanceamento de carga, limites de taxa, logging, monitoramento. Formato de dados: uma API real devolveria JSON (com uma biblioteca como nlohmann/json), não texto simples. Mostrei o núcleo conceitual — como C++ responde a HTTP e serve dados — porque é isso que conecta ao curso; o resto é um universo próprio. A lição honesta: você agora entende o princípio de um servidor web em C++ e viu seus conceitos se aplicarem, mas construir um servidor de produção seguro e escalável é uma especialização que vai muito além desta introdução.
Servir HTTP a partir de um programa C++ é menos trabalhoso do que parece com uma biblioteca de cabeçalho único, e o roteamento por lambda torna cada rota curta. O que separa esse servidor de um de produção, porém, é longo: concorrência de verdade, limites de tamanho e tempo, tratamento de entrada maliciosa, TLS, logs. Exposto à internet como está, o problema não é desempenho — é segurança.
Fontes e leituras recomendadas
- github.com/yhirose/cpp-httplib: a biblioteca usada, com documentação completa de rotas, parâmetros, e configuração no README.
- developer.mozilla.org/en-US/docs/Web/HTTP: a referência sobre o protocolo HTTP — métodos (GET, POST), status codes (200, 400, 500) — essencial para servidores.
- github.com/nlohmann/json: a biblioteca de JSON para C++, que uma API real usaria para formatar respostas.
- github.com/CrowCpp/Crow e drogon.org: frameworks web C++ mais completos, para quando você for além do mínimo.
- "OWASP Top Ten" (owasp.org): as principais vulnerabilidades de segurança web, essenciais antes de expor qualquer servidor.
Exercícios
Exercício 1
Compile e rode o servidor "Olá do C++!" desta aula (você precisará do httplib.h e de linkar threads: -lpthread). Acesse-o pelo navegador ou com curl e descreva o que acontece.
Ver resposta
✓ Resposta: Ao compilar (algo como g++ -std=c++20 servidor.cpp -lpthread -o servidor) e rodar, o programa imprime a mensagem de que está rodando e então bloqueia no listen, aguardando requisições — o terminal não retorna ao prompt, pois o servidor está ativo. Acessando http://localhost:8080 no navegador (ou curl http://localhost:8080), a requisição chega ao servidor, a lambda registrada para / executa, e o cliente recebe "Olá do C++!" como resposta (o navegador exibe o texto; o curl o imprime). Cada acesso dispara a lambda novamente. Para encerrar o servidor, interrompe-se o programa (Ctrl+C). O ponto-chave observado: o programa deixou de ser um utilitário que roda e termina, e virou um serviço que fica ativo respondendo a requisições.
Exercício 2
Adicione ao servidor uma rota GET /hora que responda com a data e hora atuais. (Dica: use <chrono> e <ctime> para obter a hora.) Explique como a lambda da rota produz a resposta.
Ver resposta
✓ Resposta: Rota /hora:
#include <chrono>
#include <ctime>
servidor.Get("/hora", [](const httplib::Request& req, httplib::Response& res) {
auto agora = std::chrono::system_clock::now();
std::time_t t = std::chrono::system_clock::to_time_t(agora);
std::string texto = std::ctime(&t); // representação textual da hora
res.set_content("Agora: " + texto, "text/plain");
});
A lambda da rota produz a resposta obtendo o instante atual do relógio do sistema (system_clock::now()), convertendo-o para o tipo time_t e depois para texto legível com std::ctime, e escrevendo esse texto no corpo da resposta com res.set_content. Quando uma requisição chega a /hora, a lambda é executada naquele momento, então cada acesso reflete a hora atual — o res é o canal pelo qual a função entrega seu resultado ao cliente, análogo a como uma função comum usaria return, mas aqui "retornando" pela resposta HTTP.
Exercício 3
Na rota /soma, explique o papel do res.status = 400 quando faltam parâmetros. Por que devolver um status HTTP apropriado é importante, e qual seria o status para uma requisição bem-sucedida?
Ver resposta
✓ Resposta: O res.status = 400 define o código de status HTTP da resposta como 400 (Bad Request), que comunica ao cliente que a requisição estava malformada — no caso, faltaram os parâmetros obrigatórios a e b. Devolver o status apropriado é importante porque o código de status é a forma padronizada como o HTTP comunica o resultado de uma requisição: clientes (navegadores, outros programas, bibliotecas) dependem dele para saber se a requisição deu certo e, se não, por quê — um programa que consome a API pode reagir programaticamente a um 400 (corrigir e reenviar) de forma diferente de um 500 (erro do servidor, talvez tentar de novo mais tarde). Devolver 200 (sucesso) com uma mensagem de erro no corpo enganaria o cliente, que pensaria que tudo deu certo. Para uma requisição bem-sucedida, o status seria 200 (OK), que o cpp-httplib define por padrão quando você não o altera — por isso as rotas de sucesso não precisam defini-lo explicitamente.
Exercício 4
No exemplo que serve dados do banco, explique como a aliança RAII-exceção (artigo Quando o Contrato se Quebra — Exceções e a Aliança com o RAII) garante que a conexão SQLite seja fechada mesmo quando uma consulta falha e o catch é acionado. Trace o caminho da conexão desde a abertura até o fechamento no caso de erro.
Ver resposta
✓ Resposta: No caso de erro, o caminho da conexão é: a lambda entra no bloco try e cria o objeto BancoSQLite banco("usuarios.db") — o construtor abre a conexão SQLite (aquisição do recurso, RAII). Em seguida, cria a Consulta e começa a iterar. Se a consulta falhar e lançar uma exceção (por exemplo, a tabela não existe), o fluxo normal é interrompido e inicia-se o desenrolamento da pilha (artigo Quando o Contrato se Quebra — Exceções e a Aliança com o RAII): ao sair do escopo do try por causa da exceção, os objetos locais criados nele são destruídos na ordem inversa — primeiro a Consulta (cujo destrutor finaliza o statement), depois o banco (cujo destrutor chama sqlite3_close, fechando a conexão). Só então o controle chega ao catch, que monta a resposta 500. Ou seja: quando o catch executa, a conexão já foi fechada pelo destrutor de banco, acionado automaticamente durante o desenrolamento. É a aliança RAII-exceção em ação: a limpeza não depende de um código explícito no caminho de erro (não há close no catch), mas do destrutor que roda sozinho ao desenrolar a pilha — garantindo que nenhuma conexão vaze, mesmo quando algo dá errado no meio da consulta.
Exercício 5
A aula lista várias camadas que um servidor de produção tem e o nosso não. Escolha duas (por exemplo, concorrência e segurança) e explique concretamente que problema surgiria se você expusesse nosso servidor didático à internet sem elas, e como cada uma seria abordada.
Ver resposta
✓ Resposta: Duas camadas ausentes e seus problemas: Concorrência — o cpp-httplib atende requisições em múltiplas threads, então se várias requisições chegarem simultaneamente e nossas rotas compartilhassem um dado mutável (digamos, um contador de visitas global sem proteção), teríamos uma corrida de dados (Fase 7): o contador seria corrompido, com incrementos perdidos, exatamente como no exemplo do artigo Fazendo Duas Coisas ao Mesmo Tempo — std::thread. Pior, se compartilhássemos uma única conexão de banco entre threads sem sincronização, poderíamos corromper seu estado. Abordagem: proteger qualquer dado compartilhado com std::mutex/lock_guard (Fase 7), e gerenciar conexões de banco com cuidado — um pool de conexões sincronizado, ou (como fizemos) uma conexão por requisição. Segurança — nosso servidor não valida entrada nem usa prepared statements ao construir consultas; se uma rota montasse SQL concatenando um parâmetro da URL (por exemplo, /usuario/{nome} inserindo nome direto no SQL), um atacante poderia fazer injeção de SQL (artigo Extensão — Dados que Sobrevivem: Integrando SQLite com RAII), lendo ou apagando dados; , sem HTTPS, todo o tráfego (incluindo dados sensíveis) trafegaria em texto claro, interceptável. Abordagem: usar prepared statements com binding para toda entrada externa, validar rigorosamente os parâmetros, adicionar HTTPS (criptografia), e nunca confiar em dados vindos do cliente. Expor nosso servidor didático à internet sem essas camadas seria um convite a corrupção de dados (por corridas) e a invasões (por injeção e interceptação) — por isso ele é para aprendizado local, não para produção.