Go web 框架基础

428 阅读12分钟

一、Web 框架概述

Web框架(Web framework)

或者叫做Web应用框架(Web application framework),是用于进行Web开发的一套软件架构。大多数的Web框架提供了一套开发和部署网站的方式。为Web的行为提供了一套支持支持的方法。使用Web框架,很多的业务逻辑外的功能不需要自己再去完善,而是使用框架已有的功能就可以。

简单来说,Web框架是用来进行Web应用开发的一个软件架构。主要用于动态网络开发。开发者在基于Web框架实现自己的业务逻辑。Web框架实现了很多功能,为实现业务逻辑提供了一套通用方法。

二、常见的几种web框架(go)

2.1 Beego

beego是一个快速开发 Go 应用的 HTTP 框架,他可以用来快速开发 API、Web 及后端服务等各种应用,是一个 RESTful 的框架,主要设计灵感来源于 tornado、sinatra 和 flask 这三个框架,但是结合了 Go 本身的一些特性(interface、struct 嵌入等)而设计的一个框架。

2.1.1 核心模块

(1) Controller

  • Beego 是一个典型的 MVC 框架 (Model-View-Controller)的,所以它定义了一个核心接口 ControllerInterface。 ControllerInterface 定义了一个控制器必须要解决什么问题(这些问题的方法定义)。
  • 路由的主要功能是实现从请求地址到实现的方法,beego中封装了Controller,所以路由是从路径到ControllerInterface的过程,ControllerInterface的方法有如下:
// ControllerInterface is an interface to uniform all controller handler.
type ControllerInterface interface {
   Init(ct *context.Context, controllerName, actionName string, app interface{})
   Prepare()
   Get()
   Post()
   Delete()
   Put()
   Head()
   Patch()
   Options()
   Trace()
   Finish()
   Render() error
   XSRFToken() string
   CheckXSRFCookie() bool
   HandlerFunc(fn string) bool
   URLMapping()
}

image.png

  • Controller 为 ControllerInterface 的默认实现, 它提供了实现自定义控制器的各种辅助方法,所以在 Beego 里面,一般都是组合 Controller 来实现自己的 Controlle
type UserController struct {
   web.Controller
}

type User struct {
   Name string
}

func (c *UserController) GetUser() {
   c.Ctx.WriteString(" hello Stone")
}

func (c *UserController) CreateUser() {
   u := &User{}
   err := c.Ctx.BindJSON(u)
   if err != nil {
      c.Ctx.WriteString(err.Error())
      return
   }
   _ = c.Ctx.JSONResp(u)
}

注意到用户虽然被要求组合 Controller,但是路由注册和服务器启动是通过另外一套机制来完成的。

(2) HttpServer

代表http服务器是服务器的抽象,大多时候就是一共进程,用于管理web应用的生命周期和资源隔离单位。

// HttpServer defines beego application with a new PatternServeMux.
type HttpServer struct {
   Handlers           *ControllerRegister
   Server             *http.Server
   Cfg                *Config
   LifeCycleCallbacks []LifeCycleCallback
}

(3) ControllerRegister

虽然路由是从路径到ControllerInterface的过程,但是在这过程中的注册路由,路由匹配和执行业务代码都是透过 ControllerRegiste 来完成的。

// ControllerRegister containers registered router rules, controller handlers and filters.
type ControllerRegister struct {
   // 路由树
   routers      map[string]*Tree  
   enablePolicy bool
   enableFilter bool
   policies     map[string]*Tree
   filters      [FinishRouter + 1][]*FilterRouter
   pool         sync.Pool

   // the filter created by FilterChain
   chainRoot *FilterRouter

   // keep registered chain and build it when serve http
   filterChains []filterChainConfig

   cfg *Config
}

image.png

image.png

image.png

image.png

image.png

(4) Context

用户操作请求和响应是通过 Ctx 来达成的。它代表的是整 个请求执行过程的上下文。Beego 将 Context 细分了几个部分:

  • Input:定义了很多和处理请求有关的方法
  • Output:定义了很多和响应有关的方法
  • Response:对 http.ResponseWriter 的二次封装

image.png

image.png

2.1.2 总结

