首页 >> 大全

Go语言教程第十集 如何玩转Restful和RPC网络编程

2023-06-23 大全 63 作者:考证青年

21 网络编程:Go 语言如何玩转 API 服务?

从这一讲开始,我将带你学习本专栏的第五模块,在这个模块中,你将学到我们项目中最常用的编码操作,也就是编写 API 和 RPC 服务。在实际开发项目中,你编写的这些服务可以被其他服务使用,这样就组成了微服务的架构;也可以被前端调用,这样就可以前后端分离。

今天我就先来为你介绍什么是 API,以及 Go 语言是如何玩转 API 的。

什么是 API

API 是一套规范,它可以规范我们如何对服务器上的资源进行操作。在了解 API 之前,我先为你介绍下 HTTP ,因为 API 和它是密不可分的。

说起 HTTP ,最常见的就是POST和GET,其实最早在 HTTP 0.9 版本中,只有一个GET方法,该方法是一个幂等方法,用于获取服务器上的资源,也就是我们在浏览器中直接输入网址回车请求的方法。

在 HTTP 1.0 版本中又增加了HEAD和POST方法,其中常用的是 POST 方法,一般用于给服务端提交一个资源,导致服务器的资源发生变化。

随着网络越来越复杂,发现这两个方法是不够用的,就继续新增了方法。所以在 HTTP1.1 版本的时候,一口气增加到了 9 个,新增的方法有 HEAD、、PUT、、TRACE、PATCH 和 。下面我为你一一介绍它们的作用。

GET 方法可请求一个指定资源的表示形式,使用 GET 的请求应该只被用于获取数据。

HEAD 方法用于请求一个与 GET 请求的响应相同的响应,但没有响应体。

POST 方法用于将实体提交到指定的资源,通常导致服务器上的状态变化或副作用。

PUT 方法用于请求有效载荷替换目标资源的所有当前表示。

方法用于删除指定的资源。

方法用于建立一个到由目标资源标识的服务器的隧道。

方法用于描述目标资源的通信选项。

TRACE 方法用于沿着到目标资源的路径执行一个消息环回测试。

PATCH 方法用于对资源应用部分修改。

从以上每个方法的介绍可以看到,HTTP 规范针对每个方法都给出了明确的定义,所以我们使用的时候也要尽可能地遵循这些定义,这样我们在开发中才可以更好地协作。

理解了这些 HTTP 方法,就可以更好地理解 API 规范了,因为 API 规范就是基于这些 HTTP 方法规范我们对服务器资源的操作,同时规范了 URL 的样式和 HTTP Code。

在 API 中,使用的主要是以下五种 HTTP 方法:

GET,表示读取服务器上的资源;

POST,表示在服务器上创建资源;

PUT,表示更新或者替换服务器上的资源;

,表示删除服务器上的资源;

PATCH,表示更新 / 修改资源的一部分。

以上 HTTP 方法在 API 规范中是一个操作,操作的就是服务器的资源,服务器的资源通过特定的 URL 表示。

现在我们通过一些示例让你更好地理解 API,如下所示:

HTTP GET https://www.flysnow.org/users
HTTP GET https://www.flysnow.org/users/123

以上是两个 GET 方法的示例:

下面再看一个 POST 方法的示例,如下所示:

HTTP POST https://www.flysnow.org/users

这个示例表示创建一个用户,通过 POST 方法给服务器提供创建这个用户所需的全部信息。

注意:这里 users 是个复数。

现在你已经知道了如何创建一个用户,那么如果要更新某个特定的用户怎么做呢?其实也非常简单,示例代码如下所示:

HTTP PUT https://www.flysnow.org/users/123

这表示要更新 / 替换 ID 为 123 的这个用户,在更新的时候,会通过 PUT 方法提供更新这个用户需要的全部用户信息。这里 PUT 方法和 POST 方法不太一样的是,从 URL 上看,PUT 方法操作的是单个资源,比如这里 ID 为 123 的用户。

小提示:如果要更新一个用户的部分信息,使用 PATCH 方法更恰当。

看到这里,相信你已经知道了如何删除一个用户,示例代码如下所示:

