Стандартная библиотека Go предоставляет два мощных пакета для работы с HTML: html и html/template. В отличие от более общего text/template, пакет html/template понимает структуру HTML и автоматически обеспечивает безопасность ваших веб-приложений.
Главное преимущество html/template - интеллектуальное экранирование, которое защищает от межсайтового скриптинга (XSS) и других атак.
Базовые принципы работы
Простейший пример шаблона
Давайте начнем с элементарного шаблона, который показывает основные концепции:
Здесь {{.Title}} и {{.Content}} - это места, куда будут подставляться реальные данные.<!DOCTYPE HTML>
<html>
<head>
<meta charset="utf-8">
<title>{{.Title}}</title>
</head>
<body>
<h1>{{.Title}}</h1>
<p>{{.Content}}</p>
</body>
</html>
Использование шаблона в программе
package main
import (
"html/template"
"net/http"
)
type Page struct {
Title, Content string
}
func displayPage(w http.ResponseWriter, r *http.Request) {
p := &Page{
Title: "Пример страницы",
Content: "Добро пожаловать на нашу страницу!",
}
t := template.Must(template.ParseFiles("templates/simple.html"))
t.Execute(w, p)
}
func main() {
http.HandleFunc("/", displayPage)
http.ListenAndServe(":8080", nil)
}Этот код создает веб-сервер, который при каждом запросе генерирует HTML-страницу, заполняя шаблон данными из структуры Page.
Автоматическая защита от XSS
Одна из самых сильных сторон html/template - автоматическое экранирование. Рассмотрим пример:
<a href="/user?id={{.Id}}">{{.Content}}</a>Если злоумышленник попытается вставить вредоносный код, система автоматически преобразует его в безопасную форму:
<a href="/user?id={{.Id | urlquery}}">{{.Content | html}}</a>
Например, если пользователь введет <script>alert('атака')</script>, это будет преобразовано в:

