浅析GO语言中的beego框架
https://twitter.com/ThePracticalDev/status/930878898245722112
如果你只是想写一个自己用的小网站,或许你不需要框架,但如果你是要开发一个投入生产运营的网站,那么你肯定会需要一个框架,而且是需要一个好的 Web 框架。
如果你已经掌握所有必要的知识和经验,你会冒险自己去重新开发所有的功能么?你有时间去找满足生产级别要求的库来用于开发么?另外,你确定这个库可以满足你后续所有的要求?
这些都是促使我们去使用框架的原因,哪怕是那些最牛的开发者也不会一直想要重新造轮子,我们可以站在前人的肩膀上,走得更快更好。
介绍
Go 是一门正在快速增长的编程语言,专为构建简单、快速且可靠的软件而设计。 点击 此处 查看有哪些优秀的公司正在使用 Go 语言来驱动他们的业务。
本文将会提供一切必要的信息来帮助开发人员了解更多关于使用 Go 语言来开发 Web 应用程序的最佳选择。
本文包含了最详尽的框架比较,从流行度、社区支持及内建功能等多个不同角度出发做对比。
Beego:开源的高性能 Go 语言 Web 框架。
Buffalo:使用 Go 语言快速构建 Web 应用。
Echo:简约的高性能 Go 语言 Web 框架。
Gin:Go 语言编写的 Web 框架,以更好的性能实现类似 Martini 框架的 API。
Iris:全宇宙最快的 Go 语言 Web 框架。完备 MVC 支持,未来尽在掌握。
Revel:Go 语言的高效、全栈 Web 框架。
流行度
按照流行度排行(根据 GitHub Star 数量)
https://github.com/speedwheel/awesome-go-web-frameworks/blob/master/README.md#popularity
学习曲线
https://github.com/speedwheel/awesome-go-web-frameworks/blob/master/README.md#learning-curve
astaxie 和 kataras 分别为 Beego 和 Iris 做了超棒的工作,希望其他的框架也能迎头赶上,为开发者提供更多的例子。至少对于我来说,如果我要切换到一个新的框架,那些例子就是最丰富的资源,来获取尽可能多的有用信息。一个实例胜千言啊。
核心功能
根据功能支持的多寡排行
https://github.com/speedwheel/awesome-go-web-frameworks/blob/master/README.md#core-features
几个知名的 Go 语言 Web 框架并不是真正意义上的框架,也就是说: Echo,Gin 和 Buffalo并不是真正意义上的 Web 框架(因为没有完备支持所有功能)但是大部分的 Go 社区认为它们是的,因此这些框架也可以和 Iris,Beego 或 Revel 做比较。所以,我们有义务将这几个框架(Echo,Gin 和 Buffalo)也列在这个表中。
以上所有这些框架,除了 Beego 和 Revel 之外,都可以适配任意 net/http
中间件。其中一部分框架可以轻松地做适配,另外一些可能就需要额外的努力 [即使这里的痛苦不是一定的]。
技术性词汇
路由:命名的路径参数和通配符
可以处理动态的路径。
命名的路径参数例子:
"/user/{username}" 匹配 "/user/me","/user/speedwheel" 等等
上面路径参数 username
的值分别是 "me"
和 "speedwheel"
。
通配符的例子:
"/user/{path *wildcard}" 匹配 "/user/some/path/here", "/user/this/is/a/dynamic/multi/level/path" 等等
上面的路径参数 path
对应的分别是 "some/path/here"
和 "this/is/a/dynamic/multi/level/path"
。
Iris 也支持一个叫 macros
的功能,它可以被表示为 /user/{username:string}
或者 /user/{username:int min(1)}
。
路由:正则表达式
过滤动态的路径。
例如:
"/user/{id ^[0-9]$}" 能匹配 "/user/42" ,但不会匹配 "/user/somestring"
这里的路径参数 id
的值为 42
。
路由:分组
通过共用逻辑或中间件来处理有共同前缀的路径组。
例如:
myGroup := Group("/user", userAuthenticationMiddleware) myGroup.Handle("GET", "/", userHandler) myGroup.Handle("GET", "/profile", userProfileHandler) myGroup.Handle("GET", "/signup", getUserSignupForm)
- /user
- /user/profile
- /user/signup
你甚至可以从一个组中创建子分组:
myGroup.Group("/messages", optionalUserMessagesMiddleware) myGroup.Handle("GET', "/{id}", getMessageByID)
- /user/messages/{id}
路由:上述所有规则相结合而没有冲突
这是一个高级且有用的的功能,我们许多人都希望路由模块或 Web 框架能支持这点,但目前,在 Go 语言框架方面,只有 Iris 能支持这一功能。
这意味着类似如 /{path *wildcard}
, /user/{username}
和 /user/static
以及 /user/{path *wildcard}
等路径都可以在同一个路由中通过静态路径(/user/static
)或通配符(/{path *wildcard}
)来正确匹配。
路由:自定义 HTTP 错误
指可以自行处理请求错误的情况。 Http 的错误状态码均 >=400
,比如 NotFound 404
,请求的资源不存在。
例如:
OnErrorCode(404, myNotFoundHandler)
上述的大多数 Web 框架只支持 404
,405
及 500
错误状态的处理,但是例如 Iris,Beego 和 Revel
等框架,它们完备支持 HTTP 错误状态码,甚至支持 any error
任意错误。( any error
— 任意错误,只有 Iris 能够支持)。
100% 兼容 net/http
这意味著:
- 这些框架能够让你直接获取
*http.Request
和http.ResponseWriter
的所有相关信息。 - 各框架提供各自相应处理
net/http
请求的方法。
中间件生态系统
框架会为你提供一个完整的引擎来定义流程、全局、单个或一组路由,而不需要你自己用不同的中间件来封装每一部分的处理器。框架会提供比如 Use(中间件)、Done(中间件) 等函数。
类 Sinatra 的 API 设计(译者注:Sinatra 是一门基于 Ruby 的领域专属语言)
可以在运行时中注入代码来处理特定的 HTTP 方法 (以及路径参数)。
例如:
.Get or GET("/path", gethandler) .Post or POST("/path", postHandler) .Put or PUT("/path", putHandler) and etc.
服务器程序:默认启用 HTTPS
框架的服务器支持注册及自动更新 SSL 证书来管理新传入的 SSL/TLS 连接 (https)。 最著名的默认启用 https 的供应商是 letsencrypt。
服务器程序:平滑关闭(Gracefully Shutdown)
当按下 CTRL + C
关闭你的终端应用程序时,服务器将等待 (一定的等待时间)其他的连接完成相关任务或触发一个自定义事件来做清理工作(比如:关闭数据库),最后平滑地停止服务。
服务器程序:多重监听
框架的服务器支持自定义的 net.Listener
或可以启动一个有多个 http 服务和地址的 Web 应用。
完全支持 HTTP/2
框架可以很好地支持处理 https 请求的 HTTP/2 协议,并且支持服务器 Push
功能。
子域名
你可以直接在你的 Web 应用中注入子域名的路径。
辅助功能(secondary)
意味着这个功能并不被这个框架原生支持,但是你仍旧可以通过启用多个 http 服务器来实现。这样做的缺点在于:主程序和子域名程序之间并不是连通的,默认情况下,它们不能共享逻辑。
会话(Sessions)
支持 http sessions,且可以在自定义的处理程序中使用 sessions。
- 有一些 Web 框架支持后台数据库来储存 sessions,以便在服务器重启之后仍旧能获得持久的 sessions。
- Buffalo 使用 gorilla 的 sessions 库,它比其他框架的实现略微慢了一点。
例如:
func setValue(context http_context){ s := Sessions.New(http_context) s.Set("key", "my value") } func getValue(context http_context){ s := Sessions.New(http_context) myValue := s.Get("key") } func logoutHandler(context http_context){ Sessions.Destroy(http_context) }
Wiki: https://en.wikipedia.org/wiki/Hypertext_Transfer_Protocol#HTTP_session
网络套接字(Websockets)
框架支持 websocket 通信协议。不同的框架对于这点有各自不同的实现方式。
你应该通过它们的例子来看看哪个适合你。我的一个同事,在试过了上述所有框架中的 websocket 功能之后告诉我:Iris 实现了最多的 websocket 特性,并且提供了相对更容易使用的 API 。
Wiki: https://en.wikipedia.org/wiki/WebSocket
程序内嵌对视图(又名模版)的支持
通常情况下,你必须根据 Web 应用的可执行文件一一对应地转换模版文件。内嵌到应用中意味着这个框架集成了 go-bindata ,因此在最终的可执行文件中可以以 []byte
的形式将模版包含进来。
什么是视图引擎
框架支持模版加载、自定义及内建模版功能,以此来节省我们的开发时间。
视图引擎:STD
框架支持标准的 html/template
解析器加载模版。
视图引擎:Pug
框架支持 Pug
解析器加载模版。
视图引擎:Django
框架支持 Django
解析器加载模版。
视图引擎:Handlebars
框架支持 Handlebars
解析器加载模版。
视图引擎:Amber
框架支持 Amber
解析器加载模版。
渲染:Markdown, JSON, JSONP, XML…
框架提供一个简单的方法来发送和自定义各种内容类型的响应。
MVC
Model–view–controller (MVC) 模型是一种用于在计算机上实现用户界面的软件架构模式,它将一个应用程序分为互相关联的三部分。这样做的目的是为了:将信息的内部处理逻辑、信息呈现给用户以及从用户获取信息三者分离。MVC 设计模式将这三个组件解耦合,从而实现高效的代码复用和并行开发。
- Iris 支持完备的 MVC 功能, 可以在运行时中注入。
- Beego 仅支持方法和数据模型的匹配,可以在运行时中注入。
- Revel 支持方法,路径和数据模型的匹配,只可以通过生成器注入(生成器是另外一个不同的软件用于构建你的 Web 应用)。
Wiki: https://en.wikipedia.org/wiki/Model%E2%80%93view%E2%80%93controller
缓存
Web 缓存(或 http 缓存)是一种用于临时存储(缓存)网页文档,如 HTML 页面和图像,来减缓服务器延时。一个 Web 缓存系统缓存网页文档,使得后续的请求如果满足特定条件就可以直接得到缓存的文档。Web 缓存系统既可以指设备,也可以指软件程序。
Wiki: https://en.wikipedia.org/wiki/Web_cache
文件服务器
可以注册一个(物理的)目录到一个路径,使得这个路径下的文件可以自动地提供给客户端。
文件服务器:内嵌入应用
通常情况下,你必须将所有的静态文件(比如静态资产,assets:CSS,JavaScript 文件等)与应用程序的可执行文件一起传输。支持此项功能的框架为你提供了在应用中,以 []byte
的形式,内嵌所有这些数据的机会。由于服务器可以直接使用这些数据而无需在物理位置查找文件,它们的响应速度也将更快。
响应可以在发送前的生命周期中被多次修改
目前只有 Iris 通过 http_context 中内建的的响应写入器(response writer)支持这个功能。
当框架支持此功能时,你可以在返回给客户端之前检索、重置或修改状态码、正文(body)及头部(headers)。默认情况下,在基于 net/http
的 Web 框架中这是不可能的,因为正文和状态码一经写定就不能被检索或修改。
Gzip
当你在一个路由的处理程序中,并且你可以改变响应写入器(response writer)来发送一个用 gzip 压缩的响应时,框架会负责响应的头部。如果发生任何错误,框架应该把响应重置为正常,框架也应该能够检查客户端是否支持 gzip 压缩。
gzip 是用于压缩和解压缩的文件格式和软件程序。
Wiki: https://en.wikipedia.org/wiki/Gzip
测试框架
可以使用框架特定的库,来帮助你轻松地编写更好的测试代码来测试你的 HTTP 。
例如(目前仅 Iris 支持此功能):
func TestAPI(t *testing.T) { app := myIrisApp() tt := httptest.New(t, app) tt.GET("/admin").WithBasicAuth("name", "pass").Expect(). Status(httptest.StatusOK).Body().Equal("welcome") }
myIrisApp
返回你虚构的 Web 应用,它有一个针对 /admin
路径的 GET 方法,它有基本的身份验证逻辑保护。
上面这个简单的测试,用 "name"
和 "pass"
通过身份验证并访问 GET /admin
,检查它的响应状态是否为 Status OK
,并且响应的主体是否为 "welcome"
。
TypeScript 转译器
TypeScript 的目标是成为 ES6 的超集。除了标准定义的所有新特性外,它还增加了静态类型系统。TypeScript 还有转换器用于将 TypeScript 代码(即 ES6 + 类型)转换为 ES5 或 ES3 JavaScript 代码,如此我们就可以在现今的浏览器中运行这些代码了。
在线编辑器
在在线编辑器的帮助下,你可以快速轻松地在线编译和运行代码。
日志系统
自定义日志系统通过提供有用的功能,如彩色日志输出、格式化、日志级别分离及不同的日志记录后端等,来扩展原生日志包。
维护和自动更新
以非侵入的方式通知框架的用户即时更新。
浅析GO语言中的beego框架
beego是一个快速开发Go应用的http框架,作者是SegmentFault 用户,go 语言方面技术大牛。beego可以用来快速开发API、Web、后端服务等各种应用,是一个RESTFul的框架,主要设计灵感来源于tornado、sinatra、flask这三个框架,但是结合了Go本身的一些特性(interface、struct继承等)而设计的一个框架。
架构
beego是基于八大独立的模块之上构建的,是一个高度解耦的框架。当初设计beego的时候就是考虑功能模块化,用户即使不适用beego的http逻辑,也是可以在使用这些独立模块,例如你可以使用cache模块来做你的缓存逻辑,使用日志模块来记录你的操作信息,使用config模块来解析你各种格式的文件,所以不仅仅在beego开发中,你的socket游戏开发中也是很有用的模块,这也是beego为什么受欢迎的一个原因。大家如果玩过乐高的话,应该知道很多高级的东西都是一块一块的积木搭建出来的,而设计beego的时候,这些模块就是积木,高级机器人就是beego。至于这些模块的功能以及如何使用会在后面的文档会逐一介绍。
执行逻辑
既然beego是基于这些模块构建的,那么他的执行逻辑是怎么样的呢?beego是一个典型的MVC架构,他的执行逻辑如下图所示:
项目结构
一般的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是入口文件。
选择 Go 语言
断断续续看了 Go 几个星期了,讲真的真是喜欢的不得了。认真学过之后,你会觉得非常的优雅,写东西很舒服。学习 Go 我觉得很有必要的是,Go 中自带的数据结构很少,类似于 List 或者 Tree 之类的,最好尝试一下如何去设计一些常用的数据结构。话说回来,Go 的出身始终是一门后端语言。我非常后悔用 Flask 或者 Django 来作为我的后端入门框架或者选择。封装的太好了,往往对于一个入门新手来说学习不到什么。
而 Go 就不一样了,它天生被设计是一门后端语言。也就是说,你将会学习到非常多的后端知识。看看下面这一张图,当时我看着就有一种很过瘾的感觉,因为这些知识你都知道,但是作为一个后端开发者你没有去了解过,这是非常大的失误。并不是想去用学习好 Go 去弥补没有学习好 C++ 的遗憾,只是新生事物,多尝试尝试总是极好的,哪怕只是浅尝辄止。Go 作为一门新的语言,其语言设计的特性,背后 Google 爸爸的撑腰以及现在 Docker 技术发展,前景应该还是不错的。所以如果你是编程新手或者是想入门后端的开发者,我强烈建议你选择 Go 语言。
语言学到最后,框架总是少不了的。虽然不能完全依赖框架,但是还是可以学习一下框架的设计思想。对于 Beego 框架的评价总是各种各样,这也要看自己的选择了。之所以选择 Beego 框架来入门,主要是因为其详细的文档以及教程示例非常多。
Go Web 初探
先看一下最基本的 Go 中的 web 服务,只用到了 Go 中的 net/http 这个包:
package main import ( "fmt" "net/http" "strings" "log" ) func sayhelloName(w http.ResponseWriter, r *http.Request) { r.ParseForm() //解析参数,默认是不会解析的 fmt.Println(r.Form) //这些信息是输出到服务器端的打印信息 fmt.Println("path", r.URL.Path) fmt.Println("scheme", r.URL.Scheme) fmt.Println(r.Form["url_long"]) for k, v := range r.Form { fmt.Println("key:", k) fmt.Println("val:", strings.Join(v, "")) } fmt.Fprintf(w, "Hello astaxie!") //这个写入到w的是输出到客户端的 } func main() { http.HandleFunc("/", sayhelloName) //设置访问的路由 err := http.ListenAndServe(":9090", nil) //设置监听的端口 if err != nil { log.Fatal("ListenAndServe: ", err) } }
安装 Go 以及 Beego
基本的你得有个 Go 语言的环境,安装什么的就不讲了。只是最后配置其环境变量许多人都容易弄错,包括我自己也是。其实多次也能够配置好,只是每次重新启动就提示上次的配置无效,也不知道是怎么回事。讲一下安装 Beego 框架
export GOBIN="/usr/local/go/bin" export GOPATH="/Users/allenwu/GoProjects" export PATH="$PATH:$GOBIN:$GOPATH/bin"
在终端输入如上所示代码,其中 allenwu 替换成你自己的 username,并且在根目录下创建 GoProjects 文件夹,作为下一步工作目录。配置好之后,输入如下命令确保保存成功:
source ~/.zshrc
最后当然要测试一下环境变量是否配置成功,在 shell 中输入 go env
若如下所示即表明成功(着重注意一下的就是 gopath 是不是为空):
配置环境没问题之后,就是安装 go 和 bee 工具了:
$ go get github.com/astaxie/beego $ go get github.com/beego/bee
检查安装是否成功,启动一个 Hello world
级别的 App:
$ cd $GOPATH/src $ bee new hello $ cd hello $ bee run hello
会提示你在浏览器输入地址,然后就能知道是否安装成功啦。
体验 Beego 框架
如下图所示即为 Beego 官网所提供的 Beego 框架概览,一眼就能明白其 MVC 模式的构造,结构也是非常清晰的。
安装好 Beego 框架之后,官方给了三个 samples,我们选择其中一个来进行入门体验一下。如下实例选择的是 todo App。我们将 clone 下来的 todo App 放置到指定目录下,用 sublimeText3 打开这个示例项目,强烈建议你打开侧边栏设置选项:
了解完基本的结构之后,我们启动这个 App。我们采用 Beego 提供的工具 bee 来启动。进入到最终的指定文件夹 todo 之后,执行 bee run 命令:
可以看到打印出一个 Bee 的 Logo,表示启动成功了,稍等一下就会继续提示你在浏览器中输入指定 IP 地址和端口号,也就是如下所示:
官方讲这个小 App 结合了 Angular ,体验还是挺不错的。接下来我们来简单分析一下示例 App 的代码结构。首先入口程序是 Main.go,这是想都不用想的,一个程序员的直觉:
package main import ( "github.com/astaxie/beego" // 注释一 "samples/todo/controllers" ) func main() { // 注释二 beego.Router("/", &controllers.MainController{}) beego.Router("/task/", &controllers.TaskController{}, "get:ListTasks;post:NewTask") beego.Router("/task/:id:int", &controllers.TaskController{}, "get:GetTask;put:UpdateTask") // 注释三 beego.Run() }
我们第一感觉还是看到 Main() 函数,看到了 Router() 函数,有一些 web 开发或者开发经验的应该都知道这是路由机制。对应的是 url 与 Controller。在 MVC 框架中 Controller 是一个很重要的概念了。我们自然下一步骤就是去往 Controller 中看看:
package controllers import ( "github.com/astaxie/beego" ) // 注释一 type MainController struct { beego.Controller } // 注释二 func (this *MainController) Get() { // 注释三 this.TplName = "index.html" this.Render() }
在看完 Go 的基本语法之后,看到注释一应该也能明白一个一二三四了,我们声明了一个控制器 MainController,这个控制器里面内嵌了 beego.Controller,这就是 Go 的嵌入方式,也就是 MainController 自动拥有了所有 beego.Controller 的方法。而 beego.Controller 拥有很多方法,其中包括 Init、Prepare、Post、Get、Delete、Head等 方法。我们可以通过重写的方式来实现这些方法,而我们上面的代码就是重写了 Get 方法。
在注释三处,我们看到了 index.html,应该明白了 Get 方法去获取对应名称的 HTML 文件,并进行渲染。到这里我们很简单的讲述了一下 MVC 中的 V 和 C,发现没,Model 竟然不知道从哪去讲。还请回头看看 Main.go 中的注释二处:
beego.Router("/task/", &controllers.TaskController{}, "get:ListTasks;post:NewTask")
上述路由引导我们进入了 TaskController 这个控制器来了,我们分析一下下面这个文件:
package controllers import ( "encoding/json" "strconv" "github.com/astaxie/beego" "samples/todo/models" ) type TaskController struct { beego.Controller } // Example: // // req: GET /task/ // res: 200 {"Tasks": [ // {"ID": 1, "Title": "Learn Go", "Done": false}, // {"ID": 2, "Title": "Buy bread", "Done": true} // ]} func (this *TaskController) ListTasks() { res := struct{ Tasks []*models.Task }{models.DefaultTaskList.All()} this.Data["json"] = res this.ServeJSON() }
很明显我们看到了 models 关键字,并且调用了其中的 Task ,我们选择进入 Task.go 文件看看:
package models import ( "fmt" ) var DefaultTaskList *TaskManager // Task Model type Task struct { ID int64 // Unique identifier Title string // Description Done bool // Is this task done? } // NewTask creates a new task given a title, that can't be empty. func NewTask(title string) (*Task, error) { if title == "" { return nil, fmt.Errorf("empty title") } return &Task{0, title, false}, nil } // TaskManager manages a list of tasks in memory. // 注释一 type TaskManager struct { tasks []*Task lastID int64 } // NewTaskManager returns an empty TaskManager. func NewTaskManager() *TaskManager { return &TaskManager{} } // Save saves the given Task in the TaskManager. func (m *TaskManager) Save(task *Task) error { if task.ID == 0 { m.lastID++ task.ID = m.lastID m.tasks = append(m.tasks, cloneTask(task)) return nil } for i, t := range m.tasks { if t.ID == task.ID { m.tasks[i] = cloneTask(task) return nil } } return fmt.Errorf("unknown task") } // cloneTask creates and returns a deep copy of the given Task. func cloneTask(t *Task) *Task { c := *t return &c } // All returns the list of all the Tasks in the TaskManager. func (m *TaskManager) All() []*Task { return m.tasks } // Find returns the Task with the given id in the TaskManager and a boolean // indicating if the id was found. func (m *TaskManager) Find(ID int64) (*Task, bool) { for _, t := range m.tasks { if t.ID == ID { return t, true } } return nil, false } func init() { DefaultTaskList = NewTaskManager() }
如上所示的 task Model 主要就是定义了 task 这个实体该有的成员变量。以及一个 taskManager 来管理这些 task,其整体结构在理解了 Go 语言的一些基本的机制之后还是比较简单的。
在之前的整个实例结构中,我们还看到了如下所示的静态文件,它们的作用就很明显啦:
├── static │ ├── css │ ├── img │ └── js
好了,以上就是 Go 的一个框架 Beego 的入门实例了,其实很简单。我也只是简单的写一下入门的东西。后续研究一下 Go 的自动化 API 构建。往后继续学习 Go 和 Docker 的结合应用吧。
【参考文章】
1、Go中国技术社区 – golang https://gocn.io/
2、首页 – beego: 简约 & 强大并存的 Go 应用框架 https://beego.me/