同学们好!在我们之前构建的基于微服务架构的应用程序中,客户机已经可以通过互联网,远程调用服务器上的服务方法,这些方法负责具体业务逻辑的实现。但这对于真正意义上的互联网应用而言还是不够的。这里所说的客户机在真正的互联网应用中,也扮演着服务器的角色,提供的是Web服务。它的客户机应该是浏览器或移动端应用之类的,直接与用户发生交互的软件。我们把这样的结构关系概括为,浏览器——Web服务器——应用服务器模式。这里的Web服务器同时也是RPC客户机,而应用服务器则是一个个微服务形式的RPC服务器。浏览器和Web服务器之间的通信基于HTTP协议,而Web服务器与应用服务器之间的通信则遵循RPC协议。我们在前面的课程中已经看到,Go语言本身对HTTP协议具有非常好的支持。只需很少量的代码就可以快速构建出一个满足高并发要求的Web服务器。但这并不是基于Go语言实现Web应用的唯一选择。我们即将在这节课里学习的Gin框架,可以说是迄今为止最流行的,基于Go语言的Web编程框架。而且它可以和我们在上节课里为大家介绍的,基于go-micro框架的微服务系统完美地集成,构建出功能强大且性能卓越的,基于微服务架构的互联网应用系统。
在这节课里,我们先简单浏览一下在Go语言开发中,几种常用的Web编程框架。而后就其中的Gin框架详细展开,看看它是怎么安装和使用的。最后再结合go-micro框架,将我们之前构建的微服务应用移植到Web上。
在基于Go语言的程序开发中,有几种常用的Web框架可供选择。
作为Go语言社区里最受欢迎的Web框架,Gin具有运行速度快,支持路由分组、崩溃捕获和错误处理等特性,对中间件和JSON也有很好的支持。Beego是另一个同样非常流行的Web框架。基于Beego可以快速地开发远程API、Web及后端应用。它的设计灵感来源于Tornado、Sinatra和Flask三个已经非常成熟的Web框架。Echo框架以高性能、可扩展和轻量级著称,轻到只包含MVC架构中的C,即控制器部分。相对于轻量级的Echo,Iris框架实现了完整的MVC模式,且支持依赖注入,入门相对简单,便于快速构建Web后端应用。
和所有第三方框架一样,Gin框架也需要单独安装才能使用。
好在Gin框架已在GitHub上开源,可以直接下载并安装到正确的路径下。
为了让基于Gin框架的Web应用得以正确运行,还要安装一些其它依赖。
下面我们来学习如何使用Gin框架构建面向互联网应用。
我们首先从最简单的Web服务器开始。这里只需要三个步骤,首先创建一个表示路由的对象,其次通过该路由对象将特定的HTTP方法和路径字符串匹配到特定的处理函数上,最后运行这个服务器。有过网上冲浪经历的人对资源路径与处理函数之间的对应关系并不难以理解。毕竟不同的路径做不同的事。比如一个博客系统,通常会提供针对特定文章的添加、删除、修改和读取功能。这四个功能会有四个独立的处理函数予以实现。与这四个处理函数关联的会是四个独立的资源路径,比如/insertblog、/deleteblog、/updateblog和/getblog,当然,换一种形式也许会更好,比如/blog/insert、/blog/delete、/blog/update和/blog/get。那么你有没有想过,其实我们只用一个资源路径,比如/blog,也能把这四种操作区分开来。请不要忘记,在每个HTTP请求中不仅仅有资源路径,还有访问方法。我们完全可以将访问方法和资源路径加以组合,以形成更多样化的路由选择。比如将POST方法和/blog路径组合表示添加文章,将DELETE方法和/blog路径组合表示删除文章,将PUT方法和/blog路径组合表示修改文章,将GET方法和/blog路径组合表示读取文章。不可否认,方法的选择并不是随意的,通常POST方法用于增加型的操作,DELETE方法用于删除型操作,PUT方法用于更新型操作,GET方法用于查询型操作。与之对应的是微服务侧访问数据库的四种基本操作——增、删、改、查。事实上,这与基于RESTful风格的接口设计原则也是一脉相承的。
在下面的Gin工程中,我们基于Gin框架,构建了一个最简单的Web服务器:
x1package main
2
3import "github.com/gin-gonic/gin"
4
5func main() {
6 // 初始化路由
7 router := gin.Default()
8
9 // 路由匹配
10 router.GET("/", func(contex *gin.Context) {
11 contex.Writer.WriteString("Hello World")
12 })
13
14 // 运行
15 router.Run(":8080")
16}
在这段代码中,我们首先通过gin包的Default方法创建了一个Engine类型的路由器对象。然后通过该路由器对象的GET方法,将HTTP请求中的GET方法和“/”路径加以组合,匹配到一个匿名函数。该函数将字符串“Hello World”写入到HTTP响应中。最后通过路由器对象的Run方法运行这个服务器,并启动对当前主机任意IP地址8080端口的监听。
运行服务器程序。用浏览器访问localhost:8080,可以看到“Hello World”显示在浏览器窗口中。
下面我们将再结合使用Gin框架和go-micro框架,将之前构建的微服务应用移植到Web上。
首先,我们将上节课创建的Micro目录原地复制一份,更名为GinMicro。
然后在客户机工程helloworld-client的main.go文件中稍作修改:
xxxxxxxxxx
461package main
2
3import (
4 "context"
5
6 pb "helloworld-client/proto"
7
8 "github.com/gin-gonic/gin"
9 "github.com/go-micro/plugins/v4/registry/consul"
10 "go-micro.dev/v4"
11)
12
13var (
14 service = "helloworld"
15 version = "latest"
16)
17
18func main() {
19 // 初始化路由
20 router := gin.Default()
21
22 // 路由匹配
23 router.GET("/", func(contex *gin.Context) {
24 contex.Writer.WriteString(callHelloworld())
25 })
26
27 // 运行
28 router.Run(":8080")
29}
30
31func callHelloworld() string {
32 // Create service
33 srv := micro.NewService(micro.Registry(consul.NewRegistry()))
34 srv.Init()
35
36 // Create client
37 c := pb.NewHelloworldService(service, srv.Client())
38
39 // Call service
40 rsp, err := c.Call(context.Background(), &pb.CallRequest{Name: "John"})
41 if err != nil {
42 return err.Error()
43 }
44
45 return rsp.GetMsg()
46}
这段代码中的main函数与Gin工程中的main函数差别不大。唯一的不同在于,写入到Web响应中的字符串不再是固定的“Hello World”,而是调用callHelloworld函数得到的返回值。callHelloworld函数借助go-micro框架,调用远程微服务中的方法,并返回该方法返回的消息文本。
启动Consul、helloworld和helloworld-client。用浏览器访问localhost:8080,可以看到“Hello John”显示在浏览器窗口中。
是时候对我们所做的一切做个总结了。在我们的基于微服务架构的互联网应用系统中,共有四个重要角色,它们是微服务,如helloworld,服务注册与发现,如Consul,Web服务器,如helloworld-client和浏览器。首先微服务将包括IP地址和端口号等在内的自身信息,注册到服务发现服务器。浏览器向Web服务器发起HTTP请求。Web服务器从HTTP请求中提取必要的信息,包括方法、路径、参数、数据等,并据此判定具体为何种业务。Web服务器根据业务种类确定与之对应的服务名,向服务发现服务器发起查询,得到适用微服务进程的IP地址和端口号。Web服务器根据IP地址和端口号建立与特定微服务进程的TCP连接,发起针对特定方法的远程过程调用,传入参数并获得返回值。Web服务器从微服务远程方法的返回值中提取必要的信息,组织成HTTP响应,返回给浏览器。
当然,这里所描述的只是一个原理性的结构。实际的应用系统会比这个更复杂一些。比如,为了实现负载均衡,通常会在Web服务器前面部署一个反向代理服务器,如Nginx,同时会将微服务和服务注册与发现做成集群,以满足高并发、高可用的实际要求。
谢谢大家,我们下节课再见!