飞哥的海

导航

用Gin开发Restful

本章,讲解了如何使用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"})
}

 

posted on 2021-09-10 17:59  飞哥的海  阅读(839)  评论(0)    收藏  举报