Templates HTML (html/template)
Nesta aula, você aprenderá a usar o pacote html/template do Go para gerar HTML dinâmico com segurança, cobrindo parsing, execução, variáveis, escape automático e composição de templates.
Bem-vindo à aula 59 do nosso curso de Go! Hoje vamos mergulhar no pacote html/template, uma das ferramentas mais importantes para criar páginas web dinâmicas em Go. Diferente de simplesmente concatenar strings, o html/template oferece um mecanismo robusto para gerar HTML com segurança, evitando ataques de injeção de código (XSS) por meio de escape automático.
Nesta aula, você aprenderá desde o básico de parsing e execução até técnicas avançadas de composição de templates. Vamos explorar como passar dados para os templates, como o escape automático protege seu site e como organizar templates reutilizáveis. Ao final, você terá uma base sólida para criar interfaces web dinâmicas e seguras em Go.
Parsing e execução
O primeiro passo para usar templates é fazer o parsing do conteúdo. O pacote html/template oferece funções como template.New, template.ParseFiles e template.ParseGlob para carregar templates de strings ou arquivos. O parsing analisa o texto do template e o converte em uma estrutura interna que pode ser executada.
Após o parsing, a execução é feita com o método Execute, que recebe um io.Writer (geralmente um http.ResponseWriter ou um buffer) e os dados que serão injetados no template. Exemplo básico:
package main
import (
"html/template"
"os"
)
func main() {
// Template simples
t := template.New("exemplo")
t, _ = t.Parse("Olá, {{.Nome}}!")
// Dados
dados := struct{ Nome string }{"Maria"}
// Executa e escreve na saída padrão
t.Execute(os.Stdout, dados)
}Nesse exemplo, criamos um template com a string "Olá, {{.Nome}}!" e executamos com um struct contendo o campo Nome. O resultado será "Olá, Maria!". Se você estiver criando um servidor web, o http.ResponseWriter é o writer comum.
É importante notar que html/template é um pacote separado de text/template, mas com a mesma API básica. A diferença crucial é que html/template aplica escape automático para contextos HTML, o que veremos a seguir.
Variáveis
No sistema de templates, você pode acessar os dados passados na execução através de . (ponto). O ponto representa o valor passado como data no Execute. Você pode acessar campos de structs, chaves de mapas e métodos.
Além disso, é possível criar variáveis locais dentro do template usando $. Por exemplo:
// Template
const tpl = `
{{$nome := .Nome}}
Olá, {{$nome}}!
`
t := template.Must(template.New("var").Parse(tpl))
dados := map[string]string{"Nome": "João"}
t.Execute(os.Stdout, dados)Nesse caso, definimos uma variável $nome que recebe o valor de .Nome e depois a usamos. Isso é útil para evitar repetição e para armazenar valores temporários.
Também podemos usar funções em templates. O pacote html/template já inclui algumas funções embutidas, como html, js, urlquery, e você pode adicionar funções personalizadas com Funcs. Por exemplo:
funcs := template.FuncMap{
"upper": strings.ToUpper,
}
t := template.New("func").Funcs(funcs)
t, _ = t.Parse("Olá, {{upper .Nome}}!")
t.Execute(os.Stdout, dados)Isso permite transformar dados antes de exibi-los, como colocar o nome em maiúsculas.
Escape automático
Uma das principais vantagens do html/template é o escape automático. Quando você usa {{.Campo}}, o pacote analisa o contexto em que a ação está inserida (HTML, atributo, JavaScript, CSS, URL) e aplica o escape adequado. Isso previne ataques de XSS (Cross-Site Scripting), pois caracteres especiais como <, >, &, aspas e outros são convertidos para entidades HTML seguras.
Por exemplo, se um usuário enviar o nome <script>alert('xss')</script>, ao exibi-lo com {{.Nome}}, o template irá gerar <script>alert('xss')</script>, que será exibido como texto, não executado.
O escape é contextual: se você colocar uma variável dentro de um atributo HTML, ele escapará aspas de forma apropriada. Se colocar dentro de um bloco JavaScript, ele escapará caracteres específicos de JS. Isso é feito automaticamente, sem que você precise chamar funções de escape manualmente.
Porém, em situações onde você precisa confiar em HTML gerado por terceiros (como markdown), você pode usar o tipo template.HTML para desabilitar o escape. Mas isso deve ser feito com extrema cautela, pois abre brechas de segurança.
// Cuidado: usar template.HTML pode ser perigoso
conteudo := "<b>Texto em negrito</b>"
t, _ := template.New("safe").Parse("{{.Conteudo}}")
dados := struct{ Conteudo template.HTML }{template.HTML(conteudo)}
t.Execute(os.Stdout, dados)Nesse caso, o conteúdo será interpretado como HTML. Use apenas quando tiver certeza de que o conteúdo é seguro.
Composição
Em aplicações reais, é comum ter vários templates que se repetem, como cabeçalho, rodapé, menus. Para evitar duplicação, o html/template permite compor templates de várias formas:
- Definição de blocos: você pode definir blocos com
{{define "nome"}}...{{end}}e depois incluí-los com{{template "nome" .}}. - Parsing de múltiplos arquivos: com
ParseFilesouParseGlob, você carrega vários templates em um únicoTemplatee pode referenciá-los pelo nome. - Herança de layout: usando
blockedefine, você pode criar layouts base e substituir partes específicas.
Exemplo de composição com ParseFiles:
// layout.html
{{define "layout"}}
<!DOCTYPE html>
<html>
<head><title>Meu Site</title></head>
<body>
{{template "conteudo" .}}
</body>
</html>
{{end}}
// conteudo.html
{{define "conteudo"}}
<h1>Olá, {{.Nome}}!</h1>
{{end}}
// main.go
t, err := template.ParseFiles("layout.html", "conteudo.html")
if err != nil { panic(err) }
t.ExecuteTemplate(os.Stdout, "layout", dados)Nesse exemplo, o template layout define a estrutura HTML e chama o bloco conteudo. O método ExecuteTemplate executa um template específico pelo nome.
Com essa abordagem, você pode criar sistemas de layout flexíveis e reutilizáveis, mantendo o código organizado.
Boas práticas
Ao trabalhar com templates em Go, algumas boas práticas ajudam a manter a segurança e a manutenibilidade:
- Sempre use
html/templateem vez detext/templatepara HTML, pois ele oferece escape automático. - Evite usar
template.HTMLa menos que seja absolutamente necessário e você tenha controle sobre o conteúdo. - Organize seus templates em arquivos separados e use
ParseFilesouParseGlobpara carregá-los. - Use
template.Mustpara garantir que o parsing não falhe silenciosamente. - Prefira passar dados estruturados (structs) em vez de mapas, para ter segurança de tipos.
- Teste seus templates com dados de exemplo, incluindo inputs maliciosos, para verificar o escape.
Referências
- Documentação oficial do pacote html/template
- Writing Web Applications - Golang.org
- Documentação do text/template (base do html/template)
- Context and html/template - Blog do Go
- Go Web Examples: Templates
- Go Wiki - Templates
Exercícios
Crie um programa que carregue um template de arquivo chamado
index.htmlcom o conteúdo<h1>Hello, {{.Name}}!</h1>e execute com um struct contendoNameigual a "Alice". Mostre o resultado.✓ Resposta:package main import ( "html/template" "os" ) type PageData struct { Name string } func main() { t, err := template.ParseFiles("index.html") if err != nil { panic(err) } data := PageData{Name: "Alice"} t.Execute(os.Stdout, data) }Escreva um template que use uma variável local para armazenar a idade e exiba "Idade: X anos". Execute com dados de exemplo.
✓ Resposta:package main import ( "html/template" "os" ) func main() { const tpl = ` {{$idade := .Idade}} Idade: {{$idade}} anos ` t := template.Must(template.New("var").Parse(tpl)) data := struct{ Idade int }{30} t.Execute(os.Stdout, data) }Demonstre o escape automático exibindo a string
<script>alert('xss')</script>em um template e mostre o HTML gerado. O que acontece?✓ Resposta: O template escapa os caracteres especiais, gerando<script>alert('xss')</script>. No navegador, isso é exibido como texto, não executado. Exemplo:package main import ( "html/template" "os" ) func main() { t := template.Must(template.New("escape").Parse("{{.}}")) t.Execute(os.Stdout, "<script>alert('xss')</script>") }Crie um layout principal com cabeçalho e rodapé fixos, e um bloco de conteúdo que pode ser substituído. Use
ParseFilescom dois arquivos:layout.htmlepagina.html.✓ Resposta:// layout.html {{define "layout"}} <!DOCTYPE html> <html> <head><title>Meu Site</title></head> <body> <header><h1>Cabeçalho</h1></header> {{template "conteudo" .}} <footer><p>Rodapé</p></footer> </body> </html> {{end}} // pagina.html {{define "conteudo"}} <h2>Página: {{.Titulo}}</h2> {{end}} // main.go package main import ( "html/template" "os" ) func main() { t := template.Must(template.ParseFiles("layout.html", "pagina.html")) data := struct{ Titulo string }{"Home"} t.ExecuteTemplate(os.Stdout, "layout", data) }Adicione uma função personalizada que converte uma string para maiúsculas e use-a em um template para exibir o nome do usuário.
✓ Resposta:package main import ( "html/template" "os" "strings" ) func main() { funcs := template.FuncMap{"upper": strings.ToUpper} t := template.New("func").Funcs(funcs) t, _ = t.Parse("Olá, {{upper .Nome}}!") data := struct{ Nome string }{"maria"} t.Execute(os.Stdout, data) }