Extensão — Páginas Dinâmicas: HTML Gerado com Templates

Extensão — Páginas Dinâmicas: HTML Gerado com Templates

Montar HTML concatenando string quebra por dois motivos ao mesmo tempo: fica ilegível e abre XSS assim que um dado de usuário entra. O motor de template separa molde e dados, escapa a saída por padrão e devolve ao C++ apenas o que é regra de negócio, deixando a aparência no arquivo do molde.
Linguagem C++

14 min de leitura

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ê 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

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" (&lt;, &gt;, &amp;) antes de inserir os dados no molde. Assim, <script> vira o texto literal &lt;script&gt;, 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 /usuariosextensã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_listaextensã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.

Comentários

Mais em Linguagem C++

Adeus NULL, Bem-vindo à Dedução — nullptr e auto
Adeus NULL, Bem-vindo à Dedução — nullptr e auto

Dois recursos pequenos em aparência e presentes em quase toda linha de C++…

Um Método, Vários Comportamentos — Funções Virtuais e Polimorfismo
Um Método, Vários Comportamentos — Funções Virtuais e Polimorfismo

Uma função que recebe uma forma qualquer e manda desenhá-la, sem saber se é…

Fazendo Duas Coisas ao Mesmo Tempo — std::thread
Fazendo Duas Coisas ao Mesmo Tempo — std::thread

Criar uma thread é a parte fácil. O C++ obriga a decidir entre join e detach…