Введение
Сегодня мы покажем, как создавать веб-приложения и микросервисы в Go с помощью фреймворка Gin. Gin это фреймворк, позволяющий уменьшить объём кода, необходимого для построения таких приложений. Он поощряет создание многократно-используемого и расширяемого кода.
Мы рассмотрим создание проекта и сборку несложного приложения с Gin, которое будет выводить список топиков и отдельный топик.
Подготовка
Перед началом работы убедитесь, что у вас установлены Go и утилита curl. Если curl не установлена и вы не хотите работать с ней, используйте любую другую утилиту тестирования API.
Что такое Gin?
Gin это высокопроизводительный микрофреймворк, который используется для создания веб-приложений и микросервисов. С ним очень удобно делать комплексную конвейерную обработку запросов из модулей — многократно используемых кусочков кода. Вы пишете промежуточный слой приложения, который затем подключается в один или более обработчик запросов или в группу обработчиков.
Почему именно Gin?
Одно из лучших качеств Go — его встроенная библиотека net/http, позволяющая с лёгкостью создавать HTTP сервер. Однако, она не настолько гибкая, как бы хотелось, и количество кода, требуемое при работе с ней, довольно большое.
В Go нет встроенной поддержки обработчика роутов на базе регулярных выражений. Вам нужно писать код для получения этого функционала. Однако, с ростом количества ваших приложений, вы будете вынуждены копировать один и тот же код везде или всё-таки создадите библиотеку.
В этом и есть задача Gin. Он содержит набор часто употребляемых функций, таких как роутинг, поддержка middleware, обработка шаблонов. Вдобавок к этому, он позволяет уменьшить количество кода в приложениях и создание веб-приложений с ним намного проще.
Проектирование приложения
Посмотрим, как Gin обрабатывает запросы:
Request -> Route Parser -> [Optional Middleware] -> Route Handler -> [Optional Middleware] -> Response
Когда приходит запрос, Gin сначала проверяет, есть ли подходящий роут (маршрут). Если соответствующий роут найден, Gin запускает обработчик этого роута и промежуточные звенья в заданном порядке. Мы увидим как это происходит, когда перейдём к коду в следующем разделе.
Функционал приложения
Наше приложение — это простой менеджер топиков. Оно должно:
- позволять пользователям регистрироваться с логином и паролем (для неавторизованных пользователей),
- позволять пользователям авторизоваться (для неавторизованных пользователей),
- позволять пользователям завершать сеанс (для авторизованных пользователей),
- позволять пользователям создавать топики (для авторизованных пользователей),
- Выводить список всех топиков на главной странице (для всех пользователей), и
- Выводить топик на его собственной странице (для всех пользователей).
Вдобавок к этому мы сделаем, чтобы список топиков и отдельные топики были доступны в форматах HTML, JSON и XML.
Это позволит нам проиллюстрировать, как можно использовать Gin для проектирования веб-приложений, API серверов и микросервисов.
Для этого мы используем следующий функционал Gin:
- Routing — для обработки различных URL адресов,
- Custom rendering — для обработки формата ответа, и
- Middleware — для реализации авторизации.
Также мы напишем тесты для проверки работоспособности нашего функционала.
Routing
Роутинг (маршрутизация) это одна из важнейших функций, имеющихся во всех современных веб-фреймворках. Любая веб-страница или вызов API доступен по URL. Фреймворки используют роуты для обработки запросов к этим URL-адресам. Если URL такой: httр://www.example.com/some/random/route, то роут будет: /some/random/route.
У Gin очень быстрый роутер, удобный в конфигурировании и работе. Вместе с обработкой определенных URL-адресов, роутер в Gin может обрабатывать шаблоны адресов и группы URL.
В нашем приложении мы будем:
- Хранить главную страницу в роуте / (запрос HTTP GET),
- Группировать роуты, относящиеся к пользователям, в роуте /u ,
- Хранить страницу авторизации в /u/login (запрос HTTP GET),
- Передавать данные авторизации в /u/login (запрос HTTP POST),
- Завершение сеанса в /u/logout (запрос HTTP GET),
- Хранить страницу регистрации в /u/register (запрос HTTP GET),
- Передавать регистрационную информацию в /u/register (запрос HTTP POST) ,
- Группировать роуты, относящиеся к топикам, в роуте /article,
- Хранить страницу создания топика в /article/create (запрос HTTP GET),
- Передавать утверждённый топик в /article/create (запрос HTTP POST), и
- Хранить страницу топика в /article/view/:article_id (запрос HTTP GET). Обратите внимание на часть :article_id в этом роуте. Двоеточие : в начале указывает на то, что это динамический роут. Это значит, что :article_id может содержать любое значение и Gin сделает это значение доступным в обработчике запроса.
Rendering
Веб-приложение может вывести ответ в различных форматах, таких как HTML, текст, JSON, XML или другие форматы. API и микросервисы обычно отдают данные в формате JSON, но здесь также нет ограничений.
В следующем разделе мы увидим, как можно обработать разные типы ответов без дублирования функционала. По-умолчанию мы будем отвечать на запрос шаблоном HTML. Однако, мы создадим ещё два вида запроса, которые будут отвечать в формате JSON или XML.
Middleware
В контексте веб-приложений на Go, middleware это часть кода, которую можно выполнить на любом этапе обработки HTTP-запроса. Обычно их используют для инкапсуляции типового функционала, который вам нужно вызывать из различных роутов. Мы можем использовать middleware перед и/или после обработанного HTTP-запроса. К типовым примерам применения middleware относятся авторизация, валидация и т.п.
Если middleware используется перед обработкой роута, любые изменения, сделанные им, будут доступны в главном обработчике запросов. Это удобно, если мы хотим реализовать проверку определённых запросов. С другой стороны, если middleware используется после обработчика, он получит ответ из обработчика роутов. Это можно использовать для модификации ответа из обработчика роута.
Мы должны быть уверены, что некоторые страницы и действия, к примеру, создание топика, завершение сеанса, доступны только авторизованным пользователям. И также необходимо, чтобы некоторые страницы и действия, к примеру, регистрация, авторизация, были доступны только неавторизованным пользователям.
Если мы включим соответствующую логику в каждый роут, это будет сложно, излишне повторяемо и склонно к ошибкам. К счастью, мы можем создать middleware для каждой из этих задач и многократно использовать их в соответствующих роутах.
Мы создадим middleware, которое будет применимо ко всем роутам. Наше middleware (setUserStatus) будет проверять — от авторизованного пользователя пришёл запрос или от неавторизованного. Затем оно установит флаг, который можно будет использовать в шаблонах для настройки видимости определённых ссылок в меню приложения.
Установка зависимостей
Наше приложение будет использовать только одну внешнюю зависимость — сам фреймворк Gin. Установим актуальную версию такой командой:
go get -u github.com/gin-gonic/gin
Создание многократно-используемых шаблонов
Наше приложение будет отображать веб-страницу, используя её шаблон. Однако, в ней будет несколько частей, таких как шапка (header), меню, боковая панель и подвал (footer), которые будут представлены на всех страницах. В Go можно создавать шаблонные снипеты, которые можно будет загружать в любые шаблоны.
Мы создадим снипеты для шапки и подвала, также создадим меню в соответствующем файле-шаблоне, которое затем вызовем из шапки. Ну и наконец, мы создадим шаблон главной страницы, с которой вызовем шапку и подвал. Все файлы шаблонов будут размещаться в папке templates нашего проекта.
Сначала создайте шаблон меню в файле templates/menu.html как описано ниже:
<!--menu.html-->
<nav class="navbar navbar-default">
<div class="container">
<div class="navbar-header">
<a class="navbar-brand" href="/">
Home
</a>
</div>
</div>
</nav>
Пока в нашем меню есть только одна ссылка на главную страницу. Позже мы добавим остальные ссылки по мере роста функционала приложения. Шаблон шапки будет в файле templates/header.html:
<!--header.html-->
<!doctype html>
<html>
<head>
<!--Use the `title` variable to set the title of the page-->
<title>{{ .title }}</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta charset="UTF-8">
<!--Use bootstrap to make the application look nice-->
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/css/bootstrap.min.css" integrity="sha384-1q8mTJOASx8j1Au+a5WDVnPi2lkFfwwEAa8hDDdjZlpLegxhjVME1fgjWPGmkzs7" crossorigin="anonymous">
<script async src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/js/bootstrap.min.js" integrity="sha384-0mSbJDEHialfmuBBQP6A4Qrprq5OVfW37PRR3j5ELqxss1yVqOtnepnHVP9aJ7xS" crossorigin="anonymous"></script>
</head>
<body class="container">
<!--Embed the menu.html template at this location-->
{{ template "menu.html" . }}
Как вы видите, мы используем здесь фреймворк с открытым исходным кодом Bootstrap. Большая часть файла это стандартный HTML. Однако, посмотрим внимательно на пару строк. В строке сдинамически задаётся заголовок страницы с помощью переменной .title, которая должна быть определена в приложении. А в строке {{ template «menu.html» . }} мы загружаем шаблон меню из файла menu.html. Вот так в Go можно вызывать один шаблон из другого.
Шаблон подвала содержит только статический HTML. Шаблон главной страницы вызывает шапку и подвал и выводит сообщение Hello Gin:
<!--index.html-->
<!--Embed the header.html template at this location-->
{{ template "header.html" .}}
<h1>Hello Gin!</h1>
<!--Embed the footer.html template at this location-->
{{ template "footer.html" .}}
По аналогии с шаблоном главной, в шаблонах других страниц мы также используем эти шапку и подвал.
Завершение и проверка установки
Создав шаблоны, теперь самое время создать главный файл приложения. Мы создадим файл main.go, в нём будет простое веб-приложение, загружающее главную страницу. С Gin это делается в четыре шага:
1. Создаём роутер
Роутер в Gin создаётся так:
router := gin.Default()
2. Загружаем шаблоны
После создания роутера, загрузим все шаблоны:
router.LoadHTMLGlob("templates/*")
Это загрузит все шаблоны из папки templates. Загрузив один раз шаблоны, больше не будет необходимости перечитывать их, что делает веб-приложения с Gin очень быстрыми.
3. Задаём обработчик роутов
Очень важно правильно спроектировать приложение, разделив на соответствующие роуты и задав обработчики для каждого из них. Мы создадим роут для главной страницы и его обработчик.
router.GET("/", func(c *gin.Context) {
// Call the HTML method of the Context to render a template
c.HTML(
// Set the HTTP status to 200 (OK)
http.StatusOK,
// Use the index.html template
"index.html",
// Pass the data that the page uses (in this case, 'title')
gin.H{
"title": "Home Page",
},
)
})
С помощью метода router.GET мы задаём обработчик роута для GET-запросов. Он принимает в качестве параметров сам роут (/) и один или несколько обработчиков, которые всего лишь функции.
Обработчик роута имеет указатель на Контекст (gin.Context) в параметрах. В этом контексте содержится вся информация о запросе, которая может понадобится обработчику в дальнейшем. К примеру, в нём есть информация о заголовках, cookies и т.д. В Контексте также есть методы для вывода ответа в форматах HTML, тексте, JSON и XML. В нашем случае мы взяли метод context.HTML для обработки HTML шаблона (index.html). Вызов этого метода включает дополнительные данные, в которых значение title установлено Home Page. Это значение, которое может быть обработано в HTML шаблоне. Мы используем это значение в теге в шаблоне шапки.
4. Запуск приложения Для запуска приложения воспользуемся методом Run нашего роутера:
router.Run()
Приложение запустится на localhost и 8080 порте, по-умолчанию.
Финальный файл main.go будет таким:
// main.go
package main
import (
"net/http"
"github.com/gin-gonic/gin"
)
var router *gin.Engine
func main() {
// Set the router as the default one provided by Gin
router = gin.Default()
// Process the templates at the start so that they don't have to be loaded
// from the disk again. This makes serving HTML pages very fast.
router.LoadHTMLGlob("templates/*")
// Define the route for the index page and display the index.html template
// To start with, we'll use an inline route handler. Later on, we'll create
// standalone functions that will be used as route handlers.
router.GET("/", func(c *gin.Context) {
// Call the HTML method of the Context to render a template
c.HTML(
// Set the HTTP status to 200 (OK)
http.StatusOK,
// Use the index.html template
"index.html",
// Pass the data that the page uses (in this case, 'title')
gin.H{
"title": "Home Page",
},
)
})
// Start serving the application
router.Run()
}
Для запуска приложения из командной строки, перейдите в папку приложения и выполните команду:
go build -o app
Будет собрано приложение и создан исполняемый файл с именем app, который можно запустить так:
./app
Если всё прошло успешно, вы должны увидеть приложение по адресу http://localhost:8080 и оно будет выглядеть примерно так:
На этом этапе иерархия папок приложения будет такой:
├── main.go
└── templates
├── footer.html
├── header.html
├── index.html
└── menu.html
Разработчик: java, kotlin, c#, javascript, dart, 1C, python, php.
Пишите: @ighar. Buy me a coffee, please :).