В предыдущих статьях мы рассказывали про особенности Go и как написать на нем первое приложение, а также рассмотрели пример простейшего веб-сервера. В этой статье разделим файлы на модели, контроллеры и шаблоны.
В прошлой статье мы научились обрабатывать роут “/”
package main
import (
"fmt"
"net/http"
"log"
)
func sayhello(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "Привет!")
}
func main() {
http.HandleFunc("/", sayhello) // Устанавливаем роутер
err := http.ListenAndServe(":8080", nil) // устанавливаем порт веб-сервера
if err != nil {
log.Fatal("ListenAndServe: ", err)
}
}
Если хотите использовать https, то вместо ListenAndServe используйте ListenAndServeTLS
err := http.ListenAndServeTLS(":8080", "cert.pem", "key.pem", nil)
Современные сайты могут иметь большое количество роутов. Сложно читать файл, в котором перечислен набор обрабатываемых запросов и их реализация. Для решения этих проблем существует различные концепции и паттерны, которые делают ваши приложения более гибкими к изменениям, позволяют красиво организовать код и улучшают восприятие архитектуры.
Одной из самых популярных концепций является MVC, которая используется в большинстве фреймворков. Суть её в том, чтобы разделить сущность на отдельные компоненты, которые отвечают за представление и за состояние данных. Рекомендуем прочитать статью.
Итак, что мы хотим получить? В рамках этой статьи мы «разнесем» код по отдельным файлам и реализуем простейший MVC.
Создаем контроллер
Контроллер — это часть проекта, которая отвечает за управление шаблонами и данными. Пользовательские запросы, в зависимости от роута, попадают в контроллер, который берет на себя задачу собрать необходимые данные из моделей, а затем передает их в шаблон. Обработкой данных контроллер не занимается, а лишь связывает несколько моделей.
Создадим папку с названием «controllers» рядом с main.go, а внутри этой папки создадим файл index.go. Структура будет выглядеть так:
src
|-- controllers
| +-- index.go
+-- main.go
Можете называть файл как угодно, мы назвали так, чтобы показать, что это точка входа в контроллер.
package controllers
В этом файле у нас будет реализация методов, которые обрабатывают все пользовательские запросы. Перенесем обработчик sayHello
package controllers
// Sayhello начинается с заглавной для того, чтобы метод был доступен вне этого файла
func Sayhello(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "Привет!")
}
Теперь мы можем использовать публичные методы. Подключим пакет в файле main.go, которая является точкой входа.
import (
"fmt"
"net/http"
"log"
"myFirstGoApp/controllers"
)
А в обработчике укажем метод контроллера
http.HandleFunc("/", controllers.Sayhello) // Обработчик из контроллера
Таким образом, можно распутать код, отделив реализацию обработчиков адресов от инициализации приложения. Попробуйте добавить еще один роут, например, “/sayBye” и передайте в ответ клиенту “Good Bye!”.
Представление View
Представление — это часть проекта, которая отвечает за отображение данных. Например, мы можем отправить клиенту в ответ HTML-страницу.
В Go существуют пакеты html/template и text/template для оформления ответа. Первый пакет умеет работать с HTML, чистить лишние атрибуты, закрывать теги и защищаться от XSS. Второй пакет работает намного проще:все переданные теги, атрибуты и спецсимволы будут экранированы и выведутся в виде обычного текста.
Подключим один из этих пакетов в файле controllers.go:
import "html/template"
Теперь, расширим функцию SayHello в контроллере.
func Sayhello(w http.ResponseWriter, r *http.Request) {
tmpl, err := template.New("template.html").ParseFiles("template.html")
text := "Привет!"
tplErr := tmpl.Execute(w, map[string]interface{}{
"text" : text,
})
}
И создадим файл template.html
{{ .text }}
Итак, мы подключили пакет для работы с HTML. В функцию template.New передается уникальное имя шаблона, а затем вызывается функция ParseFiles, которая получает данные из файла и хранит в памяти. Далее мы передаем в ответ «w» этот шаблон c данными. В результате исполнения этого кода вы увидите то же самое, только теперь у вас HTML-файл независим. Попробуйте поэкспериментировать и создать несколько обработчиков и шаблонов перед тем, как пойти дальше.
Модели
Модель предоставляет данные и методы для работы с ними, управляет состоянием объекта-сущности и при необходимости может обновить или удалить.
Создадим папку models и файл todo.go внутри этой папки
package models
Добавим несколько методов в togo.go
package models
type Todo struct {
Name string
Done bool
}
func Get() []Todo {
todos := []Todo{
{"Выучить Go", false},
{"Создать сайт", false},
{"Profit", false},
}
return todos
}
Подключим этот пакет в контроллере и передадим полученные от модели данные в шаблон
import "html/template"
func Sayhello(w http.ResponseWriter, r *http.Request) {
tmpl, err := template.New("template.html").ParseFiles("template.html")
title := "Я пишу сайт на Go!"
todos := models.Get()
tplErr := tmpl.Execute(w, map[string]interface{}{
"text" : text,
"todos" : todos,
})
}
Добавим следующий код в template.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8"/>
<title>{{ .title }}</title>
</head>
<body>
{{ range $index, $value := .todos }}
<div class="item{{ if eq .Done true }} done {{end}}">
<div class="title">{{ .Name }}</div>
</div>
{{ end }}
</body>
</html>
Готово. Все довольно просто.
Для закрепления можете попробовать создать несколько страниц с разными ответами.В следующей статье речь пойдет о базе данных. Мы создадим форму добавления данных, обработку GET и POST запросов, а также удаление и обновление.