ControllerInterface 可以看做核心接口,因为它直接体现了Beego 的设计初衷:MVC 模式。同时它也是用户核心接入点。但是如果从功能特性上来说,HttpServer 和 ControllerRegister 才是核心。

  • HttpServer 作为服务器抽象,用于管理应用生命周期和资源隔离单位。
  • ControllerRegister 解决了路由注册和路由匹配问题。
  • Context 和 Controller 为用户提供了丰富 API,用于辅助构建系统。

image.png

优点:

  • 简单
  • 性能良好,开发效率不错,解放开发者生产力
  • 社区有很多中文开发者,找资料是相对方便的
  • 代码文档化做的很好

缺点

  • 比较臃肿,因为提供了很多支持,当遇到坑时需要花很多时间查源码解决问题
  • 模块众多,这既是优点也是缺点

2.2 Gin

Gin是用Go(Golang)编写的Web框架。他是一个类似于 martini 但拥有更好性能的API框架,由于 httprouter,速度提高了40倍。如果您追求性能和高效的效率,您将会爱上Gin。

2.2.1 核心模块

(1) Engine

Engine 是 gin 框架的一个实例,它包含了多路复用器中间件和配置中心。(Engine 可以看做是 Beego 中 HttpServer 和 ControllerRegister 的合体)

  • 实现了路由树功能,提供了注册和匹配路由的功能
  • 它本身可以作为一个 Handler 传递到 http 包,用于启动服务器

Engine 的启动:

gin 通过 Engine.Run(addr ...string) 来启动服务,最终调用的是http.ListenAndServe(address, engine),其中第二个参数应当是一个 Handler 接口的实现,即 engine 实现了此接口:

func TestUserController_GetUser(t *testing.T) {
   g := gin.Default()
   ctrl := &UserController{}
   g.GET("/user/*", ctrl.GetUser)
   g.POST("/user/*", ctrl.CreateUser)

   g.GET("/static", func(context *gin.Context) {
      // 读文件
      // 谐响应
   })
   _ = g.Run(":8082")

}

image.png image.png image.png Engine.ServeHTTP() 会先初始化一个空的上下文,然后挂上请求 c.Reuqest = req,随后执行 engine.handlerHTTPRequest(c)(包含主要处理逻辑的函数)。

image.png

  • engine.handlerHTTPRequest() 会先设置处理一些配置项,如 UseRawPath、RemoveExtraSlash 等

image.png

  • 然后开始寻找路由,从 engine.trees 中寻找

image.png

image.png

image.png

  • 当找到后执行找到路由对应的处理链 .handlers

image.png

image.png

以上就是正常处理一个请求的主要逻辑, Engine 的路由树功能本质上是依赖于 methodTree 的。

(2) IRoutes

Engine 组合了 RouterGroup, RouterGroup 是对路由树的包装,所有的路由规则最终都是由它来进行管理, 因为 RouterGroup 实现了 IRouter 接口,IRouter 接口是 IRoutes 接口和 Group 函数组合而成。提供的是注册路由的抽象。它的实现类 Engine 类似 ControllerRegister

所以 Engine 直接具备了 RouterGroup 所有的路由管理功能,同时 RouteGroup 对象里面还会包含一个 Engine 的指针,这样 Engine 和 RouteGroup 就成了「你中有我我中有你」的关系。

image.png

image.png

  • IRoutes 接口定义了所有路由处理的实现方法。
    • Use 方法提供了用户接入自定义逻辑的能力,这个一般情况下也被看做是插件机制。

    • POST(relativePath string, handlers ...HandlerFunc) 调用了 handle() 方法,是在 Group 中加一个路由(相对地址)及处理函数链(很常用就不多说了,其他类似方法也略)。

    • Handle(method, relativePath string, handlers ...HandlerFunc) 方法相对于 POST()/GET() 等方法只是可以传入自定义的方法名,用于特殊的、不标准的、Gin 内置不存在的的请求方法(不常用)。

    • Any() 会将路由及函数处理链在所有的支持方法上都 copy 存储一份,以实现通过任何请求方法都会有同样的调用链。

    • StaticFile(relativePath, filePath) 会将路由映射到文件系统的某一文件上,此时的 relativePath 是不允许有变量存在的(不允许有 : 和 * )。内部通过 c.File() 响应此文件。

    • Static(relativePath, root string) 将路由映射到文件系统的某一个文件夹上,底层调用了 StaticFS(relativePath, Dir(root, false))

    • StaticFS() 类似 Static(),但自定义 http.FileSystem 了,FileSystem 就可以理解为一个目录,这个目录就是所谓的文件系统。gin 的实现为了安全禁用了目录中的 list 功能。