HTTP DELETE https://www.flysnow.org/users/123

方法的使用和 PUT 方法一样,也是操作单个资源,这里是删除 ID 为 123 的这个用户。

一个简单的 API

相信你已经非常了解什么是 API 了,现在开始,我会带你通过一个使用 实现 API 风格的示例,加深 API 的理解。

Go 语言的一个很大的优势,就是可以很容易地开发出网络后台服务,而且性能快、效率高。在开发后端 HTTP 网络应用服务的时候,我们需要处理很多 HTTP 的请求访问,比如常见的 API 服务,就要处理很多 HTTP 请求,然后把处理的信息返回给使用者。对于这类需求, 提供了内置的 net/http 包帮我们处理这些 HTTP 请求,让我们可以比较方便地开发一个 HTTP 服务。

下面我们来看一个简单的 HTTP 服务的 Go 语言实现,代码如下所示:

ch21/main.go

func main() {http.HandleFunc("/users",handleUsers)http.ListenAndServe(":8080", nil)
}
func handleUsers(w http.ResponseWriter, r *http.Request){fmt.Fprintln(w,"ID:1,Name:张三")fmt.Fprintln(w,"ID:2,Name:李四")fmt.Fprintln(w,"ID:3,Name:王五")
}

这个示例运行后,你在浏览器中输入 :8080/users, 就可以看到如下内容信息:

ID:1,Name:张三
ID:2,Name:李四
ID:3,Name:王五

也就是获取所有的用户信息,但是这并不是一个 API,因为使用者不仅可以通过 HTTP GET 方法获得所有的用户信息,还可以通过 POST、、PUT 等 HTTP 方法获得所有的用户信息,这显然不符合 API 的规范。

现在我对以上示例进行修改,使它符合 API 的规范,修改后的示例代码如下所示:

ch20/main.go

func handleUsers(w http.ResponseWriter, r *http.Request){switch r.Method {case "GET":w.WriteHeader(http.StatusOK)fmt.Fprintln(w,"ID:1,Name:张三")fmt.Fprintln(w,"ID:2,Name:李四")fmt.Fprintln(w,"ID:3,Name:王五")default:w.WriteHeader(http.StatusNotFound)fmt.Fprintln(w,"not found")}
}

这里我只修改了 函数,在该函数中增加了只在使用 GET 方法时,才获得所有用户的信息,其他情况返回 not found。

现在再运行这个示例,会发现只能通过 HTTP GET 方法进行访问了,使用其他方法会提示 not found。

JSON API

在项目中最常见的是使用 JSON 格式传输信息,也就是我们提供的 API 要返回 JSON 内容给使用者。

同样用上面的示例,我把它改造成可以返回 JSON 内容的方式,示例代码如下所示:

ch20/main.go

//数据源,类似MySQL中的数据
var users = []User{{ID: 1,Name: "张三"},{ID: 2,Name: "李四"},{ID: 3,Name: "王五"},
}
func handleUsers(w http.ResponseWriter, r *http.Request){switch r.Method {case "GET":users,err:=json.Marshal(users)if err!=nil {w.WriteHeader(http.StatusInternalServerError)fmt.Fprint(w,"{\"message\": \""+err.Error()+"\"}")}else {w.WriteHeader(http.StatusOK)w.Write(users)}default:w.WriteHeader(http.StatusNotFound)fmt.Fprint(w,"{\"message\": \"not found\"}")}
}
//用户
type User struct {ID intName string
}

从以上代码可以看到,这次的改造主要是新建了一个 User 结构体,并且使用 users 这个切片存储所有的用户,然后在 函数中把它转化为一个 JSON 数组返回。这样,就实现了基于 JSON 数据格式的 API。

运行这个示例,在浏览器中输入 :8080/users,可以看到如下信息:

[{"ID":1,"Name":"张三"},{"ID":2,"Name":"李四"},{"ID":3,"Name":"王五"}]

这已经是 JSON 格式的用户信息,包含了所有用户。

Gin 框架

虽然 Go 语言自带的 net/http 包,可以比较容易地创建 HTTP 服务,但是它也有很多不足:

