Разработка Web-приложений и микросервисов на Go с Gin, часть 1

Разработка /
Разработка: Разработка Web-приложений и микросервисов на Golang с Gin

Введение

Сегодня мы покажем, как создавать веб-приложения и микросервисы в 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 }} динамически задаётся заголовок страницы с помощью переменной .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 и оно будет выглядеть примерно так:
Разработка: Разработка Web-приложений и микросервисов на Golang с Gin

На этом этапе иерархия папок приложения будет такой:

├── main.go
└── templates
    ├── footer.html
    ├── header.html
    ├── index.html
    └── menu.html


Продолжение
2 комментария
w32blaster
очень полезная статья (для меня, по крайней мере). Начал изучение этого фреймворка, сейчас пытаюсь написать простое приложение. Спасибо автору
fokusov
как автору перевода очень приятно, что вам понравился материал :) В конце третьей части есть ссылка на первоисточник на английском (вдруг будет полезно).
Только зарегистрированные и авторизованные пользователи могут оставлять комментарии.