image.png

  • IRouter 接口定义了所有路由处理的实现方法以及一个分组方法(Group())。
    • Group() 方法,RouterGroup 通过 Group() 方法创建子分组,子分组会继承下来父 Group 的 handlers 并追加自己独有的 handlers,计算出此 Group 的 path 地址,及记录 Engine 地址。

image.png

Gin 没有 Controller 的抽象。所以在这方面我和 Gin 的倾向比较一致,即 MVC 应该是用户组织 Web 项目的模式,而不是我们中间件设计者要考虑的。

(3) methodTrees 和 methodTree

Gin 在 Engine 结构体中定义了 methodTrees, 实际上它代表的是森林, 即每一种 http 方法都对应到一颗路由树。

image.png

methodTrees 是一种 methodTree 类型的切片,而 methodTree才是真实的路由树,代表位于 Gin 路由结构顶端的方法树,它含有两个属性 methodroot,其中method 属性表示请求的方法类型,如:GETPOSTPUT 等,而 root 属性则指向对应路由树的根节点。

image.png

(4) HandlerFunc 和 HandlersChain

image.png

image.png

从源代码中给的注释,我们可以知道 RouterGroup 在 Gin 内部用于配置路由器,其与前缀处理函数(中间件)数组相关联

HandlerFunc 定义了核心抽象——处理逻辑。 在默认情况下, 它代表了注册路由的业务代码。

HandlersChain 是一个 HandlerFunc 类型的切片,可以理解为构造了责任链模式

image.png

(5)Context

gin.Context内保存了请求的上下文信息,是所有请求处理器的入口参数, 提供了丰富得API:

image.png

  • 处理请求的 API,代表的是以 Get 和 Bind 为前缀的方法
  • 处理响应的 API,例如返回 JSON 或者 XML 响应的方法
  • 渲染页面,如 HTML 方法

image.png

2.2.2 总结

优点

  • 支持路由分组
  • 运行速度快
  • 更好的支持 middleware 和 json
  • 灵活性更好(封装程度不如 beego 高), 也可以说是更轻量的框架

缺点

  • 相比于 beego 更轻量,那么也就没有像 beege 那样提供更多的 api

image.png

2.3 Iris

Iris是一款Go语言中用来开发web应用的框架,该框架支持编写一次并在任何地方以最小的机器功率运行,如Android、ios、Linux和Windows等。该框架只需要一个可执行的服务就可以在平台上运行了。Iris框架以简单而强大的api而被开发者所熟悉。iris除了为开发者提供非常简单的访问方式外,还同样支持MVC。另外,用iris构建微服务也很容易。在iris框架的官方网站上,被称为速度最快的Go后端开发框架。在Iris的网站文档上,列出了该框架具备的一些特点和框架特性,列举如下:

  • 聚焦高性能
  • 健壮的静态路由支持和通配符子域名支持
  • 视图系统支持超过5以上模板
  • 支持定制事件的高可扩展性Websocket API
  • 带有GC, 内存 & redis 提供支持的会话
  • 方便的中间件和插件
  • 完整 REST API
  • 能定制 HTTP 错误
  • 源码改变后自动加载

等等还有很多特性,大家可以参考Iris官方文档。

2.3.1 核心模块

(1) Application

Application 代表的是应用,它是 Iris 的核心抽象, 实际上它的语义应该和Beego 的 HttpServer 和 Gin 的 Engine 更像。提供了:

  • 生命周期控制功能,如 Shutdown 等方法
  • 注册路由的 API

image.png

image.png

func renderHtml(ctx iris.Context) {
   _, _ = ctx.HTML("Hello <strong>%s</strong>!", "World")
}

