В предыдущих статьях мы рассказывали про особенности 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 запросов, а также удаление и обновление.
