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

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

Сегодня мы покажем, как создавать веб-приложения и микросервисы в 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
Разработка Web-приложений и микросервисов на Go с Gin, часть 1

Если всё прошло успешно, вы должны увидеть приложение по адресу http://localhost:8080 и оно будет выглядеть примерно так:

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

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

Продолжение

Leave a Comment