No artigo Extensão — O Terminal Vira Rede: Um Servidor HTTP Mínimo nosso servidor HTTP aprendeu a responder — mas só com texto simples. Um serviço web de verdade responde com páginas: HTML montado dinamicamente a partir de dados. Hoje damos esse passo, explorando como gerar HTML dinâmico em C++ com as ideias de template que mapeamos no Extensão. O problema é concreto e você já o conhece em outra forma: você tem um molde de página com "buracos" e dados para preencher esses buracos — exatamente o que o printf fazia com %d, mas agora para páginas inteiras. Veremos por que montar HTML "na mão" com concatenação de strings é frágil, como um motor de template resolve isso com elegância, e construiremos uma rota que serve uma página real com dados. O serviço que respondia texto vai, enfim, responder uma página que um navegador renderiza bonita.
O problema: HTML na marra é frágil
A tentação inicial é montar o HTML concatenando strings, como fizemos com o texto no artigo anterior. Funciona para casos triviais, mas degringola rápido:
// Montagem "na marra" — funciona, mas é FRÁGIL e ilegível.
std::string pagina_usuario(const std::string& nome, int idade) {
std::string html = "<html><body>";
html += "<h1>Perfil de " + nome + "</h1>";
html += "<p>Idade: " + std::to_string(idade) + "</p>";
html += "</body></html>";
return html;
// Problemas: o HTML fica ilegível, misturado ao código C++;
// qualquer mudança no layout exige mexer no código e recompilar;
// e — perigoso — se 'nome' contiver HTML malicioso, ele é injetado direto.
}
Três problemas sérios. Primeiro, ilegibilidade: a estrutura da página fica dissolvida em concatenações, impossível de visualizar. Segundo, acoplamento: mudar o layout (o trabalho de um designer) exige editar código C++ e recompilar. Terceiro, e mais grave, segurança: se nome vier de entrada do usuário e contiver tags HTML ou scripts, eles são injetados direto na página — a vulnerabilidade de cross-site scripting (XSS), prima da injeção de SQL do Extensão. Precisamos separar o molde (HTML) dos dados (C++). É o que um motor de template faz.
Motores de template: molde mais dados
Um motor de template deixa você escrever o HTML como um molde separado, com marcadores para os dados, e depois preenche os marcadores com valores. Usaremos o inja (mencionado no Extensão), inspirado no Jinja do Python e, como o cpp-httplib, de fácil integração. O molde é HTML limpo com marcadores {{ }}:
#include <inja/inja.hpp>
#include <string>
std::string pagina_usuario(const std::string& nome, int idade) {
// O molde: HTML legível, com marcadores {{ }} para os dados.
std::string molde = R"(
<html><body>
<h1>Perfil de {{ nome }}</h1>
<p>Idade: {{ idade }} anos</p>
</body></html>
)";
// Os dados, montados como um objeto (o inja usa nlohmann/json por baixo).
inja::json dados;
dados["nome"] = nome;
dados["idade"] = idade;
// O motor preenche os marcadores com os dados e devolve o HTML final.
return inja::render(molde, dados);
}
Agora o HTML é legível — você vê a estrutura da página —, e os dados são injetados nos marcadores {{ nome }} e {{ idade }} pela função inja::render. O molde poderia (e num projeto real, deveria) viver num arquivo .html separado, editável por um designer sem tocar no código C++, carregado com inja::Environment. A separação molde-dados resolve os três problemas: legibilidade (o HTML é HTML), desacoplamento (o molde é externo), e — com o escaping automático que bons motores fazem — segurança contra XSS (os dados são "escapados" para não virarem HTML executável).
Templates com lógica: listas e condições
O poder real aparece quando a página precisa de repetição ou condições — listar vários usuários, mostrar algo só se uma condição valer. Motores de template oferecem laços e condicionais no próprio molde:
#include <inja/inja.hpp>
std::string pagina_lista(const inja::json& usuarios) {
std::string molde = R"(
<html><body>
<h1>Usuários ({{ length(usuarios) }})</h1>
<ul>
## for usuario in usuarios
<li>{{ usuario.nome }} - {{ usuario.idade }} anos</li>
## endfor
</ul>
## if length(usuarios) == 0
<p>Nenhum usuário cadastrado.</p>
## endif
</body></html>
)";
inja::json dados;
dados["usuarios"] = usuarios;
return inja::render(molde, dados);
}
O ## for ... endfor repete um trecho para cada item da lista, e o ## if ... endif inclui um trecho condicionalmente. Repare como o molde permanece HTML legível com pequenas anotações de lógica — a estrutura da página é visível, e a lógica de apresentação (repetir, condicionar) vive no template, não afogada no código C++. Isso é a separação de responsabilidades (a filosofia do artigo A Sabedoria Codificada — Core Guidelines e Código Limpo em C++) aplicada à web: o C++ cuida dos dados e da lógica de negócio, o template cuida da apresentação. Cada um no seu lugar, e o resultado é mais sustentável.
Costurando: servindo páginas com dados do banco
Unindo as três extensões — banco (Extensão), servidor (Extensão) e templates (hoje) —, chegamos a uma aplicação web genuína: uma rota que busca dados no SQLite e os serve como uma página HTML renderizada. Este é o ápice da trilha-extensão:
#include "httplib.h"
#include "banco_sqlite.h"
#include <inja/inja.hpp>
int main() {
httplib::Server servidor;
servidor.Get("/usuarios", [](const httplib::Request& req, httplib::Response& res) {
try {
// 1. Busca os dados no banco (Extensão do Extensão).
BancoSQLite banco("usuarios.db"); // RAII (Fase 2)
Consulta c(banco, "SELECT nome, idade FROM usuarios");
inja::json usuarios = inja::json::array();
while (c.proxima_linha()) {
usuarios.push_back({ // monta os dados (Fase 4/6)
{"nome", c.coluna_texto(0)},
{"idade", c.coluna_int(1)}
});
}
// 2. Renderiza a página com os dados (Extensão de hoje).
std::string html = pagina_lista(usuarios);
// 3. Envia a página como resposta HTML (Extensão do Extensão).
res.set_content(html, "text/html");
}
catch (const std::exception& e) { // Fase 6: erro → 500
res.status = 500;
res.set_content("erro interno", "text/html");
}
});
servidor.listen("localhost", 8080);
return 0;
}
Acesse http://localhost:8080/usuarios no navegador e verá uma página HTML de verdade, com uma lista dos usuários guardados no banco, renderizada bonita pelo navegador. Contemple o fluxo completo: uma requisição HTTP chega (servidor, Extensão), busca dados persistidos no SQLite (banco, Extensão), monta uma página com um template (hoje), e responde HTML — tudo com RAII garantindo limpeza de recursos (Fase 2), exceções tratando falhas com resposta 500 (Fase 6), e containers organizando os dados (Fase 4). As três extensões se fundiram numa aplicação web completa em miniatura, e nela pulsa o curso inteiro. Do "Olá, C++!" do artigo O Mapa da Jornada: de C a C++ em 52 Passos a uma aplicação web que serve dados de um banco — a jornada se materializou.
A honestidade sobre desenvolvimento web em C++
Prometo a franqueza final da extensão. O que construímos é uma aplicação web funcional e conceitualmente completa, mas preciso reafirmar a perspectiva do Extensão: para a maioria dos projetos web, C++ não é a ferramenta mais produtiva, e ecossistemas como os de Python (Django, Flask), JavaScript (Node) ou Go têm frameworks web muito mais maduros, com autenticação, ORMs, sessões e mil recursos prontos. Você usaria C++ para web quando o desempenho for crítico — um serviço de altíssimo volume, um backend com computação pesada. omiti camadas essenciais para produção: gerenciamento de sessões e cookies, formulários e requisições POST, autenticação, arquivos estáticos (CSS, imagens), e a robustez de concorrência e segurança que o Extensão detalhou. Mostrei o núcleo conceitual — como C++ gera páginas dinâmicas com dados de um banco — porque é isso que conecta ao curso e prova que seus conceitos alcançam esse domínio. Construir uma aplicação web C++ de produção é uma especialização própria; o que você tem agora é o entendimento do princípio e a capacidade de começar. E isso não é pouco: é a ponte que você pediu, construída.
Montar HTML por concatenação quebra por dois motivos ao mesmo tempo: fica ilegível e abre XSS assim que um dado de usuário entra no meio. O motor de template separa molde e dados e, o que mais importa, escapa a saída por padrão — a proteção passa a ser o comportamento normal, não algo a lembrar. A divisão de responsabilidades que resulta disso é clara: mudança de aparência fica no molde, mudança de regra fica no C++.
Fontes e leituras recomendadas
- github.com/pantor/inja: a biblioteca de templates usada, com documentação de sintaxe (marcadores, laços, condições).
- github.com/nlohmann/json: a biblioteca JSON que o inja usa para os dados, onipresente no C++ moderno.
- "Cross-Site Scripting (XSS)" (owasp.org): a vulnerabilidade que o escaping de templates previne — essencial entender.
- developer.mozilla.org/en-US/docs/Web/HTML: a referência de HTML, para escrever bons moldes.
- Documentação do Jinja2 (jinja.palletsprojects.com): o inja se inspira nele; entender Jinja ilumina o inja.
Exercícios
Exercício 1
Escreva uma função que use o inja (ou, se não tiver a biblioteca, simule a ideia com substituição de strings) para gerar uma página com um título e um parágrafo, ambos vindos de variáveis. Explique a vantagem sobre concatenar strings.
Ver resposta
✓ Resposta: Geração com template (com inja, ou simulada):
// Com inja:
std::string pagina(const std::string& titulo, const std::string& corpo) {
std::string molde = "<html><body><h1>{{ titulo }}</h1><p>{{ corpo }}</p></body></html>";
inja::json dados;
dados["titulo"] = titulo;
dados["corpo"] = corpo;
return inja::render(molde, dados);
}
A vantagem sobre concatenar strings: o molde mostra a estrutura da página de forma legível (você vê o HTML como HTML, não dissolvido em +), separa a apresentação da lógica (o molde pode ser editado sem mexer na lógica de dados), e — com o escaping automático — protege contra injeção de conteúdo malicioso. Concatenar strings esconde a estrutura, mistura HTML com código, e é inseguro.
Exercício 2
Explique o problema de segurança (XSS) que surge ao inserir dados de usuário diretamente no HTML por concatenação. Como o escaping automático de um motor de template o previne?
Ver resposta
✓ Resposta: O problema de XSS (Cross-Site Scripting) surge quando dados de usuário são inseridos diretamente no HTML sem tratamento: se um usuário fornecer como "nome" algo como <script>codigo_malicioso()</script>, e você concatenar isso direto no HTML, o navegador de quem visualizar a página executará esse script — permitindo ao atacante roubar cookies, sessões, ou agir em nome da vítima. É a versão web da injeção de SQL: dados viram código executável. O escaping automático de um motor de template previne isso convertendo os caracteres especiais do HTML (como <, >, &) em suas versões "escapadas" (<, >, &) antes de inserir os dados no molde. Assim, <script> vira o texto literal <script>, que o navegador exibe como texto na página em vez de executar como código. Bons motores fazem esse escaping por padrão nos marcadores {{ }}, tratando todo dado como texto puro, nunca como HTML — tornando o XSS estruturalmente impossível, assim como prepared statements tornam a injeção de SQL impossível.
Exercício 3
Modifique o molde pagina_lista para exibir a idade em destaque (por exemplo, em negrito) apenas para usuários com 60 anos ou mais, usando um ## if dentro do laço. Descreva como a lógica de apresentação fica no template.
Ver resposta
✓ Resposta: Molde com destaque condicional:
## for usuario in usuarios
<li>{{ usuario.nome }} -
## if usuario.idade >= 60
<b>{{ usuario.idade }} anos</b>
## else
{{ usuario.idade }} anos
## endif
</li>
## endfor
A lógica de apresentação — "idosos têm a idade em negrito" — fica no template, onde pertence: é uma decisão de como exibir os dados, não de lógica de negócio. O código C++ continua apenas fornecendo os dados (nome e idade de cada usuário), indiferente a como serão apresentados. Se amanhã decidíssemos destacar em vermelho em vez de negrito, ou mudar o limite de 60 para 65, mexeríamos só no template, sem tocar no C++. Essa é a separação de responsabilidades: apresentação no template, dados e lógica no código.
Exercício 4
Explique a separação de responsabilidades entre o código C++ e o template HTML aqui. Que tipo de mudança na aplicação exigiria mexer só no template, e que tipo exigiria mexer no código C++?
Ver resposta
✓ Resposta: A separação: o código C++ é responsável pela lógica de negócio e pelos dados — buscar usuários no banco, calcular valores, decidir o que a aplicação faz. O template HTML é responsável pela apresentação — como os dados são exibidos, o layout, a formatação visual. Mudanças que exigiriam mexer só no template: alterar o layout da página, mudar cores/estilos, reorganizar a ordem dos elementos exibidos, ajustar textos estáticos, mudar como um dado é formatado visualmente (negrito, destaque) — tudo que é "aparência". Mudanças que exigiriam mexer no código C++: alterar quais dados são buscados (por exemplo, incluir o email dos usuários), mudar a lógica de negócio (filtrar usuários por um critério novo), corrigir como um valor é calculado, ou adicionar uma nova fonte de dados. Essa divisão permite que um designer trabalhe no template e um programador no C++ com mínima interferência mútua — cada um no seu domínio.
Exercício 5
Trace o fluxo completo da rota /usuarios que serve a página com dados do banco, identificando em cada etapa qual extensão (banco/servidor/template) e qual fase do curso principal está em ação. Explique por que este exemplo é a culminação da trilha-extensão.
Ver resposta
✓ Resposta: Fluxo da rota /usuarios, etapa por etapa:
- Requisição chega a /usuarios → extensão servidor (Extensão); a rota é registrada com uma lambda (Fase 5 do curso principal).
- Abre o banco com BancoSQLite banco("usuarios.db") → extensão banco (Extensão); RAII (Fase 2) garante que a conexão fechará sozinha.
- Consulta os dados com Consulta e itera as linhas → extensão banco (Extensão); a Consulta também é RAII (Fase 2), e os resultados são organizados numa estrutura (Fase 4).
- Monta os dados num inja::json array → Fase 4 (coleções) e Fase 6 (dados estruturados poliformes).
- Renderiza a página com pagina_lista → extensão template (hoje); separação apresentação/lógica (Fase 8, boas práticas).
- Envia a resposta com res.set_content(html, "text/html") → extensão servidor (Extensão).
- Trata erros no catch que devolve 500, com o RAII fechando a conexão durante o desenrolamento → Fase 6 (exceções) e Fase 2 (aliança RAII-exceção, artigo Quando o Contrato se Quebra — Exceções e a Aliança com o RAII).
Este exemplo é a culminação da trilha-extensão porque une as três extensões numa única operação coerente: persistência (banco), rede (servidor) e apresentação (template) colaboram para transformar uma requisição HTTP numa página HTML servida com dados reais de um banco — e, ao fazê-lo, mobiliza praticamente todas as fases do curso principal (RAII, STL, lambdas, exceções, boas práticas). É a prova concreta e final de que o C++ que você dominou, conceito a conceito ao longo de um ano, se integra para construir aplicações reais completas. A ponte que você pediu no início não só foi construída — foi atravessada.