В предыдущей статье мы создали RESTful API в Golang. Теперь, когда у нас есть готовый проект, надо понять, как его использовать. Ведь если нельзя протестировать проект и увидеть его возможности, он будет просто бесполезен.
Без полноценной документации, позволяющей тестировать конечные точки и видеть возможности проекта, пользователи не будут даже пытаться его использовать. Стало быть, нужно написать документацию, но на это может уйти много времени, которое можно было бы потратить на разработку крутых функций для наших приложений. Так что же делать? Крутым функциям — крутая документация Swagger!
Библиотеки
Начнём создавать Swagger документацию с библиотек. Вообще-то, нужна всего одна — swag — библиотека Golang, которая преобразует аннотации кода (комментарии) в документацию Swagger Documentation 2.0. Кроме swag, нам понадобится промежуточное ПО/библиотека-оболочка для каркаса разработки веб-приложений. В swag документации присутствуют ссылки на библиотеки для поддерживаемых платформ. Здесь есть простенький вариант на net/http
, которому отдаёт предпочтение большинство людей. Лично мне больше нравится GIN, его я вам и продемонстрирую. Если вы используете какую-то другую платформу — не беда, ведь аннотации одни и те же, так что всё равно почерпнёте здесь для себя что-то новое.
Стоит упомянуть и о ещё одной библиотеке go-swagger
, которая кажется намного более мощной и популярной: переключайтесь на неё, если вам нужен больший контроль над тем, что у вас генерируется. Ну а я предпочитаю библиотеку swaggo/swag
из-за её простоты.
Строка документации
Теперь переходим к аннотациям/комментариям/строке документации или как там всё это дело называется? Короче, пояснениям перед конкретной API функцией, использующейся для генерирования Swagger документации.
Прежде чем приступать к рассмотрению отдельных конечных точек API, нам надо составить общее описание всего нашего проекта. Эта часть аннотаций находится в пакете main
, непосредственно перед функцией main
:
Примечание: все примеры взяты из моего репозитория, где можно найти запускаемое приложение вместе с пользовательским интерфейсом Swagger UI и документацией.
import (
...
swaggerFiles "github.com/swaggo/files"
"github.com/swaggo/gin-swagger"
_ "github.com/MartinHeinz/go-project-blueprint/cmd/blueprint/docs"
)
// @title Blueprint Swagger API
// @version 1.0
// @description Swagger API for Golang Project Blueprint.
// @termsOfService http://swagger.io/terms/
// @contact.name API Support
// @contact.email [email protected]
// @license.name MIT
// @license.url https://github.com/MartinHeinz/go-project-blueprint/blob/master/LICENSE
// @BasePath /api/v1
func main() {
...
r.GET("/swagger/*any", ginSwagger.WrapHandler(swaggerFiles.Handler))
...
}
Вот первый пример с общей информацией об API: имя, версия, лицензия, базовый URL и т.д. Вы можете добавить ещё несколько полей, которые приводятся вот тут вместе с примерами.
Кроме аннотаций, надо импортировать необходимые библиотеки, в том числе blank import пакета docs
, который мы будем генерировать (об этом дальше). И ещё нам нужно подключить в одной из конечных точек интерфейс Swagger UI. Здесь используем "/swagger/*any
.
Вот часть пользовательского интерфейса, создаваемого аннотациями:
Дальше важная часть — аннотации для API функций. Аннотации стоят перед каждой функцией, зашитой в main
, и при работе с конечной точкой, такой как эта v1.GET("/users/:id", apis.GetUser)
, надо предварять её аннотацией. Как показано здесь:
// GetUser godoc
// @Summary Retrieves user based on given ID
// @Produce json
// @Param id path integer true "User ID"
// @Success 200 {object} models.User
// @Router /users/{id} [get]
func GetUser(c *gin.Context) {
...
}
Это минимальный набор аннотаций, который вы должны использовать. Здесь всё интуитивно понятно. Мне хотелось бы только обратить ваше внимание на models.User
, возвращаемый при успешном завершении. Это модель таблицы базы данных, которая находится в пакете models
. Ссылка на неё приводит к её появлению в интерфейсе Swagger UI в разделе models:
А это раздел для нашей конечной точки:
Генерируем документацию!
Здесь нам потребуется всего одна команда swag init
, которую мы должны запустить из каталога с main
. Для репозитория нашего макета это будет .../cmd/blueprint/
. Этой командой будет создан пакет docs
с двумя версиями документации — в формате JSON и YAML.
Имея возможность таким образом генерировать этот пакет, я всё же предпочитаю хранить его на GitHub: ведь он импортируется в пакете main
, следовательно, нужен для запуска приложения. Если нет желания помещать этот сгенерированный код на GitHub, можете написать цель Makefile, которая тут же повторно сгенерирует Swagger документацию перед созданием и запуском приложения. Если всё же решите отправить его на GitHub, возможно, потребуется запускать документацию через go fmt
, потому что не факт, что она будет форматирована так, «как должна быть».
Аутентификация
На этом этапе мы могли бы уже запустить приложение, полюбоваться новеньким интерфейсом Swagger UI и поставить точку. Не хватает лишь одного — аутентификации для API. Если сейчас оставить Swagger UI как есть, кто угодно сможет отправить запрос в любую конечную точку. А это крайне нежелательно, так как в этом случае есть опасность повреждения данных или из базы данных может произойти утечка конфиденциальной информации в Интернет. Словом, причин установить хотя бы простую аутентификацию пользователя для API и Swagger UI у нас предостаточно. Как же нам это сделать?
Во-первых, давайте реализуем аутентификацию. В случае с GIN создаём простенькое промежуточное ПО аутентификации, которое мы подключаем к группе маршрутизаторов:
func auth() gin.HandlerFunc { // Функция промежуточного ПО
return func(c *gin.Context) {
authHeader := c.GetHeader("Authorization") // Получаем заголовок
if len(authHeader) == 0 { // Проверяем, установлен ли
httputil.NewError(c, http.StatusUnauthorized, errors.New("Authorization is required Header"))
c.Abort()
}
if authHeader != config.Config.ApiKey { // Проверяем, подходит ли API-ключ
httputil.NewError(c, http.StatusUnauthorized, fmt.Errorf("this user isn't authorized to this operation: api_key=%s", authHeader))
c.Abort()
}
c.Next()
}
}
Подключая промежуточное ПО к той или иной группе (группам), мы можем контролировать, что подлежит аутентификации, а что — нет. Например, нам не нужно, чтобы сам Swagger UI проходил аутентификацию.
Примечание: я убрал из примеров часть кода для простоты восприятия. Весь код доступен в ветке rest-api
репозитория здесь.
Вернёмся теперь к модулю main
. Здесь нам надо добавить аннотацию securityDefinitions
:
// @securityDefinitions.apikey ApiKeyAuth
// @in header
// @name Authorization
func main() {
...
}
Мы прикручиваем к Swagger UI аутентификацию через API ключи в заголовке Authorization
. Кроме такой аутентификации, вы можете также установить базовую аутентификацию (securitydefinitions.basic
) с использованием имени пользователя и пароля или одну из версий OAuth2 (securitydefinitions.oauth2
): все варианты доступны в документации здесь. Лично я предпочитаю API-ключи за простоту и удобство.
Чтобы Swagger понимал, что та или иная конечная точка прошла аутентификацию, к уже упомянутой API функции надо добавить аннотацию @Security
:
// @Security ApiKeyAuth
func GetUser(c *gin.Context) {
...
}
На этом всё. Выполнив повторную генерацию документации, запускаем наше приложение:
foo@bar:~$ docker run -p 1234:1234 \
-e BLUEPRINT_DSN="postgres://<USER>:<PASSWORD>@<DOCKER_SERVICE/URL>:<PORT>/<DB>?sslmode=disable" \
-e BLUEPRINT_API_KEY="api_key" \
docker.pkg.github.com/martinheinz/go-project-blueprint/blueprint:latest
В логах GIN вы должны увидеть что-то вроде этого:
[GIN-debug] GET /swagger/*any --> github.com/swaggo/gin-swagger.CustomWrapHandler.func1 (3 handlers) # <- Swagger готов!
[GIN-debug] GET /api/v1/users/:id --> github.com/MartinHeinz/go-project-blueprint/cmd/blueprint/apis.GetUser (4 handlers)
Теперь можно открыть Swagger UI на http://localhost:1234/swagger/index.html и проверить нашу документацию!
Внимание: если эта команда не даёт результата, пробежитесь ещё раз по предыдущей статье.
Напомним: security definition в пакете main
даёт нам следующее окошко:
При вводе неправильного API-ключа ("wrong_api_key"
) получаем код ответа 401:
Если же API-ключ подошёл, получаем 200 вместе с запрашиваемыми данными:
Важно отметить, что отправка аутентификационных заголовков открытым текстом (как это сделали мы) никак не защищена и сводит на нет цель аутентификации. Поэтому в реальных приложениях надо использовать HTTP.
Заключение
Надеюсь, что теперь вы сможете настроить Swagger документацию для API, а также написать крутую документацию для очередного своего проекта — это не так сложно. Хорошая документация будет вам большим подспорьем при проведении тестирования и здорово поможет пользователям вашего приложения.
Читайте также:
- Конкурентность и параллелизм в Golang. Горутины.
- Обработка ошибок в Golang с помощью Panic, Defer и Recover
- Golang — изящная обработка ошибок
Перевод статьи Martin Heinz: Setting Up Swagger Docs for Golang API (впервые опубликована на martinheinz.dev).