本章,讲解了如何使用Gin(Gin Web Framework) 开发restful风格的webService Api
首先,你要确定自己已经十分了解Go基础知识,以及如何驾驭它,否则,请移步 go 基础知识章节
Gin简化了构建web应用/web服务相关的代码工作,在本章,您将使用Gin路由请求、检索请求详细信息并将JSON封装到响应中.
在本章中,你将创建具有两个端点的Restful Api,示例项目将是一个关于古典爵士乐记录的数据存储库。
本章包含下面小节:
设计API端点.
创建一个代码文件夹
创建数据
编写一个返回所有项目的处理器
编写一个新增项目的处理器
编写一个特定项目的处理器
你可以在google云shell中完成这个教程的所有内容.点击这里<别点了,被墙了>
先决条件
- 安装Go1.16以上版本
- 一个代码编辑器
- 命令行终端
- curl工具 Linux和Max已经内置此工具,win10 17063版本亦内置,更早版本的windows,需要你自己去下载
设计API端点
设计一个API,它能提供记录在黑胶唱片上的古典音乐,所以,你需要提供一个端点,用来让客户端获取或新增用户专辑
开发设计API,通常是从设计端点开始,如果这个端点很容易理解,使用你api的人,将更容易成功调用
你将创建下面两个端点:
/albums
- GET ---- 获取专辑列表,以JSON返回
- POST ---- 添加新专辑,请求数据格式为JSON
/albums/:id
- GET ---- 通过ID获取专辑,专辑数据以JSON返回
创建代码文件夹
1.2. 分别是在liunx 和windows下如何创建文件夹
3.创建一个可以管理依赖的模块
运行 go mod init 命令,将模块路径传给他
$ go mod init example/web-service-gin
go: creating new go.mod: module example/web-service-gi
此命令创建一个go.mod文件,您添加的依赖项将在其中列出以进行跟踪
接下来,你将设计一个数据结构,用来处理数据
创建数据
为保持本教程间接,数据被存到内存里,更常用的做法是,API与数据库交互
注意,把数据放在内存中的意思是,专辑合集会在你停止服务后丢失.重启服务后,只能重新创建
编写代码
在web-service路径下,创建一个名为main.go的文件,
在mian.go文件内,敲入下面一行代码,用来声明包
package main
对于一个单程序(不同于库) 总是在main包中.
在包声明下方,把album结构体声明拷贝过去,这用来在内存中保存专辑数据
结构体中的标记 json:"artist" 是标识字段名的,用于结构体内容被序列化成json.如果没有使用标记,那将使用结构体中的大写字母作为json字段名--这种风格通常不常见
// album represents data about a record album. type album struct { ID string `json:"id"` Title string `json:"title"` Artist string `json:"artist"` Price float64 `json:"price"` }
4.在结构体声明的下方,粘贴下面album结构体的数据切片,你的梦从这里开始
// albums slice to seed record album data. 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}, }
接下来,编写第一个端点代码
写一个返回所有项目的处理器
当客户端发起了一个请求GET/albums, 这回把所有专辑信息作为json返回
为了达到这个目标,你需要做下面的:
- 准备响应逻辑
- 将请求路径映射到逻辑的代码
请注意,这与它们在运行时的执行方式相反,但您首先添加依赖项,然后添加依赖于它们的代码
编写代码
粘贴下面代码,获取专辑清单
getAlbums函数创建了一个从album结构体切片中的 json, 然后把这个json插入响应中
// getAlbums responds with the list of all albums as JSON. func getAlbums(c *gin.Context) { c.IndentedJSON(http.StatusOK, albums) }
在这片代码:
getAlbums函数有一个gin.Context参数,注意,你可以任意命名,----Go和Gin都没有规定函数名称
在Gin中gin.Context是最常用.它承载了请求的细节,验证和序列化json,等等(尽管名称相似,但这个和Go内置的context包完全不同)
调用Context.IndextedJSON来把结构体序列化为JSON,然后插入响应
这个函数的第一个参数是你想发送给客户端的HTTP状态码,此处,你传入了net/http包的StatusOK常量,这标识了 200 ok
注意,你也可以使用Context.JSON代替Context.IndentedJSON来发送更加复杂的JSON,实际上,在调试阶段Indented形式更容易使用,并且大小差异通常很小。
2.在main.go文件的顶部附近,粘贴下面代码,将处理函数分配一个端点
这里把getAlbums处理函数和/albums端点路径做了关联
func main() { router := gin.Default() router.GET("/albums", getAlbums) router.Run("localhost:8080") }
在本片代码:
- 使用Default初始化Gin路由
- 使用GET函数来把Http Get方法 和/albums路径处理函数关联,
注意,你要传入的是,getAlbums函数的名称,这和获取函数结果有很大不同,当你要获取函数结果,你要这么做getAlbums()(注意括号) - 使用Run函数,把路由附加到http.server上,并启动服务
3.在main.go的顶部附件,导入你需要使用的包
package main import ( "net/http" "github.com/gin-gonic/gin" )
4.保存文件
运行代码
1.开始作为依赖项跟踪Gin模块。
在命令行中,使用go-get添加github.com/gin-gonic/gin模块作为模块的依赖项。使用点参数表示“获取当前目录中代码的依赖项”
$ go get .
go get: added github.com/gin-gonic/gin v1.7.2
Go解析并下载此依赖项,以满足您在上一步中添加的导入声明
2.在main.go所在目录运行,代码,使用 点 参数,意思是在当前目录下运行代码
$ go run .
一旦代码运行,你就拥有了一个Http服务,这样你就能发送请求
3.另外开一个新的命令行窗口,使用curl能将请求发送给运行中的we服务
$ 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请求
编写处理新增项目
当客户端发起了/albums 的post请求,你想在请求body中添加一个专辑描述到现存的专辑数据中
为了达到这个目的:
将新专辑添加到现存清单中
将post请求和代码路由在一起
编写代码
1.添加新专辑到专辑清单中的代码:
在import段之后,粘贴下面代码(推荐粘贴在文件末尾,不过Go是不强制你声明函数的出现位置)
// postAlbums adds an album from JSON received in the request body. func postAlbums(c *gin.Context) { var newAlbum album // Call BindJSON to bind the received JSON to // newAlbum. if err := c.BindJSON(&newAlbum); err != nil { return } // Add the new album to the slice. albums = append(albums, newAlbum) c.IndentedJSON(http.StatusCreated, newAlbum) }
本代码:
- 使用Context.BindJSON来构建请求体到newAlbum.
- 将初始化后的album结构体传给alums切片
- 向响应中添加201状态代码,表示您添加的相册的JSON。
2.修改main函数,以便包含router.post函数,
func main() { router := gin.Default() router.GET("/albums", getAlbums) router.POST("/albums", postAlbums) router.Run("localhost:8080") }
将/albums路径上的POST方法与postAlbums函数相关联。
这片代码:
- 使用Gin,您可以将处理程序与HTTP方法和路径组合相关联。通过这种方式,您可以根据客户端使用的方法将发送到单个路径的请求单独路由。
运行代码
1.如果上一步的服务,没有停止,请先停止
2.在main.go所在的路径运行,下面的命令
$ go run .
3.另开一个命令行窗口,使用curl向你的web服务发送请求
$ 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}'
这个命令应该返回了json和头文件
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 }
4.与上一节一样,使用curl检索完整的专辑列表,您可以使用该列表确认新专辑已添加。
$ curl http://localhost:8080/albums --header "Content-Type: application/json" --request "GET"
应该会显示如下
[ { "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 } ]
接下来,你将实现使用GET获得特定的项目
返回指定项目
1.在postAlbums下面添加下面代码,检索指定项目
getAlbumByID函数将提取请求路径的ID,然后匹配定位到的专辑
// getAlbumByID locates the album whose ID value matches the id // parameter sent by the client, then returns that album as a response. func getAlbumByID(c *gin.Context) { id := c.Param("id") // Loop over the list of albums, looking for // an album whose ID value matches the parameter. 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字段值与ID参数值匹配的,如果找到了,则将该专辑结构序列化为JSON,并使用200 OK HTTP代码作为响应返回
如前文所述,实际服务可能会使用数据库查询来执行此查找 - 如果专辑不存在返回HTTP 404错误与HTTP.statusnotfound。
2.最后,修改main函数,以便让他包含router.GET,
func main() { router := gin.Default() router.GET("/albums", getAlbums) router.GET("/albums/:id", getAlbumByID) router.POST("/albums", postAlbums) router.Run("localhost:8080") }
本代码中:
- 将/albums/:id路径与getAlbumByID函数相关联。在Gin中,路径中项目前面的冒号表示该项目是路径参数。
运行代码:
1.如果服务一直运行,停止他
2.在main.go 运行命令行
$ go run .
3.使用curl请求web服务
$ curl http://localhost:8080/albums/2
该命令应显示您所使用ID的专辑JSON。如果找不到专辑,您将获得带有错误消息的JSON。
{ "id": "2", "title": "Jeru", "artist": "Gerry Mulligan", "price": 17.99 }
总结
恭喜,刚刚使用Go和Gin编写了一个简单的RESTful web服务。
建议下一个主题:
如果你是Go的新手,你将在Go本质论和如何写go代码 能发现最好的教程
Go之旅 是最好的基础知识
更多关于Gin,看看Gin web框架包文档 或者 Gin Web框架文档
完整代码
package main import ( "net/http" "github.com/gin-gonic/gin" ) // album represents data about a record album. type album struct { ID string `json:"id"` Title string `json:"title"` Artist string `json:"artist"` Price float64 `json:"price"` } // albums slice to seed record album data. 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 responds with the list of all albums as JSON. func getAlbums(c *gin.Context) { c.IndentedJSON(http.StatusOK, albums) } // postAlbums adds an album from JSON received in the request body. func postAlbums(c *gin.Context) { var newAlbum album // Call BindJSON to bind the received JSON to // newAlbum. if err := c.BindJSON(&newAlbum); err != nil { return } // Add the new album to the slice. albums = append(albums, newAlbum) c.IndentedJSON(http.StatusCreated, newAlbum) } // getAlbumByID locates the album whose ID value matches the id // parameter sent by the client, then returns that album as a response. func getAlbumByID(c *gin.Context) { id := c.Param("id") // Loop through the list of albums, looking for // an album whose ID value matches the parameter. for _, a := range albums { if a.ID == id { c.IndentedJSON(http.StatusOK, a) return } } c.IndentedJSON(http.StatusNotFound, gin.H{"message": "album not found"}) }