基于以上这些不足,出现了很多 Web 框架,如 Mux,Gin、Fiber 等,今天我要为你介绍的就是这款使用最多的 Gin 框架。

引入 Gin 框架

Gin 框架是一个在 上开源的 Web 框架,封装了很多 Web 开发需要的通用功能,并且性能也非常高,可以让我们很容易地写出 API。

Gin 框架其实是一个模块,也就是 Go Mod,所以采用 Go Mod 的方法引入即可。我在第 18讲的时候详细介绍过如何引入第三方的模块,这里再复习一下。

首先需要下载安装 Gin 框架,安装代码如下:

$ go get -u github.com/gin-gonic/gin

然后就可以在 Go 语言代码中导入使用了,导入代码如下:

import "github.com/gin-gonic/gin"

通过以上安装和导入这两个步骤,就可以在你的 Go 语言项目中使用 Gin 框架了。

使用 Gin 框架

现在,已经引入了 Gin 框架,下面我就是用 Gin 框架重写上面的示例,修改的代码如下所示:

ch21/main.go

func main() {r:=gin.Default()r.GET("/users", listUser)r.Run(":8080")
}
func listUser(c *gin.Context)  {c.JSON(200,users)
}

相比 net/http 包,Gin 框架的代码非常简单,通过它的 GET 方法就可以创建一个只处理 HTTP GET 方法的服务,而且输出 JSON 格式的数据也非常简单,使用 c.JSON 方法即可。

最后通过 Run 方法启动 HTTP 服务,监听在 8080 端口。现在运行这个 Gin 示例,在浏览器中输入 :8080/users,看到的信息和通过 net/http 包实现的效果是一样的。

获取特定的用户

现在你已经掌握了如何使用 Gin 框架创建一个简单的 API,并且可以返回所有的用户信息,那么如何获取特定用户的信息呢?

我们知道,如果要获得特定用户的信息,需要使用的是 GET 方法,并且 URL 格式如下所示:

http://localhost:8080/users/2

以上示例中的 2 是用户的 ID,也就是通过 ID 来获取特定的用户。

下面我通过 Gin 框架 Path 路径参数来实现这个功能,示例代码如下:

ch21/main.go

func main() {//省略没有改动的代码r.GET("/users/:id", getUser)
}
func getUser(c *gin.Context) {id := c.Param("id")var user Userfound := false//类似于数据库的SQL查询for _, u := range users {if strings.EqualFold(id, strconv.Itoa(u.ID)) {user = ufound = truebreak}}if found {c.JSON(200, user)} else {c.JSON(404, gin.H{"message": "用户不存在",})}
}

在 Gin 框架中,路径中使用冒号表示 Path 路径参数,比如示例中的 :id,然后在 函数中可以通过 c.Param("id") 获取需要查询用户的 ID 值。

小提示:Param 方法的参数要和 Path 路径参数中的一致,比如示例中都是 ID。

现在运行这个示例,通过浏览器访问 :8080/users/2,就可以获得 ID 为 2 的用户,输出信息如下所示:

{"ID":2,"Name":"李四"}

可以看到,已经正确的获取到了 ID 为 2 的用户,他的名字叫李四。

假如我们访问一个不存在的 ID,会得到什么结果呢?比如 99,示例如下所示:

➜ curl http://localhost:8080/users/99
{"message":"用户不存在"}%

从以上示例输出可以看到,返回了『用户不存在』的信息,和我们代码中处理的逻辑一样。

新增一个用户

现在你已经可以使用 Gin 获取所有用户,还可以获取特定的用户,那么你也应该知道如何新增一个用户了,现在我通过 Gin 实现如何新增一个用户,看和你想的方案是否相似。

根据 API 规范,实现新增使用的是 POST 方法,并且 URL 的格式为 :8080/users ,向这个 URL 发送数据,就可以新增一个用户,然后返回创建的用户信息。

现在我使用 Gin 框架实现新增一个用户,示例代码如下:

