这篇教程介绍基础的使用Go和Gin Web服务框架写一个RESTful web服务API。
如果您对 Go 及其工具有基本的了解,您将充分利用本教程。
Gin简化了很多与构建web应用,包括web服务的代码任务。在这个教程中,你将使用Gin去路由请求,获取请求详情,并编组 JSON 以获取响应。
在这个教程中,你将使用两个断点构建RESTful API。您的示例项目将是有关老式爵士乐唱片的数据存储库。
这个教程包含下面部分:
要尝试将此作为您在 Google Cloud Shell 中完成的交互式教程,请单击下面的按钮。
curl
工具;您将构建一个 API,该 API 可让您访问销售黑胶唱片的商店。因此,您需要提供端点,客户端可以通过这些端点为用户获取和添加相册。
当开发一个API,您通常从设计端点开始。如果端点易于理解,您的 API 用户将获得更大的成功。
这儿是你将在本教程中创建的断点:
/albums
GET请求,获取所有的专辑,作为JSON返回;
POST请求,根据JSON格式的请求数据,添加一个转接;
albums/:id
GET请求,根据它的ID获取一个专辑,以JSON格式返回请求数据。
接下来,你将为你的代码创建一个目录。
开始,为你将写的代码创建一个项目:
打开命令行,进入主目录:
$ cd
$
使用命令行,创建一个叫web-service-gin
的目录
$ mkdir web-service-gin
$ cd web-service-gin/
$
创建一个模块,你能管理管理依赖
运行go mod init
命令,给出你的代码将要在的模块路径。
$ go mod init example/web-service-gin
go: creating new go.mod: module example/web-service-gin
$
这个命令创建了一个go.mod
文件,在这个文件中,你能添加跟踪列表。
接下来,我们将设计数据结构,用于处理数据。
为了保持本教程的东西简单,你将存储数据到内存中。一个非常典型的API将使用数据库进行交互。
注意,存储数据在内存中,意味着在每次停止服务的时候专辑集合会丢失,当你启动它的时候会重新创建它。
使用编辑器,在web-service-gin
目录中创建一个main.go
文件。你将在这个文件中写代码。
进入main.go
文件,粘贴下面的一句声明:
package main
一个独立的程序(与库相反)总是在main
包中。
包下方的声明,粘贴下面的声明作为album
结构。你将使用它在内存中存储专辑数据。
结构的标签像:json:"artist"
指明了当结构的内容被序列化成json的时候,字段的名称是什么。没有它们,JSON将使用结构中的大写字段名称——一种在 JSON 中不常见的样式。
// album 代表一个专辑的数据
type album struct {
ID string `json:"id"`
Title string `json:"title"`
Artist string `json:"artist"`
Price float64 `json:"price"`
}
仅在结构下面添加,粘贴下面的专辑切片结构,包含了你将开始使用的数据
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},
}
接下来,你将写代码实现你第一个端点。
当客户端进行一个/albums
的GET请求时,你以JSON的形式返回所有的专辑。
为了做这个,要进行下面两步:
注意,这与它们在运行时时相反的。但是你首先要添加依赖,并基于依赖进行编码。
在上面部分,你添加下面的代码结构,粘贴下面的代码用于获取专辑列表。
这个getAlbums
函数根据切片列表创建一个JSON,写这个JSON到响应中。
// getAlbums 使用专辑列表作为JSON响应
func getAlbums(c *gin.Context) {
c.IndentedJSON(http.StatusOK, albums)
}
在这个代码中:
写了一个getAlbums
函数,并获取一个gin.Context
参数。注意,你能给这个函数任意到名字,Gin或Go都不需要特定的函数名格式。
gin.Context
是Gin非常重要的部分。它携带了请求详情,验证和序列化JSON,等等。(尽管名字相似,但是它不同于Go的内置上下文包)。
调用c.IndextedJSON
去序列化结构成为JSON,并且添加到响应中。
函数的第一个参数你想发送给客户端的HTTP状态码。这里,你从net/http
包,传递StatusOK
状态,表明是200 OK
。
注意,你能替换Context.IndentedJSON
使用Context.JSON
,去发送一个更严谨的JSON。在实际中,缩进形式在调试时更容易使用,并且大小差异通常很小。
在靠近main.go
的顶部,在albums
切片声明下面,粘贴下面的代码,分配处理函数到端点路径。
这建立起了getAlbums
处理请求到 /albums
端点路径到联系。
func main() {
router := gin.Default()
router.GET("/albums", getAlbums)
router.Run("localhost:8080")
}
在这个代码中:
使用Deault
初始化Gin 路由;
使用GET
函数去关联GET
到HTTP方法和/albums
路径,带有一个处理函数;
注意,你传递getAlbums
函数名。这是不同于传递函数结果,你可以通过传递getAlbums()
。
使用Run
函数去绑定路由到http.Server
,并启动服务。
在main.go
函数的顶部,下面的包声明,导入包,你将需要支持你已经写的代码。
第一行代码看起来像这样:
import (
"net/http"
"github.com/gin-gonic/gin"
)
保存main.go
文件。
从跟踪Gin模块,作为依赖
在命令行,为你的模块使用go get
去添加github.com/gin-gonic/gin
模块作为依赖。使用一个点参数,意味着要为当前的目录代码获取依赖。
$ go get .
go: downloading github.com/gin-gonic/gin v1.8.1
$
Go解决并下来依赖,去满足你在之前步骤中添加的导入声明。
在包含main.go
文件的命令行中,运行代码。使用一个点参数,意思是运行当前目录的代码。
$ go run .
[GIN-debug] [WARNING] Creating an Engine instance with the Logger and Recovery middleware already attached.
[GIN-debug] [WARNING] Running in "debug" mode. Switch to "release" mode in production.
- using env: export GIN_MODE=release
- using code: gin.SetMode(gin.ReleaseMode)
[GIN-debug] GET /albums --> main.getAlbums (3 handlers)
[GIN-debug] [WARNING] You trusted all proxies, this is NOT safe. We recommend you to set a value.
Please check https://pkg.go.dev/github.com/gin-gonic/gin#readme-don-t-trust-all-proxies for details.
[GIN-debug] Listening and serving HTTP on localhost:8080
一旦代码运行,你就开始了一个HTTP服务,你能用来发送请求。
在一个新的命令行窗口,使用curl
去制造一个请求,用于运行你的web服务。
该命令应显示您为服务播种的数据。
$ curl http://localhost:8080/albums
[
{
"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
}
]$
你已经开始了一个API。在下面的部分,你将使用代码创建另一个端点,用于处理POST
请求来添加一个项目。
当客户创造一个POST
的/albums
请求,你想添加在请求体中描述的album到已经存在的albums的数据中。
为了做这件事,你要做下面的事情:
POST
请求到你的逻辑;添加代码用于添加album数据到album列表中;
在import
语句后面到某处,粘贴下面的代码。(文件的结尾是写这些代码的好地方,但是Go不强迫你声明函数代码的顺序)
// postAlbums 添加一个album 从接受的JSON请求体中
func postAlbums (c *gin.Context) {
var newAlbum album
// 调用BindJSON去绑定接受的JSON,变成一个newAlbum
if err := c.BindJSON(&newAlbum); err != nil {
return
}
// 添加新的album到切片中
albums = append(albums, newAlbum)
c.IndentedJSON(http.StatusCreated, newAlbum)
}
在这个代码中:
Context.BindJSON
去绑定请求体成为newAlbum;album
结构到albums切片;201
状态码去响应,同时使用JSON代表你添加的album;改变main
函数,以便于它包含router.POST
函数,像下面:
func main() {
router := gin.Default()
router.GET("/albums", getAlbums)
router.POST("/albums", postAlbums)
router.Run("localhost:8080")
}
在这个代码中:
关联POST
方法到/albums
路径,带有postAlbums
函数;
使用Gin
,你能关联一个带有“方法路径”组合的处理器。在这种方法中,你能分别路由请求,发送到单个路径基于客户端使用的方法。
如果从上一步,服务仍然在运行,停止它。
在main.go
文件的目录下的命令行中,运行下面的代码:
$ go run .
[GIN-debug] [WARNING] Creating an Engine instance with the Logger and Recovery middleware already attached.
[GIN-debug] [WARNING] Running in "debug" mode. Switch to "release" mode in production.
- using env: export GIN_MODE=release
- using code: gin.SetMode(gin.ReleaseMode)
[GIN-debug] GET /albums --> main.getAlbums (3 handlers)
[GIN-debug] POST /albums --> main.postAlbums (3 handlers)
[GIN-debug] [WARNING] You trusted all proxies, this is NOT safe. We recommend you to set a value.
Please check https://pkg.go.dev/github.com/gin-gonic/gin#readme-don-t-trust-all-proxies for details.
[GIN-debug] Listening and serving HTTP on localhost:8080
在另一个命令行窗口,使用curl
去创建一个请求,到一个运行的web服务
$ curl http://localhost:8080/albums \
--include \
--header "Content-Type: application/json" \
--request "POST" \
--data '{"id":"4","title": "晴天", "artist":"周杰伦","price":49.99}'
命令行将展示头信息和用来添加albums的JSON。
$ curl http://localhost:8080/albums --include --header "Content-Type: application/json" --request "POST" --data '{"id":"4","title": "晴天", "artist":"周杰伦","price":49.99}'
HTTP/1.1 201 Created
Content-Type: application/json; charset=utf-8
Date: Sun, 28 Aug 2022 03:33:58 GMT
Content-Length: 87
{
"id": "4",
"title": "晴天",
"artist": "周杰伦",
"price": 49.99
} $
就像上一部分,使用curl
取出所有的albums,能用来验证新的album是否添加进去
命令行将展示所有的album列表。
$ curl http://localhost:8080/albums
[
{
"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": "晴天",
"artist": "周杰伦",
"price": 49.99
}
]$
下一部分,我们将添加代码,用于处理GET
用于特定的项。
当一个客户端制造一个GET请求/albums/[id]
,你想要返回与路径参数ID匹配的album。
为了做这个事情:
在上一部分你添加的postAlbums
函数下面,粘贴下面的代码,用于取回特定的album。
这个getAlbumByID
函数将从请求路径中提炼出ID,然后定位到匹配到album。
// getAlbumByID 定位于客户端发送请求到id匹配的album,然后返回album作为响应
func getAlbumByID(c *gin.Context) {
id := c.Param("id")
// 遍历albums列表,查找与参数ID匹配的album
for _, a := range albums {
if a.ID == id {
c.IndentedJSON(http.StatusOK, a)
return
}
}
c.IndentedJSON(http.StatusNotFound, gin.H{"Message": "album not found"})
}
在这个代码中:
使用Context.Param
从URL路径中提取id路径参数。当你映射请求器到请求路径,你将包含一个占位符,用于路径上的参数。
遍历在切片结构中的album,查看一个与ID字段匹配的项。如果找到,你能序列化album结构成为JSON,并且返回它作为响应,带有200 OK
HTTP码。
就像上面提到的,一个真实的服务使用数据库去做查询。
返回404错误,带有http.Status.NotFound
,如果album没有发现。
最后,改变你的main
函数,以便于它包含一个新的调用rounter.GET
,路径是/albums/:id
,就像下面展示的:
func main() {
router := gin.Default()
router.GET("/albums", getAlbums)
router.POST("/albums", postAlbums)
router.GET("/albums/:id", getAlbumByID)
router.Run("localhost:8080")
}
在这个代码中:
/albums/:id
路径,带有getAlbumByID
函数。在Gin中,项目前的冒号,表明这个项是路径参数。如果从上一步服务仍然在运行,停止它。
在包含main.go
文件的目录的命令行下,运行代码,启动服务:
$ go run .
[GIN-debug] [WARNING] Creating an Engine instance with the Logger and Recovery middleware already attached.
[GIN-debug] [WARNING] Running in "debug" mode. Switch to "release" mode in production.
- using env: export GIN_MODE=release
- using code: gin.SetMode(gin.ReleaseMode)
[GIN-debug] GET /albums --> main.getAlbums (3 handlers)
[GIN-debug] POST /albums --> main.postAlbums (3 handlers)
[GIN-debug] GET /albums/:id --> main.getAlbumByID (3 handlers)
[GIN-debug] [WARNING] You trusted all proxies, this is NOT safe. We recommend you to set a value.
Please check https://pkg.go.dev/github.com/gin-gonic/gin#readme-don-t-trust-all-proxies for details.
[GIN-debug] Listening and serving HTTP on localhost:8080
在一个不同的命令行窗口,使用curl
制造一个请求到你运行的web服务。
curl http://localhost:8080/albums/2
这个命令将展示一个id在使用的album的JSON。如果album没有发现,你将获取一个错误消息。
$ curl http://localhost:8080/albums/2
{
"id": "2",
"title": "Jeru",
"artist": "Gerry Mulligan",
"price": 17.99
}lifeideMacBook-Pro:~ lifei$ curl http://localhost:8080/albums/4
{
"Message": "album not found"
}$