Привет, ребята, рад вас снова видеть на мастер-классе по бэкенду!
На этой лекции мы узнаем как создать API документацию для сервера gRPC шлюза, который мы реализовали ранее. Сделать это на самом деле довольно просто.
На странице документации gRPC шлюза
давайте поищем по ключевому слову OpenAPI
. OpenAPI — это стандартная
техническая спецификация для описания RESTful веб-сервисов.
В этом разделе мы найдем инструкции для автоматического генерирования OpenAPI документов, используя те же определения protobuf, которые были написаны в предыдущих лекциях.
Поскольку мы уже установили protoc-gen-openapiv2
в лекции 43, нам не нужно
снова это делать, и мы просто используем его. Если вы применяете buf
, то
можете обновить buf.gen.yaml
, как в этом примере.
version: v1
plugins:
- name: go
out: gen/go
opt:
- paths=source_relative
- name: go-grpc
out: gen/go
opt:
- paths=source_relative
- name: grpc-gateway
out: gen/go
opt:
- paths=source_relative
- name: openapiv2
out: gen/openapiv2
В нашем случае мы используем не buf
, а protoc
. Поэтому ниже показано
как мы обновим нашу команду protoc
.
protoc -I . --openapiv2_out ./gen/openapiv2 \
--openapiv2_opt logtostderr=true \
your/service/v1/your_service.proto
Во-первых, мы должны добавить ключ openapiv2_out
, чтобы указать папку
куда сгенерировать документацию. Я создам новую папку под названием
swagger
внутри папки doc
и здесь напишу doc/swagger
.
proto:
rm -f pb/*.go
protoc --proto_path=proto --go_out=pb --go_opt=paths=source_relative \
--go-grpc_out=pb --go-grpc_opt=paths=source_relative \
--grpc-gateway_out=pb --grpc-gateway_opt=paths=source_relative \
--openapiv2_out=doc/swagger \
proto/*.proto
И этого будет достаточно для генерации документации. Откроем терминал и выполним
make proto
Хорошо, теперь, если мы заглянем в папку swagger
, то увидим, что было
сгенерировано несколько JSON файлов. Каждый файл соответствует одному
proto
файлу, созданному нами в папке proto
.
Хотя мы можем оставить их как есть, в виде отдельных файлов, я думаю что будет удобнее объединить их в один JSON файл. Причина в том, что позднее будет гораздо проще загрузить его на Swagger Hub.
Итак, как мы можем это сделать?
Итак, во-первых, я собираюсь добавить сюда команду, чтобы удалить все
существующие Swagger JSON файлы перед запуском protoc
для регенерации.
proto:
rm -f pb/*.go
rm -f doc/swagger/*.swagger.json
protoc --proto_path=proto --go_out=pb --go_opt=paths=source_relative \
--go-grpc_out=pb --go-grpc_opt=paths=source_relative \
--grpc-gateway_out=pb --grpc-gateway_opt=paths=source_relative \
--openapiv2_out=doc/swagger \
proto/*.proto
Затем мы можем добавить к команде openapiv2
ключ, чтобы сообщить
protoc
, что мы хотим объединить все файлы JSON вместе. Чтобы узнать,
как называется этот ключ, мы запустим следующую команду для вызова
справочной информации в терминале:
protoc-gen-openapiv2 --help
Она достаточно длинная, поэтому давайте поищем по ключевому слову
merge
.
protoc-gen-openapiv2 --help
Usage of protoc-gen-openapiv2:
-allow_delete_body
unless set, HTTP DELETE methods may not have a body
-allow_merge
if set, generation one OpenAPI file out of multiple protos
Вот нужный нам ключ, если этот параметр allow_merge
равен true
, то на
выходе мы получим единственный OpenAPI JSON файл. Итак, давайте зададим
для allow_merge
значение true
в нашей команде protoc
.
proto:
rm -f pb/*.go
rm -f doc/swagger/*.swagger.json
protoc --proto_path=proto --go_out=pb --go_opt=paths=source_relative \
--go-grpc_out=pb --go-grpc_opt=paths=source_relative \
--grpc-gateway_out=pb --grpc-gateway_opt=paths=source_relative \
--openapiv2_out=doc/swagger --openapiv2_opt=allow_merge=true \
proto/*.proto
Кроме того, мы также можем задать название для результирующего файла,
используя следующий ключ merge_file_name
.
-merge_file_name string
target OpenAPI file name prefix after merge (default "apidocs")
Поэтому здесь я поставлю запятую, чтобы разделить два ключа и задам для
merge_file_name
значение simple_bank
.
proto:
rm -f pb/*.go
rm -f doc/swagger/*.swagger.json
protoc --proto_path=proto --go_out=pb --go_opt=paths=source_relative \
--go-grpc_out=pb --go-grpc_opt=paths=source_relative \
--grpc-gateway_out=pb --grpc-gateway_opt=paths=source_relative \
--openapiv2_out=doc/swagger --openapiv2_opt=allow_merge=true,merge_file_name=simple_bank \
proto/*.proto
На этом по сути всё!
Теперь давайте снова запустим
make proto
чтобы повторно сгенерировать документацию.
На этот раз, если мы заглянем в папку swagger
, то увидим, что там
находится единственный файл под названием simple_bank.swagger.json
.
Этот файл содержит описания всех API нашего простого банковского сервиса.
Итак, теперь, если мы хотим поделиться им с командой или другими
разработчиками интерфейса, мы можем загрузить его в Swagger Hub.
Вы можете перейти по адресу swagger.io и создать
бесплатную учетную запись. После того как вы вошли в систему, вы можете
легко импортировать API документацию, с помощью этой кнопки Import API
,
выбрав JSON файл, который только что сгенерировал protoc
, и нажав
Upload File
.
Далее у нас есть возможность задать название и версию API. Я назову его
simple-bank
. И в качестве версии выберу 1.0
. Затем нажмите Import OpenAPI
.
И, вуаля, документация успешно импортирована.
Мы видим, что прямо сейчас в ней есть 2 API для создания и входа пользователей в систему.
Как владельцы документации, вы можете обновить документацию в любое время, используя эту страницу.
Если вы хотите поделиться ею с кем-то, просто нажмите на эту кнопку View Documentation
и вы попадете на страницу с доступом только для чтения.
Это страница, которой вы можете поделиться с членами вашей команды или другими фронтенд разработчиками
Итак, теперь вы знаете, как сгенерировать и поделиться вашей документацией к API.
Далее я покажу вам, как обновить его с помощью некоторых пользовательских аннотаций.
Выше вы видели, что мне пришлось ввести название и версию документации вручную. Можем ли мы каким-то образом сгенерировать его автоматически? Ответ — да!
Как видно на рисунке ниже, JSON файл, который мы загрузили, был преобразован в YAML формат, а в вверху приведена некая информация о названии и версии API.
swagger: '2.0'
info:
title: rpc_create_user.proto
version: '1.0'
tags:
- name: SimpleBank
Мы можем добавить определённую специальную аннотацию к нашему proto
определению, чтобы эта информация могла автоматически генерироваться
protoc
. Вернемся на страницу GitHub grpc-gateway
и откроем этот файл a_bit_of_everything.proto.
В этом файле мы увидим пример того, как добавить информационный раздел в
верхнюю часть proto
файла в качестве опции OpenAPI Swagger. В этот раздел
можно добавить много информации, но я просто скопирую небольшую часть для
демонстрационных целей.
Теперь давайте вернемся к нашему коду и откроем файл
service_simple_bank.proto
. Я вставлю фрагмент в начало файла,
непосредственно перед определением сервиса SimpleBank
. Итак, давайте
переместим этот импорт сюда,
import "google/api/annotations.proto";
import "rpc_create_user.proto";
import "rpc_login_user.proto";
import "protoc-gen-openapiv2/options/annotations.proto";
и добавим фигурную скобку, чтобы закрыть эту опцию.
option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_swagger) = {
info: {
title: "A Bit of Everything";
version: "1.0";
contact: {
name: "gRPC-Gateway project";
url: "https://github.com/grpc-ecosystem/grpc-gateway";
email: "[email protected]";
};
license: {
name: "BSD 3-Clause License";
url: "https://github.com/grpc-ecosystem/grpc-gateway/blob/master/LICENSE.txt";
};
extensions: {
key: "x-something-something";
value {
string_value: "yadda";
}
}
};
}
Итак, теперь мы можем изменить название на «SimpleBank API», давайте
пока оставим версию 1.0. Я изменю контакт для связи на Tech School
,
URL-адресом будет GitHub страница Tech School, а электронную почту поменяю
на [email protected]
. Также удалим информацию о лицензии и
расширениях, поскольку в данный момент она нам не нужна. И, наконец, мы
должны добавить точку с запятой в конце опции.
option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_swagger) = {
info: {
title: "SimpleBank API";
version: "1.0";
contact: {
name: "Tech School";
url: "https://github.com/techschool";
email: "[email protected]";
};
};
};
После этого вы увидите, что команда импорта аннотаций приводит к некоторым
ошибкам. Они связаны с тем, что этого proto
файла не существует в
нашем проекте. Чтобы добавить его, мы должны вернуться на страницу
GitHub grpc-gateway
и войти в эту папку protoc-gen-openapiv2
, а затем в options
. Здесь мы
увидим файл annotations.proto
и файл openapiv2.proto
, которые нужно
скопировать в наш проект. Итак, я собираюсь скопировать URL-адрес для
клонирования этого репозитория, затем открыть терминал и запустить следующую
Git команду, чтобы клонировать репозиторий на мою локальную машину.
git clone [email protected]:grpc-ecosystem/grpc-gateway.git
Теперь давайте перейдём в проект grpc-gateway
cd grpc-gateway
зайдём в папку protoc-gen-openapiv2
,
cd protoc-gen-openapiv2
и, наконец, в папку options
.
cd options
Теперь, прежде чем копировать proto
файлы, нам нужно подготовить точно
такую же структуру папок для нашего проекта.
Поэтому в проекте simplebank
, я выполню команду mkdir -p proto/
, дописав
к ней путь к папке options
.
mkdir -p proto/protoc-gen-openapiv2/options
Итак, теперь вернитесь к проекту grpc-gateway
. Здесь ниже показан
список proto
файлов, которые мы должны скопировать.
ls -l
total 368
-rw-r--r-- 1 quangpham staff 1067 Apr 10 16:44 BUILD.bazel
-rw-r--r-- 1 quangpham staff 13802 Apr 10 16:44 annotations.pb.go
-rw-r--r-- 1 quangpham staff 1760 Apr 10 16:44 annotations.proto
-rw-r--r-- 1 quangpham staff 798 Apr 10 16:44 annotations.swagger.json
-rw-r--r-- 1 quangpham staff 124863 Apr 10 16:44 openapiv2.pb.go
-rw-r--r-- 1 quangpham staff 25439 Apr 10 16:44 openapiv2.proto
-rw-r--r-- 1 quangpham staff 796 Apr 10 16:44 openapiv2.swagger.json
Давайте выполним
cp *.proto
чтобы скопировать все proto
файлы в папку options
внутри нашего проекта
simplebank
.
cp *.proto ~/Projects/techschool/simplebank/proto/protoc-gen-openapiv2/options
И теперь, когда мы вернёмся в Visual Studio Code, то увидим, что ошибка
пропала. Это означает, что компилятор нашел необходимые ему proto
файлы в
папке protoc-gen-openapiv2/options
. Превосходно!
Теперь давайте выполним
make proto
в терминале, чтобы повторно сгенерировать документацию.
На этот раз в файле simple_bank.swagger.json
мы видим, что в информационном
разделе содержатся вся данные, которые мы указали в определении protobuf
.
{
"swagger": "2.0",
"info": {
"title": "SimpleBank API",
"version": "1.0",
"contact": {
"name": "Tech School",
"url": "https://github.com/techschool",
"email": "[email protected]"
}
}
}
Довольно круто, не так ли?
Теперь, чтобы обновить документацию на Swagger Hub, нам просто нужно скопировать новое содержимое этого файла, а затем вернуться сюда,
удалить устаревший YAML файл и вставить новый JSON. Swagger Hub спросит, хотите ли вы конвертировать JSON в YAML или нет. Нажимаем «ОК», чтобы согласиться.
Вуаля и документация обновлена. Я нажму Save
, чтобы сохранить её.
А затем щелкните по этой кнопке View Documentation
чтобы открыть версию документации только для чтения.
На этот раз мы увидим некоторую информацию о метаданных в верхней части
страницы, именно ту, которую указали в определении protobuf
.
Использовать Swagger Hub очень просто и удобно, мы можем создать и обновить страницу документации с помощью нескольких щелчков мышкой. Однако он предлагает вам только 14-дневный пробный период с полным функционалом, когда вы можете создавать API документацию с ограниченным доступом. По истечении 14 дневного периода вам придется либо заплатить, либо сделать свой API общедоступным.
Кроме того, необходимость каждый раз копировать JSON вручную, как мы это делали, раздражает. Существует ли альтернатива? Можем ли мы также автоматически обновлять веб-страницу документации? А не только содержимое JSON? На самом деле — да!
К счастью уже существует проект под названием swagger-ui.
Это веб-приложение на основе React, помогающее нам создать такую же
красивую страницу документации, как ту, которую мы видим на Swagger Hub.
Но оно совершенно бесплатно, и все, что нам нужно сделать, это встроить его
в наш внутренний веб-сервер. На самом деле существует три варианта: вы можете
использовать полный пакет swagger-ui
для традиционного одностраничного
приложения, или просто использовать swagger-ui-dist
для формирования и
выдачи пользовательского интерфейса Swagger сервером или использовать
пакет swagger-ui-react
, если вы хотите встроить его на свою React
веб-страницу.
В нашем случае мы просто хотим, чтобы сервер формировал и выдавал
пользовательский интерфейс Swagger, поэтому давайте выберем второй
вариант: использовать скомпилированный пакет dist
.
Во-первых, я собираюсь клонировать этот репозиторий на свою локальную машину.
git clone [email protected]:swagger-api/swagger-ui.git
Затем перейдите в папку swagger-ui
. Если мы заглянем в папку dist
, то
увидим, что в ней есть все необходимые компоненты, такие как HTML, CSS и
Javascript код для страницы документации. Поэтому я скопирую их в наш проект
и сохраню в папке swagger
, туда же, где находится сгенерированный JSON
файл.
cp -r dist/* ~/Projects/techschool/simplebank/doc/swagger
Хорошо, теперь вернемся в Visual Studio Code. Мы видим, появились новые файлы
в этой папке swagger
. Самый важный, на который нам нужно обратить
внимание, это файл swagger-initializer.js
.
В этом файле вы найдете URL поле, которое указывает на файл swagger.json
.
window.onload = function() {
//<editor-fold desc="Changeable Configuration Block">
// the following lines will be replaced by docker/configurator, when it runs in a docker-container
window.ui = SwaggerUIBundle({
url: "https://petstore.swagger.io/v2/swagger.json",
dom_id: '#swagger-ui',
deepLinking: true,
presets: [
SwaggerUIBundle.presets.apis,
SwaggerUIStandalonePreset
],
plugins: [
SwaggerUIBundle.plugins.DownloadUrl
],
layout: "StandaloneLayout"
});
//</editor-fold>
};
Как вы могли догадаться, мы должны изменить этот файл на наш собственный
Swagger JSON-файл, который ранее был сгенерирован protoc
.
window.onload = function() {
//<editor-fold desc="Changeable Configuration Block">
// the following lines will be replaced by docker/configurator, when it runs in a docker-container
window.ui = SwaggerUIBundle({
url: "simple_bank.swagger.json",
dom_id: '#swagger-ui',
deepLinking: true,
presets: [
SwaggerUIBundle.presets.apis,
SwaggerUIStandalonePreset
],
plugins: [
SwaggerUIBundle.plugins.DownloadUrl
],
layout: "StandaloneLayout"
});
//</editor-fold>
};
Теперь следующий шаг, нам нужно будет немного обновить код нашего
сервера, чтобы вся эта папка swagger
отдавалась как статический ресурс.
Итак, давайте откроем файл main.go
. Мы добавим новый код в функцию
runGatewayServer()
сразу после создания HTTP-мультиплексора и регистрации
grpcMux
. Я создам новый файловый сервер, вызвав функцию http.FileServer()
и передав объект http.Dir()
, потому что мы хотим отдавать весь каталог.
Путь к этому каталогу должен быть равен ./doc/swagger
. Теперь мы создадим
новый HTTP обработчик для этого файлового сервера. В этом случае я хочу, чтобы
файлы документации отдавались по пути, который начинается с префикса
"swagger". Но из-за этого мне придется обрезать префикс URL-адреса,
чтобы удалить этот префикс "swagger", прежде чем передавать запрос на
статический файловый сервер. Поэтому тут используется вызов функции
http.StripPrefix()
.
Итак, попробуем запустить сервер в терминале.
make server
И откроем браузер и перейдём на страницу localhost:8080/swagger.
К сожалению, у нас возникла ошибка с сообщением: "Not Found" («Страница не найдена»). Вернемся к коду, чтобы увидеть, что не так.
А, кажется, я нашёл причину! А Вы? Вы можете приостановить видео, чтобы попытаться найти её самостоятельно, если хотите. Итак, вам удалось обнаружить мою ошибку?
На самом деле, здесь мы должны зарегистрировать новый маршрут с
мультиплексором, который мы создали выше, вместо использования HTTP
ServeMux
по умолчанию. Поэтому я остановлю текущий сервер и изменю тут
http
на mux
.
mux.Handle("/swagger/", http.StripPrefix("/swagger/", fs))
Затем сохраните файл и вернитесь в терминал, чтобы перезапустить сервер.
make server
На этот раз, когда мы обновим страницу, мы увидим красивую страницу документации Swagger, точно такую же, какую мы видели на Swagger Hub раньше.
Но на этот раз она доступна совершенно бесплатно, так как мы отдаём её сами, непосредственно с нашего внутреннего Golang сервера. Довольно круто, не правда ли?
И на этом мы завершаем сегодняшнюю лекцию о Swagger документации. Я надеюсь, что вы узнали из неё что-то полезное и интересное для себя.
Большое спасибо за время, потраченное на чтение, желаю Вам получать удовольствие от обучения и до встречи на следующей лекции!