func TestHelloWorld(t *testing.T) {

   app := iris.New()

   app.Get("/", renderHtml)

   _ = app.Listen(":8083")

}

(2) Route、APIBuilder 和 repository

Iris 的设计非常复杂。在 Beego 和 Gin 里面能够明显看到路由 树的痕迹,但是在 Iris 里面就很难看出来。

image.png

image.png

  • Route:直接代表了已经注册的路由。在 Beego 和 Gin 里面,对应的是路由树的节点
  • APIBuilder:创建 Route 的 Builder 模式,Party 也是它创建的
  • repository:存储了所有的 Routes,有点接近 Gin 的 methodTrees 的概念

(3) Context

image.png

image.png

Iris Context 也代表上下文。本身也是提供了各种处理请求和响应的方法。 基本上和 Beego 和 Gin 的 Context 没啥区别。 唯一的一点是 Iris Context 支持请求级别的添加 Handler,即 AddHandler 方法。

2.3.2 总结

(1) 优点

  • 是社区驱动的Go语言Web 框架,支持http2,MVC。
  • 有着较高的社区活跃度和文档支持

(2) 缺点

  • 不够稳定
  • 使用数量和国内开源贡献数量目前仍然不如Gin和Echo

image.png

2.4 Echo

Echo 是众多 Go Web 框架的一个,根据官网介绍,它有着高性能、可扩展性、极简的特点。echo框架默认其实只包含了MVC框架的C部分,就是负责url路由和控制器部分。至于V视图部分和M数据操作部分可以随意使用自己喜欢的工具库来操作。

2.4.1 核心模块

(1) Echo Echo 是框架内部的一个结构体,类似于 Beego 的 HttpServer 和 Gin 的 Engine

image.png

image.png

核心功能:

  • 对外暴露路由注册方法,但不负责成为路由树的载体
  • 负责服务的生命周期管理,如 Shutdown 和 Start 等
func TestHelloWorld(t *testing.T) {
   // Echo instance
   e := echo.New()

   // Middleware
   e.Use(middleware.Logger())
   e.Use(middleware.Recover())

   // Routes
   e.GET("/", hello)

   // Start server
   e.Logger.Fatal(e.Start(":8084"))
}

// Handle
func hello(c echo.Context) error {
   return c.String(http.StatusOK, "Hello, World!")
}

在 Echo 里面有两个相似的字段:

  • Router:这其实就是代表路由树
  • Routers:这代表的是根据 Host 来进行分组组织,可以看做是近似于 namespace 之类的概念,既是一种组织方式,也是一种隔离机制

(2) Router 和 node

image.png

  • Router 代表的就是路由树,node 代表的是路由树上的节点, 它里面还有一个字段叫做 echo 维护的是使用 Route 的是 echo。这种设计形态在别的地方也能见到,比如说在 sql.Tx 里 面维持了一个 sql.DB 的实例
  • node 里面有一个很有意思的设计:staticChildren、paramChild 和 anyChild。利用这种设计可以轻松实现路由优先级和路由冲突检测

(3) Context

image.png

这里可能会感到意外,context是接口类型,定义了处理请求和响应的各种方法。 但其实作用和 Beego、Gin、Iris 的 Context 没有什么区别

2.4.2 总结

优点

  • 路由性能高
  • 更轻量级的web开发框架

缺点

  • 调试不方便,报错信息不友好
  • 路由性能虽高,但是路由实现的算法底层不支持路由排序,会引起路由冲突

image.png

2.5 一张图对比四种web框架的核心汇总

image.png

2.6 Web 框架面试题

实际上,面整体 Web 框架的还是比较少的。大多数时候,面试都是聊具体的某个 Web 框架。从整体上来面的话,比较高频率的问题:

  • Web 框架拿来做什么?处理 HTTP 请求,为用户提供便捷 API,为用户提供无侵入式的插件机制,提供如上传下载等默认功能
  • 为什么都已经有了 http 包,还要开发 Web 框架?高级路由功能、封装 HTTP 上下文以提供简单API、封装 Server 以提供生命周期控制、设计插件机制以提供无侵入式解决方案
  • Web 框架的核心?路由树、上下文 Context、Server(按照我理解的重要性排序)