- Middlewares
- Группировка запросов в маршрутизации
- Логирование запросов
- Ограничение количества запросов
- Выводы
В веб-приложениях часто есть логика, которая должна выполняться перед обработкой любого запроса. К такой логике относятся: проверка доступа, логирование запроса, ограничение количества запросов. Перед веб-разработчиками часто встает проблема в организации такой логики, чтобы разрабатывать без сложностей и поддерживать приложение.
Чтобы решить эту проблему, в веб-приложениях используются посредники, которые выполняются перед обработчиками HTTP-запросов. В этом уроке мы научимся использовать посредников в микрофреймворке Fiber и разберем конкретные примеры реализации.
Middlewares
Посредники или middlewares в Fiber являются функциями func(c *fiber.Ctx) error
, которые мы устанавливаем перед обработчиками. Посредник может изменять запрос, добавлять заголовки, выполнять логирование. Посредник также может прервать цепочку обработки запроса, если возникла ошибка.
Пример посредника, который проверяет возможность доступа пользователя к веб-приложению, может выглядеть следующим образом:
package main
import (
"github.com/gofiber/fiber/v2"
"github.com/sirupsen/logrus"
)
func main() {
webApp := fiber.New()
webApp.Use(accessMiddleware)
webApp.Post("/do/something", func(ctx *fiber.Ctx) error {
...
})
logrus.Fatal(webApp.Listen(":80"))
}
func accessMiddleware(c *fiber.Ctx) error {
accessToken := c.Params("access_token")
if !hasAccess(accessToken) {
// Посредник прерывает цепочку обработки запроса.
return c.SendStatus(fiber.StatusUnauthorized)
}
// Пользователь имеет доступ, продолжаем выполнение запроса.
return c.Next()
}
В примере выше мы создали посредник accessMiddleware
, который проверяет наличие корректного токена доступа в параметрах запроса. Если токен не прошел проверку, то посредник прерывает цепочку обработки запроса и возвращает клиенту статус 401 Unauthorized. Если токен валиден, то посредник вызывает метод c.Next()
, который продолжает обработку запроса.
Такой подход работает, когда мы хотим использовать посредника на всех обработчиков в приложении. Но бывают ситуации, когда нам нужно использовать посредник только на некоторых обработчиках.
Группировка запросов в маршрутизации
Чтобы назначить посредника только на некоторые обработчики в Fiber, их нужно сначала сгруппировать с помощью функции r.Group()
. После этого мы можем устанавливать посредников для всей группы с помощью r.Use()
:
package main
import (
"github.com/gofiber/fiber/v2"
"github.com/sirupsen/logrus"
)
func main() {
webApp := fiber.New()
// Создаем группу с префиксом пути запроса "/authorized".
authGroup := webApp.Group("/authorized")
// Добавляем посредника проверки доступа в группу.
authGroup.Use(accessMiddleware)
// Добавляем обработчики запросов в группу.
authGroup.Post("/action/1", func(ctx *fiber.Ctx) error {})
authGroup.Post("/action/2", func(ctx *fiber.Ctx) error {})
authGroup.Post("/action/3", func(ctx *fiber.Ctx) error {})
// Создаем группу с префиксом пути запроса "/public".
publicGroup := webApp.Group("/public")
// У группы нет посредников.
// При запросах к группе сразу выполняются обработчики.
publicGroup.Post("/action/1", func(ctx *fiber.Ctx) error {})
publicGroup.Post("/action/2", func(ctx *fiber.Ctx) error {})
publicGroup.Post("/action/3", func(ctx *fiber.Ctx) error {})
logrus.Fatal(webApp.Listen(":80"))
}
В примере выше мы создали две группы запросов: authGroup и publicGroup. В группу authGroup мы добавили посредник accessMiddleware, который проверяет наличие корректного токена доступа в параметрах запроса. В группу publicGroup мы не добавили посредников, поэтому при запросах к группе сразу выполняются обработчики:
Мы разобрались, как создавать своих посредников в Fiber. Но также существует набор готовых посредников, которые мы можем подключать в своих проектах. Каждый посредник представлен отдельным пакетом, который можно добавлять в проект при необходимости. Рассмотрим самые популярные из них.
Логирование запросов
Логирование необходимо для отладки работающих веб-приложений. Помимо явного логирования в местах ошибок, мы можем добавить логирование всех запросов. Так мы будем понимать, какие запросы приходят в приложение, по какому пути и с какими параметрами.
Чтобы добавить логирование всех запросов в Fiber-приложение, нужно подключить к проекту пакет github.com/gofiber/fiber/v2/middleware/logger и инициализировать его перед всеми обработчиками:
package main
import (
"github.com/gofiber/fiber/v2"
"github.com/gofiber/fiber/v2/middleware/logger"
"github.com/sirupsen/logrus"
"time"
)
func main() {
webApp := fiber.New()
webApp.Use(logger.New())
webApp.Get("/", func(c *fiber.Ctx) error {
// Создаем искусственную задержку, чтобы проверить логирование.
time.Sleep(300 * time.Millisecond)
return c.SendString("OK")
})
logrus.Fatal(webApp.Listen(":80"))
}
Запускаем веб-приложение и переходим в браузере на страницу http://localhost. Видим, что в ответ пришло сообщение «OK». При этом в консоли веб-приложения появилось сообщение о залогированном запросе:
12:59:24 | 200 | 301ms | 127.0.0.1 | GET | /
Если несколько раз перезагружать страницу http://localhost, мы увидим несколько записей лога.
Так мы добавили логирование всех запросов в наше веб-приложение. По умолчанию логирование происходит в консоль, но можно настроить логирование в файл или в другие системы логирования.
Также при инициализации посредника для логирования мы можем указать формат логов, который подходит для нашего проекта. Для этого нужно передать в функцию инициализации посредника параметр logger.Config
:
package main
import (
"github.com/gofiber/fiber/v2"
"github.com/gofiber/fiber/v2/middleware/logger"
"github.com/sirupsen/logrus"
"time"
)
func main() {
webApp := fiber.New()
webApp.Use(logger.New(logger.Config{
Format: "${time} ${method} ${path} - ${status} - ${latency}\n",
TimeFormat: "2006-01-02 15:04:05.000000",
}))
webApp.Get("/", func(c *fiber.Ctx) error {
// Создаем искусственную задержку, чтобы проверить логирование.
time.Sleep(300 * time.Millisecond)
return c.SendString("OK")
})
logrus.Fatal(webApp.Listen(":80"))
}
Запускаем веб-приложение и переходим в браузере на страницу http://localhost. Видим, что в ответ пришло сообщение «OK». При этом в консоли веб-приложения изменился формат логов:
2022-10-28 13:02:07.607325 GET / - 200 - 301ms
Также хорошей практикой считается логировать идентификатор, который поможет нам связать все логи в рамках одного запроса. Для этого нам нужно подключить к проекту пакет github.com/gofiber/fiber/v2/middleware/requestid. И инициализировать его перед посредником для логирования:
package main
import (
"github.com/gofiber/fiber/v2"
"github.com/gofiber/fiber/v2/middleware/logger"
"github.com/gofiber/fiber/v2/middleware/requestid"
"github.com/sirupsen/logrus"
"time"
)
func main() {
webApp := fiber.New()
webApp.Use(requestid.New())
webApp.Use(logger.New(logger.Config{
Format: "${locals:requestid}: ${time} ${method} ${path} - ${status} - ${latency}\n",
TimeFormat: "2006-01-02 15:04:05.000000",
}))
webApp.Get("/", func(c *fiber.Ctx) error {
// Создаем искусственную задержку, чтобы проверить логирование.
time.Sleep(300 * time.Millisecond)
logrus.WithFields(logrus.Fields{
"request_id": c.Locals("requestid"),
}).Warn("something went wrong")
return c.SendString("OK")
})
logrus.Fatal(webApp.Listen(":80"))
}
Запускаем веб-приложение и открываем в браузере страницу http://localhost. После этого проверяем консоль веб-приложения:
WARN[0001] something went wrong request_id=69b39a07-fea7-45a1-809d-3f3f1f897068
69b39a07-fea7-45a1-809d-3f3f1f897068: 2022-10-28 13:41:38.611714 GET / - 200 - 300ms
Мы видим, что теперь в каждом логе есть идентификатор запроса. Если что-то пойдет не так, по идентификатору мы сможем понять, в рамках какого запроса происходили ошибки. Это поможет при отладке или исправлении ошибок в работающем веб-приложении.
Логирование запросов помогает нам разобраться в возникающих ошибках, но есть посредники, которые позволяют предотвратить новые ошибки. Один из примеров таких посредников — ограничение количества запросов в единицу времени.
Ограничение количества запросов
Представим, что у нас есть небольшое веб-приложение, у которого сто клиентов, и все работает бесперебойно. Конкурент завидует нашему успеху и решает написать скрипт, который будет отправлять миллионы запросов в наше приложение. Цель конкурента — вывести наши сервера из работы, чтобы клиенты не могли пользоваться приложением, и бизнес терял прибыль.
Чтобы защититься со стороны веб-приложения от таких атак, следует настроить ограничение количества запросов — throttling. Для этого мы будем использовать пакет github.com/gofiber/fiber/v2/middleware/limiter. Когда пакет подключен, мы можем настроить нового посредника, чтобы ограничить количество запросов с одного IP-адреса:
package main
import (
"github.com/gofiber/fiber/v2"
"github.com/gofiber/fiber/v2/middleware/limiter"
"github.com/sirupsen/logrus"
"time"
)
func main() {
webApp := fiber.New()
webApp.Use(limiter.New(limiter.Config{
KeyGenerator: func(c *fiber.Ctx) string {
return c.IP()
},
Max: 3,
Expiration: 10 * time.Second,
}))
webApp.Get("/", func(c *fiber.Ctx) error {
return c.SendString("OK")
})
logrus.Fatal(webApp.Listen(":80"))
}
Запускаем веб-приложение и открываем в браузере страницу http://localhost четыре раза. На первые три запроса мы получаем ответ «OK», но на четвертый получаем ответ "Too Many Requests".
С помощью структуры limiter.Config
мы установили максимальное количество запросов в единицу времени. В нашем случае это три запроса в десять секунд. Если мы превысим это количество, то наш запрос будет отклонен с кодом 429 (Too Many Requests), что мы и увидели на четвертый запрос. По истечении десяти секунд с момента первого запроса мы снова сможем открывать страницу и получать успешный ответ.
В данном примере мы использовали IP-адрес клиента, чтобы определить источник запроса. Но можно использовать любой другой идентификатор, например, идентификатор пользователя или сессии.
Выводы
- Посредники позволяют выполнять действия до или после обработки запроса
- Fiber позволяет группировать обработчиков, чтобы один раз описывать всех посредников для группы обработчиков
- Посредники могут быть использованы для логирования запросов, проверки доступа, ограничения количества запросов
Дополнительные материалы
Остались вопросы? Задайте их в разделе «Обсуждение»
Вам ответят команда поддержки Хекслета или другие студенты
- Статья «Как учиться и справляться с негативными мыслями»
- Статья «Ловушки обучения»
- Статья «Сложные простые задачи по программированию»
- Вебинар «Как самостоятельно учиться»
Для полного доступа к курсу нужен базовый план
Базовый план откроет полный доступ ко всем курсам, упражнениям и урокам Хекслета, проектам и пожизненный доступ к теории пройденных уроков. Подписку можно отменить в любой момент.