如何设计一个 Web 框架 - Go Web 开发实战笔记

1,489 阅读9分钟

概述

通过学习如何使用 Go 语言来实现一个完整的框架设计,从而了解如何开发 Web 应用,如何搭建自己的目录结构,如何实现路由,如何实现 MVC 模式等各方面的开发内容。

项目规划

在学习使用 beego 框架之前,需要了解其项目配置、执行逻辑、项目结构、项目架构。

GOPATH 配置

Mac 系统配置 GOPATH:

  1. 打开终端,打开目录:cd ~

  2. 查看是否有 .bash_profile 文件:ls -all

  3. 有则跳过此步,没有则:
    1)创建:touch .bash_profile
    2)编辑:vi .bash_profile
    3)自定义 GOPATH 和 GOBIN 位置:

    export GOROOT=/usr/local/go
    export GOPATH=$HOME/Workspace/go
    export PATH=$PATH:${GOPATH//://bin:}/bin
    
  4. 编译:source .bash_profile

  5. 查看 Go 环境变量:go env

项目设置

必须保证 go(GOPATH="/Users/play/Workspace/go") 这个代码目录下面有三个目录 pkg、bin、src,新建项目的源码放在 src 目录下面,在此目录叫做 beeblog。

beego 的执行逻辑

beego 框架是基于 模型 - 视图 - 控制器 这一设计模式的。MVC 是一种将应用程序的逻辑层和表现层进行分离的结构方式。在实践中,由于表现层从 Go 中分离了出来,所以它允许你的网页中只包含很少的脚本。

  • 模型 (Model) 代表数据结构。通常来说,模型类将包含取出、插入、更新数据库资料等这些功能。

  • 视图 (View) 是展示给用户的信息的结构及样式。一个视图通常是一个网页,但是在 Go 中,一个视图也可以是一个页面片段,如页头、页尾。它还可以是一个 RSS 页面,或其它类型的“页面”,Go 实现的 template 包已经很好的实现了 View 层中的部分功能。

  • 控制器 (Controller) 是模型、视图以及其他任何处理 HTTP 请求所必须的资源之间的中介,并生成网页。

下图显示了项目设计中框架的数据流是如何贯穿整个系统:

框架的数据流

  1. main.go 作为应用入口,初始化一些运行博客所需要的基本资源,配置信息,监听端口。
  2. 路由功能检查 HTTP 请求,根据 URL 以及 method 来确定谁(控制层)来处理请求的转发资源。
  3. 如果缓存文件存在,它将绕过通常的流程执行,被直接发送给浏览器。
  4. 安全检测:应用程序控制器调用之前,HTTP 请求和任一用户提交的数据将被过滤。
  5. 控制器装载模型、核心库、辅助函数,以及任何处理特定请求所需的其它资源,控制器主要负责处理业务逻辑。
  6. 输出视图层中渲染好的即将发送到 Web 浏览器中的内容。如果开启缓存,视图首先被缓存,将用于以后的常规请求。

beego 项目结构

一般的 beego 项目的目录如下所示:

├── conf
│   └── app.conf
├── controllers
│   ├── admin
│   └── default.go
├── main.go
├── models
│   └── models.go
├── static
│   ├── css
│   ├── ico
│   ├── img
│   └── js
└── views
    ├── admin
    └── index.tpl

从上面的目录结构我们可以看出来 M(models 目录)、V(views 目录)和 C(controllers 目录)的结构, main.go 是入口文件。

beego 的架构

beego 的整体设计架构如下所示:

beego 是基于八大独立的模块构建的,是一个高度解耦的框架。当初设计 beego 的时候就是考虑功能模块化,用户即使不使用 beego 的 HTTP 逻辑,也依旧可以使用这些独立模块,例如:你可以使用 cache 模块来做你的缓存逻辑;使用日志模块来记录你的操作信息;使用 config 模块来解析你各种格式的文件。所以 beego 不仅可以用于 HTTP 类的应用开发,在你的 socket 游戏开发中也是很有用的模块。大家如果玩过乐高的话,应该知道很多高级的东西都是一块一块的积木搭建出来的,而设计 beego 的时候,这些模块就是积木,高级机器人就是 beego。

路由器设计

路由器用于检查 HTTP 请求,根据 URL 以及 method 来确定谁(控制层)来处理请求的转发资源。

HTTP 路由

HTTP 路由组件负责将 HTTP 请求交到对应的函数处理(或者是一个 struct 的方法),路由在框架中相当于一个事件处理器,而这个事件包括:

  • 用户请求的路径 (path),例如: /user/123,/article/123 ,和参数信息,例如:?id=11
  • HTTP 的请求方法 (method),例如:GET、POST、PUT、DELETE、PATCH 等

路由器就是根据用户请求的事件信息转发到相应的处理函数(控制层)。

默认的路由实现

默认使用 Go 的 http 包来设计和实现路由,以一个例子来说明:

func fooHandler(w http.ResponseWriter, r *http.Request) {
    fmt.Fprintf(w, "Hello, %q", html.EscapeString(r.URL.Path))
}

http.HandleFunc("/foo", fooHandler)

http.HandleFunc("/bar", func(w http.ResponseWriter, r *http.Request) {
    fmt.Fprintf(w, "Hello, %q", html.EscapeString(r.URL.Path))
})

log.Fatal(http.ListenAndServe(":8080", nil))

上面的例子调用了 http 默认的 DefaultServeMux 来添加路由,需要提供两个参数,第一个参数是希望用户访问此资源的 URL 路径(保存在 r.URL.Path),第二参数是即将要执行的函数,以提供用户访问的资源。路由的思路主要集中在两点:

  • 添加路由信息
  • 根据用户请求转发到要执行的函数

Go 默认的路由添加是通过函数 http.Handle 和 http.HandleFunc 等来添加,底层都是调用了 DefaultServeMux.Handle(pattern string, handler Handler),这个函数会把路由信息存储在一个 map 信息中 map[string]muxEntry,这就解决了上面说的第一点。

Go 监听端口,然后接收到 tcp 连接会扔给 Handler 来处理,上面的例子默认 nil 即为 http.DefaultServeMux,通过 DefaultServeMux.ServeHTTP 函数来进行调度,遍历之前存储的 map 路由信息,和用户访问的 URL 进行匹配,以查询对应注册的处理函数,这样就实现了上面所说的第二点。

beego 框架路由实现

目前几乎所有的 Web 应用路由实现都是基于 http 默认的路由器,但是 Go 自带的路由器有几个限制:

  • 不支持参数设定,例如 /user/:uid 这种泛类型匹配
  • 无法很好的支持 REST 模式,无法限制访问的方法,例如上面的例子中,用户访问 /foo,可以用 GET、POST、DELETE、HEAD 等方式访问
  • 一般网站的路由规则太多了,编写繁琐。

beego 框架的路由器基于上面的几点限制考虑设计了一种 REST 方式的路由实现,路由设计也是基于上面 Go 默认设计的两点来考虑:存储路由和转发路由

存储路由

针对前面所说的限制点,首先要解决参数支持就需要用到正则,第二和第三点通过一种变通的方法来解决,REST 的方法对应到 struct 的方法中去,然后路由到 struct 而不是函数,这样在转发路由的时候就可以根据 method 来执行不同的方法。

根据上面的思路,设计了两个数据类型 ControllerInfo 和 ControllerRegister ( routers 是一个 slice 用来保存用户添加的路由信息,以及 beego 框架的应用信息)

// 源码:https://github.com/astaxie/beego/blob/develop/router.go
// ControllerInfo holds information about the controller.
type ControllerInfo struct {
	pattern        string
	controllerType reflect.Type
	methods        map[string]string
	handler        http.Handler
	runFunction    FilterFunc
	routerType     int
	initialize     func() ControllerInterface
	methodParams   []*param.MethodParam
}

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

转发路由是基于 ControllerRegistor 里的路由信息来进行转发的,详细的实现查看文件 router.go 的 ServeHTTP 方法:

// https://github.com/astaxie/beego/blob/develop/router.go
func (p *ControllerRegister) ServeHTTP(rw http.ResponseWriter, r *http.Request) {
...
使用入门

具体使用方法: beego.me/docs/quicks…

Controller 设计

设计一个基于 REST 风格的 MVC 框架中的 controller,最大限度地简化 Web 应用的开发。

Controller 作用

MVC 设计模式是目前 Web 应用开发中最常见的架构模式,通过分离 Model(模型)、View(视图)和 Controller(控制器),可以更容易实现易于扩展的用户界面 (UI)。Model 指后台返回的数据;View 指需要渲染的页面,通常是模板页面,渲染后的内容通常是 HTML;Controller 指 Web 开发人员编写的处理不同 URL 的控制器,controller 在整个的 MVC 框架中起到了一个核心的作用,负责处理业务逻辑,因此控制器是整个框架中必不可少的一部分,Model 和 View 对于有些业务需求是可以不写的,例如没有数据处理的逻辑处理,没有页面输出的 302 调整之类的就不需要 Model 和 View,但是 controller 这一环节是必不可少的。

beego 的 REST 设计

前面了解到路由实现了注册 struct 的功能,而 struct 中实现了 REST 方式,现在需要设计一个用于逻辑处理 controller 的基类,这里主要设计了两个类型,一个 struct、一个 interface

// 源码地址: https://github.com/astaxie/beego/blob/develop/controller.go
// Controller defines some basic http request handler operations, such as
// http context, template and view, session and xsrf.
type Controller struct {
	// context data
	Ctx  *context.Context
	Data map[interface{}]interface{}

	// route controller info
	controllerName string
	actionName     string
	methodMapping  map[string]func() //method:routertree
	AppController  interface{}

	// template data
	TplName        string
	ViewPath       string
	Layout         string
	LayoutSections map[string]string // the key is the section name and the value is the template name
	TplPrefix      string
	TplExt         string
	EnableRender   bool

	// xsrf data
	_xsrfToken string
	XSRFExpire int
	EnableXSRF bool

	// session
	CruSession session.Store
}

// 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()
}

应用指南

beego 框架中完成了 controller 基类的设计,在应用具体使用方法看查看:beego.me/docs/mvc/co…

Model 设计

Web 应用中用的一个重要环节就是数据库操作,而 model 层的设计目的就用来做这些操作。

Model 作用

如果应用足够简单,那么 Controller 可以处理一切的逻辑,如果逻辑里面存在着可以复用的东西,那么就抽取出来变成一个模块。因此 Model 就是逐步抽象的过程,一般使用 Model 处理一些数据读取。

beego 的 Model 设计

beego ORM 是一个强大的 Go 语言 ORM 框架。它的设计灵感主要来自 Django ORM 和 SQLAlchemy。已支持数据库驱动包括:MySQL、PostgreSQL、Sqlite3。
安装 ORM:

go get github.com/astaxie/beego/orm

应用指南

beego ORM 的使用方法:beego.me/docs/mvc/mo…

View 设计

在 MVC 的设计模式中,View 负责展现结果。至于 View 层的处理,在很多动态语言里面都是通过在静态 HTML 中插入动态语言生成的数据,例如 JSP、PHP 中通过插入来实现的。在 Go 语言中,使用 template 包来进行模板处理,实现 View 展示。

View 作用

View 可以实现 web 展示和用户交互功能,在视图中其实没有真正的处理发生,它只是作为一种输出数据并允许用户操纵的方式。

beego 的 View 设计

beego 中使用的模板语法,与 go 模板语法基本相同。

应用指南

beego 的模板语法指南:beego.me/docs/mvc/vi…