Браузер отобразит это как обычный текст, а не выполнит как код.
Расширение возможностей шаблонов
Добавление собственных функций
Часто встроенных функций недостаточно. Например, нам нужно форматировать даты:
package main
import (
"html/template"
"net/http"
"time"
)
// Шаблон хранится прямо в коде для простоты примера
var tpl = `<!DOCTYPE HTML>
<html>
<head>
<meta charset="utf-8">
<title>Пример с датой</title>
</head>
<body>
<p>Текущее время: {{.Date | dateFormat "02.01.2006 15:04"}}</p>
</body>
</html>`
// Регистрируем наши функции для шаблонов
var funcMap = template.FuncMap{
"dateFormat": dateFormat,
}
func dateFormat(layout string, d time.Time) string {
return d.Format(layout)
}
func serveTemplate(res http.ResponseWriter, req *http.Request) {
t := template.New("date")
t.Funcs(funcMap) // Добавляем функции в шаблон
t.Parse(tpl) // Разбираем шаблон
data := struct{ Date time.Time }{
Date: time.Now(), // Передаем текущее время
}
t.Execute(res, data) // Выполняем шаблон
}
func main() {
http.HandleFunc("/", serveTemplate)
http.ListenAndServe(":8080", nil)
}Ключевые моменты:
- Создаем карту функций template.FuncMap
- Регистрируем ее в шаблоне через t.Funcs()
- Используем функции в шаблоне через символ |
Оптимизация производительности
Кэширование шаблонов
Разбирать шаблоны при каждом запросе - неэффективно. Гораздо лучше сделать это один раз:
package main
import (
"html/template"
"net/http"
)
// Разбираем шаблон один раз при запуске программы
var t = template.Must(template.ParseFiles("templates/simple.html"))
type Page struct {
Title, Content string
}
func displayPage(w http.ResponseWriter, r *http.Request) {
p := &Page{
Title: "Быстрая страница",
Content: "Эта страница загружается быстро!",
}
// Просто выполняем уже разобранный шаблон
t.Execute(w, p)
}
func main() {
http.HandleFunc("/", displayPage)
http.ListenAndServe(":8080", nil)
}
Такой подход значительно ускоряет работу приложения, особенно при большой нагрузке.
Обработка ошибок
Безопасное выполнение шаблонов
Если при выполнении шаблона произойдет ошибка, пользователь может увидеть частично сгенерированную страницу. Чтобы этого избежать, используем буферизацию:
package main
import (
"bytes"
"fmt"
"html/template"
"net/http"
)
var t *template.Template
func init() {
t = template.Must(template.ParseFiles("./templates/simple.html"))
}
type Page struct {
Title, Content string
}
func displayPage(w http.ResponseWriter, r *http.Request) {
p := &Page{
Title: "Безопасная страница",
Content: "Даже при ошибках пользователь не увидит ничего странного",
}
var b bytes.Buffer // Создаем буфер для результата
// Сначала выполняем шаблон в буфер
err := t.Execute(&b, p)
if err != nil {
// Если ошибка - показываем сообщение и НЕ отправляем частичный результат
fmt.Fprint(w, "Извините, произошла ошибка при формировании страницы.")
return
}
// Если все хорошо - отправляем готовый результат
b.WriteTo(w)
}
func main() {
http.HandleFunc("/", displayPage)
http.ListenAndServe(":8080", nil)
}
Это гарантирует, что пользователь либо получит полноценную
страницу, либо сообщение об ошибке, но никогда не увидит "битый" HTML.
Сложные структуры шаблонов
Вложенные шаблоны
В реальных приложениях часто нужно повторно использовать общие части страниц (шапку, меню, подвал):
index.html (основной шаблон)
<!DOCTYPE HTML>
<html>
{{template "head.html" .}} <!-- Вставляем шапку -->
<body>
<h1>{{.Title}}</h1>
<p>{{.Content}}</p>
</body>
</html>head.html (шапка)
nested_templates.go<head>
<meta charset="utf-8">
<title>{{.Title}}</title>
<link rel="stylesheet" href="/styles.css">
</head>
package main
import (
"html/template"
"net/http"
)
var t *template.Template
func init() {
// Загружаем оба шаблона вместе
t = template.Must(template.ParseFiles("index.html", "head.html"))
}
type Page struct {
Title, Content string
}
func displayPage(w http.ResponseWriter, r *http.Request) {
p := &Page{
Title: "Главная страница",
Content: "Содержимое страницы",
}
// Указываем, какой именно шаблон выполнять
t.ExecuteTemplate(w, "index.html", p)
}
func main() {
http.HandleFunc("/", displayPage)
http.ListenAndServe(":8080", nil)
}
Наследование шаблонов
Более сложный, но мощный подход - наследование, где есть базовый шаблон и дочерние, которые его расширяют:
base.html (базовый шаблон)
user.html (шаблон пользователя){{define "base"}}<!DOCTYPE HTML>
<html>
<head>
<meta charset="utf-8">
<title>{{template "title" .}}</title>
{{ block "styles" . }}<style>
/* Стили по умолчанию */
h1 { color: #400080 }
</style>{{ end }}
</head>
<body>
<h1>{{template "title" .}}</h1>
{{template "content" .}}
{{block "scripts" .}}{{end}} <!-- Блок для скриптов -->
</body>
</html>{{end}}
{{define "title"}}Пользователь: {{.Username}}{{end}}inherit.go
{{define "content"}}
<div class="user-profile">
<h2>Информация о пользователе</h2>
<p>Логин: {{.Username}}</p>
<p>Имя: {{.Name}}</p>
</div>
{{end}}
package main
import (
"html/template"
"net/http"
)
var t map[string]*template.Template
func init() {
t = make(map[string]*template.Template)
// Загружаем каждый шаблон вместе с базовым
temp := template.Must(template.ParseFiles("base.html", "user.html"))
t["user.html"] = temp
temp = template.Must(template.ParseFiles("base.html", "page.html"))
t["page.html"] = temp
}
type User struct {
Username, Name string
}
func displayUser(w http.ResponseWriter, r *http.Request) {
u := &User{
Username: "ivanov",
Name: "Иван Иванов",
}
// Выполняем через базовый шаблон
t["user.html"].ExecuteTemplate(w, "base", u)
}
func main() {
http.HandleFunc("/user", displayUser)
http.ListenAndServe(":8080", nil)
}
Продвинутые техники
Генерация HTML фрагментов
Иногда нужно сгенерировать часть страницы отдельно и вставить ее в основной шаблон:
quote.html (шаблон цитаты)
object_templates.go<blockquote class="famous-quote">
"{{.Quote}}"
<cite>— {{.Person}}</cite>
</blockquote>
package main
import (
"bytes"
"html/template"
"net/http"
)
var t *template.Template
var qc template.HTML // Для хранения безопасного HTML
func init() {
t = template.Must(template.ParseFiles("index.html", "quote.html"))
}
type Page struct {
Title string
Content template.HTML // Важно: тип template.HTML
}
type Quote struct {
Quote, Person string
}
func main() {
// Создаем цитату
q := &Quote{
Quote: "Лучше один раз увидеть, чем сто раз услышать",
Person: "Народная мудрость",
}
// Генерируем HTML для цитаты
var b bytes.Buffer
t.ExecuteTemplate(&b, "quote.html", q)
// Сохраняем как безопасный HTML
qc = template.HTML(b.String())
http.HandleFunc("/", displayPage)
http.ListenAndServe(":8080", nil)
}
func displayPage(w http.ResponseWriter, r *http.Request) {
p := &Page{
Title: "Цитата дня",
Content: qc, // Вставляем готовый HTML
}
t.ExecuteTemplate(w, "index.html", p)
}
Важно: тип template.HTML указывает системе, что это уже безопасный HTML и не требует дополнительного экранирования.
Заключение
Система шаблонов Go предлагает:
- Безопасность: автоматическое экранирование защищает от XSS
- Производительность: кэширование и эффективное выполнение
- Гибкость: вложенные шаблоны, наследование, пользовательские функции
- Надежность: обработка ошибок без частичного вывода
Эти возможности делают html/template отличным выбором для создания безопасных и производительных веб-приложений на Go. Начните с простых шаблонов и постепенно осваивайте более сложные техники по мере развития вашего приложения.
Спасибо за ваше время и внимание! Ваша поддержка очень важна для меня! Если вам понравилась статья, пожалуйста, поставьте лайк этой статье на моем канале Дзен
Подпишитесь на мой Телеграм-канал, чтобы быть в курсе новых статей.
Удачи!