func main() {//省略没有改动的代码r.POST("/users", createUser)
}
func createUser(c *gin.Context) {name := c.DefaultPostForm("name", "")if name != "" {u := User{ID: len(users) + 1, Name: name}users = append(users, u)c.JSON(http.StatusCreated,u)} else {c.JSON(http.StatusOK, gin.H{"message": "请输入用户名称",})}
}

以上新增用户的主要逻辑是获取客户端上传的 name 值,然后生成一个 User 用户,最后把它存储到 users 集合中,达到新增用户的目的。

在这个示例中,使用 POST 方法来新增用户,所以只能通过 POST 方法才能新增用户成功。

现在运行这个示例,然后通过如下命令发送一个新增用户的请求,查看结果:

➜ curl -X POST -d 'name=飞雪' http://localhost:8080/users
{"ID":4,"Name":"飞雪"}

可以看到新增用户成功,并且返回了新增的用户,还有分配的 ID。

总结

Go 语言已经给我们提供了比较强大的 SDK,让我们可以很容易地开发网络服务的应用,而借助第三方的 Web 框架,可以让这件事情更容易、更高效。比如这篇文章介绍的 Gin 框架,就可以很容易让我们开发出 API,更多关于 Gin 框架的使用可以参考 Gin 实战系列文章。

在我们做项目开发的时候,要善于借助已经有的轮子,让自己的开发更有效率,也更容易实现。

在我们做项目开发的时候,会有增、删、改和查,现在增和查你已经学会了,那么就给你留 2 个作业,任选其中 1 个即可,它们是:

修改一个用户的名字;

删除一个用户。

下一讲,也就是本专栏的最后一讲,我将为你介绍如何使用 Go 语言实现 RPC 服务,记得来听课哦。

22 网络编程:Go 语言如何通过 RPC 实现跨平台服务?

在上一讲中,我为你讲解了 API 的规范以及实现,并且留了两个作业,它们分别是删除和修改用户,现在我为你讲解这两个作业。

删除一个用户比较简单,它的 API 格式和获取一个用户一样,但是 HTTP 方法换成了。删除一个用户的示例代码如下所示:

ch21/main.go

func main() {//省略没有修改的代码r.DELETE("/users/:id", deleteUser)
}
func deleteUser(c *gin.Context) {id := c.Param("id")i := -1//类似于数据库的SQL查询for index, u := range users {if strings.EqualFold(id, strconv.Itoa(u.ID)) {i = indexbreak}}if i >= 0 {users = append(users[:i], users[i+1:]...)c.JSON(http.StatusNoContent, "")} else {c.JSON(http.StatusNotFound, gin.H{"message": "用户不存在",})}
}

这个示例的逻辑就是注册 方法,达到删除用户的目的。删除用户的逻辑是通过ID 查询:

实现了删除用户的逻辑后,相信你已经会修改一个用户的名字了,因为它和删除一个用户非常像,实现代码如下所示:

func main() {//省略没有修改的代码r.PATCH("/users/:id",updateUserName)
}
func updateUserName(c *gin.Context) {id := c.Param("id")i := -1//类似于数据库的SQL查询for index, u := range users {if strings.EqualFold(id, strconv.Itoa(u.ID)) {i = indexbreak}}if i >= 0 {users[i].Name = c.DefaultPostForm("name",users[i].Name)c.JSON(http.StatusOK, users[i])} else {c.JSON(http.StatusNotFound, gin.H{"message": "用户不存在",})}
}

整体代码逻辑和删除的差不多的,只不过这里使用的是 PATCH方法。

什么是RPC 服务

RPC,也就是远程过程调用,是分布式系统中不同节点调用的方式(进程间通信),属于 C/S 模式。RPC 由客户端发起,调用服务端的方法进行通信,然后服务端把结果返回给客户端。

RPC的核心有两个:通信协议和序列化。在 HTTP 2 之前,一般采用自定义 TCP 协议的方式进行通信,HTTP 2 出来后,也有采用该协议的,比如流行的gRPC。

序列化和反序列化是一种把传输内容编码和解码的方式,常见的编解码方式有 JSON、 等。

在大多数 RPC的架构设计中,都有、 Stub、、 Stub这四个组件, 和 之间通过 进行通信。RPC 架构如下图所示:

