Руководство: Разработка RESTful API с Go и Gin
Это руководство знакомит с основами написания RESTful веб-сервиса API с помощью Go и веб-фреймворка Gin (Gin).
Вы получите максимальную пользу от этого руководства, если имеете базовое знакомство с Go и его инструментами. Если это ваше первое знакомство с Go, пожалуйста, посмотрите Руководство: Начало работы с Go для быстрого введения.
Gin упрощает многие задачи программирования, связанные с созданием веб-приложений, включая веб-сервисы. В этом руководстве вы будете использовать Gin для маршрутизации запросов, получения деталей запросов и маршализации JSON для ответов.
В этом руководстве вы создадите сервер RESTful API с двумя конечными точками. Ваш пример проекта будет репозиторием данных о винтажных джазовых записях.
Руководство включает следующие разделы:
- Проектирование конечных точек API.
- Создание папки для вашего кода.
- Создание данных.
- Написание обработчика для возврата всех элементов.
- Написание обработчика для добавления нового элемента.
- Написание обработчика для возврата конкретного элемента.
Примечание: Другие руководства смотрите в разделе Руководства.
Чтобы попробовать это как интерактивное руководство, которое вы выполняете в Google Cloud Shell, нажмите кнопку ниже.
Предварительные требования
- Установленный Go версии 1.16 или новее. Инструкции по установке см. в разделе Установка Go.
- Инструмент для редактирования кода. Подойдет любой текстовый редактор, который у вас есть.
- Командный терминал. Go хорошо работает с любым терминалом на Linux и Mac, а также в PowerShell или cmd в Windows.
- Инструмент curl. На Linux и Mac он уже должен быть установлен. В Windows он включен в Windows 10 Insider build 17063 и новее. Для более ранних версий Windows вам может потребоваться установить его. Подробнее см. в разделе Tar и Curl появились в Windows.
Проектирование конечных точек API
Вы создадите API, который предоставляет доступ к магазину, продающему винтажные записи на виниле. Поэтому вам нужно будет предоставить конечные точки, через которые клиент сможет получать и добавлять альбомы для пользователей.
При разработке API вы обычно начинаете с проектирования конечных точек. Ваши пользователи API будут более успешны, если конечные точки легко понять.
Вот конечные точки, которые вы создадите в этом руководстве.
/albums
GET– Получить список всех альбомов, возвращаемый как JSON.POST– Добавить новый альбом из данных запроса, отправленных как JSON.
/albums/:id
GET– Получить альбом по его ID, возвращая данные альбома как JSON.
Далее вы создадите папку для вашего кода.
Создание папки для вашего кода
Для начала создайте проект для кода, который вы будете писать.
-
Откройте командную строку и перейдите в ваш домашний каталог.
На Linux или Mac:
<code>$ cd </code>
На Windows:
<code>C:\> cd %HOMEPATH% </code>
-
Используя командную строку, создайте каталог для вашего кода с именем web-service-gin.
<code>$ mkdir web-service-gin $ cd web-service-gin </code>
-
Создайте модуль, в котором вы сможете управлять зависимостями.
Выполните команду
go mod init, указав путь модуля, в котором будет находиться ваш код.<code>$ go mod init example/web-service-gin go: creating new go.mod: module example/web-service-gin </code>
Эта команда создает файл go.mod, в котором будут перечислены добавляемые вами зависимости для отслеживания. Подробнее о назначении имени модуля с помощью пути модуля см. в разделе Управление зависимостями.
Далее вы спроектируете структуры данных для обработки данных.
Создание данных
Для простоты руководства вы будете хранить данные в памяти. Более типичный API взаимодействовал бы с базой данных.
Обратите внимание, что хранение данных в памяти означает, что набор альбомов будет потерян каждый раз, когда вы останавливаете сервер, а затем пересоздается при запуске.
Напишите код
-
Используя текстовый редактор, создайте файл с именем main.go в каталоге web-service. Вы будете писать свой код Go в этом файле.
-
В main.go в верхней части файла вставьте следующее объявление пакета.
<code>package main </code>
Автономная программа (в отличие от библиотеки) всегда находится в пакете
main. -
Под объявлением пакета вставьте следующее объявление структуры
album. Вы будете использовать это для хранения данных альбома в памяти.Теги структуры, такие как
json:"artist", указывают, каким должно быть имя поля, когда содержимое структуры сериализуется в JSON. Без них JSON использовал бы имена полей структуры с заглавными буквами — стиль, не столь распространенный в JSON.<code>// album представляет данные о записи альбома. type album struct { ID string `json:"id"` Title string `json:"title"` Artist string `json:"artist"` Price float64 `json:"price"` } </code> -
Под только что добавленным объявлением структуры вставьте следующий слайс структур
album, содержащий данные, которые вы будете использовать для начала.<code>// слайс albums для заполнения данных о записях альбомов. var albums = []album{ {ID: "1", Title: "Blue Train", Artist: "John Coltrane", Price: 56.99}, {ID: "2", Title: "Jeru", Artist: "Gerry Mulligan", Price: 17.99}, {ID: "3", Title: "Sarah Vaughan and Clifford Brown", Artist: "Sarah Vaughan", Price: 39.99}, } </code>
Далее вы напишете код для реализации вашей первой конечной точки.
Написание обработчика для возврата всех элементов
Когда клиент делает запрос GET /albums, вы хотите вернуть все
альбомы как JSON.
Для этого вы напишете следующее:
- Логику для подготовки ответа
- Код для сопоставления пути запроса с вашей логикой
Обратите внимание, что это обратно тому, как они будут выполняться во время выполнения, но вы сначала добавляете зависимости, а затем код, который от них зависит.
Напишите код
-
Под кодом структуры, который вы добавили в предыдущем разделе, вставьте следующий код для получения списка альбомов.
Эта функция
getAlbumsсоздает JSON из слайса структурalbum, записывая JSON в ответ.<code>// getAlbums отвечает списком всех альбомов в виде JSON. func getAlbums(c *gin.Context) { c.IndentedJSON(http.StatusOK, albums) } </code>В этом коде вы:
-
Пишете функцию
getAlbums, которая принимает параметрgin.Context. Обратите внимание, что вы могли бы дать этой функции любое имя — ни Gin, ни Go не требуют определенного формата имени функции.gin.Context— самая важная часть Gin. Она несет детали запроса, проверяет и сериализует JSON и многое другое. (Несмотря на похожее имя, это отличается от встроенного в Go пакетаcontext.) -
Вызываете
Context.IndentedJSONдля сериализации структуры в JSON и добавления её в ответ.Первый аргумент функции — это HTTP-код состояния, который вы хотите отправить клиенту. Здесь вы передаете константу
StatusOKиз пакетаnet/http, чтобы указать200 OK.Обратите внимание, что вы можете заменить
Context.IndentedJSONвызовомContext.JSONдля отправки более компактного JSON. На практике форма с отступами гораздо проще для работы при отладке, а разница в размере обычно невелика.
-
-
В верхней части main.go, сразу под объявлением слайса
albums, вставьте код ниже, чтобы назначить функцию-обработчик пути конечной точки.Это устанавливает ассоциацию, в которой
getAlbumsобрабатывает запросы к пути конечной точки/albums.<code>func main() { router := gin.Default() router.GET("/albums", getAlbums) router.Run("localhost:8080") } </code>В этом коде вы:
-
Инициализируете маршрутизатор Gin, используя
Default. -
Используете функцию
GETдля связывания HTTP-методаGETи пути/albumsс функцией-обработчиком.Обратите внимание, что вы передаете имя функции
getAlbums. Это отличается от передачи результата функции, что вы бы сделали, передавgetAlbums()(обратите внимание на скобки). -
Используете функцию
Runдля присоединения маршрутизатора кhttp.Serverи запуска сервера.
-
-
В верхней части main.go, сразу под объявлением пакета, импортируйте пакеты, которые вам понадобятся для поддержки кода, который вы только что написали.
Первые строки кода должны выглядеть так:
<code>package main import ( "net/http" "github.com/gin-gonic/gin" ) </code>
-
Сохраните main.go.
Запустите код
-
Начните отслеживание модуля Gin как зависимости.
В командной строке используйте
go getдля добавления модуля github.com/gin-gonic/gin как зависимости для вашего модуля. Используйте аргумент точка, чтобы означать "получить зависимости для кода в текущем каталоге."<code>$ go get . go get: added github.com/gin-gonic/gin v1.7.2 </code>
Go разрешил и загрузил эту зависимость для удовлетворения объявления
import, которое вы добавили на предыдущем шаге. -
Из командной строки в каталоге, содержащем main.go, запустите код. Используйте аргумент точка, чтобы означать "запустить код в текущем каталоге."
<code>$ go run . </code>
Как только код запущен, у вас есть работающий HTTP-сервер, которому вы можете отправлять запросы.
-
Из нового окна командной строки используйте
curlдля выполнения запроса к вашему работающему веб-сервису.<code>$ curl http://localhost:8080/albums </code>
Команда должна отобразить данные, которыми вы заполнили сервис.
<code>[ { "id": "1", "title": "Blue Train", "artist": "John Coltrane", "price": 56.99 }, { "id": "2", "title": "Jeru", "artist": "Gerry Mulligan", "price": 17.99 }, { "id": "3", "title": "Sarah Vaughan and Clifford Brown", "artist": "Sarah Vaughan", "price": 39.99 } ] </code>
Вы запустили API! В следующем разделе вы создадите еще одну конечную точку с
кодом для обработки запроса POST для добавления элемента.
Написание обработчика для добавления нового элемента
Когда клиент делает запрос POST к /albums, вы хотите добавить альбом,
описанный в теле запроса, к существующим данным альбомов.
Для этого вы напишете следующее:
- Логику для добавления нового альбома в существующий список.
- Немного кода для маршрутизации запроса
POSTк вашей логике.
Напишите код
-
Добавьте код для добавления данных альбомов в список альбомов.
Где-то после операторов
importвставьте следующий код. (Конец файла — хорошее место для этого кода, но Go не требует порядка, в котором вы объявляете функции.)<code>// postAlbums добавляет альбом из JSON, полученного в теле запроса. func postAlbums(c *gin.Context) { var newAlbum album // Вызовите BindJSON для связывания полученного JSON с // newAlbum. if err := c.BindJSON(&newAlbum); err != nil { return } // Добавьте новый альбом в слайс. albums = append(albums, newAlbum) c.IndentedJSON(http.StatusCreated, newAlbum) } </code>В этом коде вы:
- Используете
Context.BindJSONдля связывания тела запроса сnewAlbum. - Добавляете структуру
album, инициализированную из JSON, в слайсalbums. - Добавляете код состояния
201в ответ вместе с JSON, представляющим добавленный вами альбом.
- Используете
-
Измените вашу функцию
mainтак, чтобы она включала функциюrouter.POST, как показано ниже.<code>func main() { router := gin.Default() router.GET("/albums", getAlbums) router.POST("/albums", postAlbums) router.Run("localhost:8080") } </code>В этом коде вы:
-
Связываете метод
POSTпо пути/albumsс функциейpostAlbums.С Gin вы можете связать обработчик с комбинацией HTTP-метода и пути. Таким образом, вы можете отдельно маршрутизировать запросы, отправленные на один путь, на основе метода, который использует клиент.
-
Запустите код
-
Если сервер все еще работает с последнего раздела, остановите его.
-
Из командной строки в каталоге, содержащем main.go, запустите код.
<code>$ go run . </code>
-
Из другого окна командной строки используйте
curlдля выполнения запроса к вашему работающему веб-сервису.<code>$ curl http://localhost:8080/albums \ --include \ --header "Content-Type: application/json" \ --request "POST" \ --data '{"id": "4","title": "The Modern Sound of Betty Carter","artist": "Betty Carter","price": 49.99}' </code>Команда должна отобразить заголовки и JSON для добавленного альбома.
<code>HTTP/1.1 201 Created Content-Type: application/json; charset=utf-8 Date: Wed, 02 Jun 2021 00:34:12 GMT Content-Length: 116 { "id": "4", "title": "The Modern Sound of Betty Carter", "artist": "Betty Carter", "price": 49.99 } </code> -
Как и в предыдущем разделе, используйте
curlдля получения полного списка альбомов, который вы можете использовать для подтверждения добавления нового альбома.<code>$ curl http://localhost:8080/albums \ --header "Content-Type: application/json" \ --request "GET" </code>
Команда должна отобразить список альбомов.
<code>[ { "id": "1", "title": "Blue Train", "artist": "John Coltrane", "price": 56.99 }, { "id": "2", "title": "Jeru", "artist": "Gerry Mulligan", "price": 17.99 }, { "id": "3", "title": "Sarah Vaughan and Clifford Brown", "artist": "Sarah Vaughan", "price": 39.99 }, { "id": "4", "title": "The Modern Sound of Betty Carter", "artist": "Betty Carter", "price": 49.99 } ] </code>
В следующем разделе вы добавите код для обработки запроса GET для конкретного элемента.
Написание обработчика для возврата конкретного элемента
Когда клиент делает запрос GET /albums/[id], вы хотите вернуть
альбом, чей ID соответствует параметру пути id.
Для этого вы:
- Добавите логику для получения запрошенного альбома.
- Сопоставите путь с логикой.
Напишите код
-
Под функцией
postAlbums, которую вы добавили в предыдущем разделе, вставьте следующий код для получения конкретного альбома.Эта функция
getAlbumByIDизвлечет ID из пути запроса, а затем найдет альбом, который соответствует.<code>// getAlbumByID находит альбом, значение ID которого совпадает с параметром id, // отправленным клиентом, затем возвращает этот альбом в качестве ответа. func getAlbumByID(c *gin.Context) { id := c.Param("id") // Цикл по списку альбомов, ищем // альбом, значение ID которого совпадает с параметром. for _, a := range albums { if a.ID == id { c.IndentedJSON(http.StatusOK, a) return } } c.IndentedJSON(http.StatusNotFound, gin.H{"message": "album not found"}) } </code>В этом коде вы:
-
Используете
Context.Paramдля получения параметра путиidиз URL. Когда вы сопоставите этот обработчик с путем, вы включите заполнитель для параметра в путь. -
Проходите циклом по структурам
albumв слайсе, ищем ту, значение поляIDкоторой соответствует значению параметраid. Если она найдена, вы сериализуете эту структуруalbumв JSON и возвращаете её в качестве ответа с HTTP-кодом200 OK.Как упоминалось выше, реальный сервис, вероятно, использовал бы запрос к базе данных для выполнения этого поиска.
-
Возвращаете HTTP-ошибку
404сhttp.StatusNotFound, если альбом не найден.
-
-
Наконец, измените вашу функцию
mainтак, чтобы она включала новый вызовrouter.GET, где путь теперь/albums/:id, как показано в следующем примере.<code>func main() { router := gin.Default() router.GET("/albums", getAlbums) router.GET("/albums/:id", getAlbumByID) router.POST("/albums", postAlbums) router.Run("localhost:8080") } </code>В этом коде вы:
- Связываете путь
/albums/:idс функциейgetAlbumByID. В Gin двоеточие перед элементом в пути означает, что этот элемент является параметром пути.
- Связываете путь
Запустите код
-
Если сервер все еще работает с последнего раздела, остановите его.
-
Из командной строки в каталоге, содержащем main.go, запустите код для запуска сервера.
<code>$ go run . </code>
-
Из другого окна командной строки используйте
curlдля выполнения запроса к вашему работающему веб-сервису.<code>$ curl http://localhost:8080/albums/2 </code>
Команда должна отобразить JSON для альбома, чей ID вы использовали. Если альбом не найден, вы получите JSON с сообщением об ошибке.
<code>{ "id": "2", "title": "Jeru", "artist": "Gerry Mulligan", "price": 17.99 } </code>
Заключение
Поздравляем! Вы только что использовали Go и Gin для написания простого RESTful веб-сервиса.
Рекомендуемые следующие темы:
- Если вы новичок в Go, вы найдете полезные лучшие практики, описанные в Эффективный Go и Как писать код на Go.
- Тур по Go — это отличное пошаговое введение в основы Go.
- Подробнее о Gin см. в документации пакета веб-фреймворка Gin или в документации веб-фреймворка Gin.
Полный код
Этот раздел содержит код для приложения, которое вы создаете с помощью этого руководства.
<code>package main
import (
"net/http"
"github.com/gin-gonic/gin"
)
// album представляет данные о записи альбома.
type album struct {
ID string json:"id"
Title string json:"title"
Artist string json:"artist"
Price float64 json:"price"
}
// слайс albums для заполнения данных о записях альбомов.
var albums = []album{
{ID: "1", Title: "Blue Train", Artist: "John Coltrane", Price: 56.99},
{ID: "2", Title: "Jeru", Artist: "Gerry Mulligan", Price: 17.99},
{ID: "3", Title: "Sarah Vaughan and Clifford Brown", Artist: "Sarah Vaughan", Price: 39.99},
}
func main() {
router := gin.Default()
router.GET("/albums", getAlbums)
router.GET("/albums/:id", getAlbumByID)
router.POST("/albums", postAlbums)
router.Run("localhost:8080")
}
// getAlbums отвечает списком всех альбомов в виде JSON.
func getAlbums(c *gin.Context) {
c.IndentedJSON(http.StatusOK, albums)
}
// postAlbums добавляет альбом из JSON, полученного в теле запроса.
func postAlbums(c *gin.Context) {
var newAlbum album
// Вызовите BindJSON для связывания полученного JSON с
// newAlbum.
if err := c.BindJSON(&newAlbum); err != nil {
return
}
// Добавьте новый альбом в слайс.
albums = append(albums, newAlbum)
c.IndentedJSON(http.StatusCreated, newAlbum)
}
// getAlbumByID находит альбом, значение ID которого совпадает с параметром id,
// отправленным клиентом, затем возвращает этот альбом в качестве ответа.
func getAlbumByID(c *gin.Context) {
id := c.Param("id")
// Цикл по списку альбомов, ищем
// альбом, значение ID которого совпадает с параметром.
for _, a := range albums {
if a.ID == id {
c.IndentedJSON(http.StatusOK, a)
return
}
}
c.IndentedJSON(http.StatusNotFound, gin.H{"message": "album not found"})
}
</code>