(图片来自于 搜索)

下面我为你总结下 RPC 调用的流程:

RPC 调用常用于大型项目,也就是我们现在常说的微服务,而且还会包含服务注册、治理、监控等功能,是一套完整的体系。

Go 语言 RPC 简单入门

RPC这么流行,Go 语言当然不会错过,在 Go SDK 中,已经内置了 net/rpc 包来帮助开发者实现 RPC。简单来说,net/rpc 包提供了通过网络访问服务端对象方法的能力。

现在我通过一个加法运算来演示 RPC的使用,它的服务端代码如下所示:

ch22//.go

package server
type MathService struct {
}
type Args struct {A, B int
}
func (m *MathService) Add(args Args, reply *int) error {*reply = args.A + args.Breturn nil
}

在以上代码中:

有了这个定义好的服务对象,就可以把它注册到暴露的服务列表中,以供其他客户端使用了。在Go 语言中,要注册一个一个RPC 服务对象还是比较简单的,通过 方法即可,示例代码如下所示:

ch22/.go

package main
import ("gotour/ch22/server""log""net""net/rpc"
)
func main()  {rpc.RegisterName("MathService",new(server.MathService))l, e := net.Listen("tcp", ":1234")if e != nil {log.Fatal("listen error:", e)}rpc.Accept(l)
}

以上示例代码中,通过 函数注册了一个服务对象,该函数接收两个参数:

然后通过 net. 函数建立一个TCP 链接,在 1234 端口进行监听,最后通过 rpc. 函数在该 TCP 链接上提供 这个 RPC 服务。现在客户端就可以看到这个服务以及它的Add 方法了。

任何一个框架都有自己的规则,net/rpc 这个 Go 语言提供的RPC 框架也不例外。要想把一个对象注册为 RPC 服务,可以让客户端远程访问,那么该对象(类型)的方法必须满足如下条件:

总结下来,该方法的格式如下所示:

func (t *T) MethodName(argType T1, replyType *T2) error

这里面的 T1、T2都是可以被 /gob 序列化的。

有了提供好的RPC 服务,现在再来看下客户端如何调用,它的代码如下所示:

ch22/.go

package main
import ("fmt""gotour/ch22/server""log""net/rpc"
)
func main()  {client, err := rpc.Dial("tcp",  "localhost:1234")if err != nil {log.Fatal("dialing:", err)}args := server.Args{A:7,B:8}var reply interr = client.Call("MathService.Add", args, &reply)if err != nil {log.Fatal("MathService.Add error:", err)}fmt.Printf("MathService.Add: %d+%d=%d", args.A, args.B, reply)
}

在以上实例代码中,首先通过 rpc.Dial 函数建立 TCP 链接,需要注意的是这里的 IP、端口要和RPC 服务提供的一致,确保可以建立 RCP 链接。

TCP 链接建立成功后,就需要准备远程方法需要的参数,也就是示例中的args 和 reply。参数准备好之后,就可以通过 Call 方法调用远程的RPC 服务了。Call 方法有 3 个参数,它们的作用分别如下所示:

调用的远程方法的名字,这里是.Add,点前面的部分是注册的服务的名称,点后面的部分是该服务的方法;

客户端为了调用远程方法提供的参数,示例中是args;

为了接收远程方法返回的结果,必须是一个指针,也就是示例中的& ,这样客户端就可以获得服务端返回的结果了。

服务端和客户端的代码都写好了,现在就可以运行它们,测试 RPC调用的效果了。

首先运行服务端的代码,提供 RPC 服务,运行命令如下所示:

➜ go run ch22/server_main.go

然后运行客户端代码,测试调用 RPC的结果,运行命令如下所示:

➜ go run ch22/client_main.go

如果你看到了 .Add: 7+8=15的结果,那么恭喜你,你完成了一个完整的RPC 调用。

关于我们

最火推荐

小编推荐

联系我们


版权声明:本站内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 88@qq.com 举报,一经查实,本站将立刻删除。备案号:桂ICP备2021009421号
Powered By Z-BlogPHP.
复制成功
微信号:
我知道了