用Golang流行的web框架Gin渲染HTML模板页面的简单例子
Gin是Golang最流行的web框架之一。我之前已经写过如何使用Golang基础模板包渲染HTML页面。使用Gin渲染HTML模板更加简单。
为了使工作流更加顺畅,尝试新的想法并进行调试,我还决定使用Codegangsta的自动重载工具Gin。
安装Gin HTTP web框架就像安装大多数(如果不是所有)Golang包一样简单:
go get -u github.com/gin-gonic/gin
package main
import (
"html/template"
"net/http"
"strings"
"github.com/gin-gonic/gin"
)
func main() {
router :=gin.Default()
router.SetFuncMap(template.FuncMap{
"upper": strings.ToUpper,
})
router.Static("/assets", "./assets")
router.LoadHTMLGlob("templates/*.html")
router.GET("/", func(c *gin.Context) {
c.HTML(http.StatusOK, "index.html", gin.H{
"content": "This is an index page...",
})
})
router.GET("/about", func(c *gin.Context) {
c.HTML(http.StatusOK, "about.html", gin.H{
"content": "This is an about page...",
})
})
router.Run("localhost:8080")
}
在第4到7行,我们引入了一些包:
在第11行,我们创建了一个名为router的默认Gin路由。默认的Gin路由使用日志和恢复中间件,以及基本功能。
在第12到14行,使用SetFuncMap()创建一个供模板使用的函数映射。这里我们添加了一个简单的模板函数upper,它使用strings.ToUpper()函数将字符串中的所有字符设置为大写。
在第15行,我们让Gin路由知道我们在./assets目录中保存了一些静态资产。Gin可以以这种方式访问任何静态资源。
在这个例子中,我在那个目录中放了一个Bulma CSS库的最小版本。通过使用Static()函数,现在HTML模板可以访问这个库。
在第16行,所有满足template/*.html模式的模板都由LoadHTMLGlob()函数加载。这个模式意味着模板文件应该有.html的扩展名,并且位于/template目录中。
在第18到22行,我们告诉Gin路由接受URL路径/上的HTTP GET方法请求。当收到请求时,Gin发送一个HTTP OK状态消息,并用gin.H{}括号内提供的数据渲染index.html模板。在这种情况下,数据只包括一个键/值对,键名为content。
在第24到28行,与上面类似,我们告诉Gin路由接受/about路径上的HTTP GET方法请求。这次渲染的是about.html模板。
在第29行,我们让Gin在localhost端口8080上运行web服务器。
下面你可以找到本例中使用的四个模板。这些模板每个都需要在自己的文件中。每段代码之前都有文件名。
模板的语法与html/template基础包中的相同。你可以我之前的文章中阅读更多关于模板语法的内容。
// header.html
{{define "header.html"}}
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="stylesheet" type="text/css" href="/assets/bulma.min.css">
<title></title>
</head>
<body>
<div class="container">
<br><br>
{{end}}
// footer.html
{{define "footer.html"}}
</div>
</body>
</html>
{{end}}
// index.html
{{template "header.html"}}
<h1 class="title">
INDEX PAGE
</h1>
<p>{{ .content }}</p>
{{template "footer.html"}}
// about.html
{{template "header.html"}}
<h1 class="title">
ABOUT PAGE
</h1>
<p>{{ .content | upper}}</p>
{{template "footer.html"}}
如前所述,我在开发时使用codegangsta/gin工具来自动重载gin。这使得在浏览器中检查我们代码的结果变得更加容易。当我们改变了代码,无需停止当前的可执行文件,重新构建和运行它,就可以做到这一点。
以下是Github页面上对这个工具的描述:
gin是一个简单的命令行工具,用于实时重新加载Go web应用程序。只需在你的应用程序目录下运行gin,你的web应用就会以gin作为代理来服务。gin会在检测到更改时自动重新编译你的代码。下次它接收到HTTP请求时,你的应用将会重启。
go get github.com/codegangsta/gin
gin -i --appPort 8080 --port 3000 run main.go
上述代码需要在命令行中运行。它假设你正在localhost的8080端口上运行你的Go web应用,并且你将使用3000端口作为Gin自动重载代理。
在从命令行运行代码之前,你可能需要运行以下命令:
go mod init
go mod tidy
在浏览器的URL字段中输入以下内容以检查代码的结果:
localhost:3000/
…对于索引页面,和…
localhost:3000/about
对于关于页面。
请记住,始终保持学习的态度,并享受编码的乐趣!祝您编码愉快!
如果你喜欢我的文章,点赞,关注,转发!
在移动端平台开发中,为了增加代码复用,降低开发成本,通常会需要采用跨平台的开发技术,花椒也不例外。本次新的单品开发,由于时间紧,人员有限,经过调研选型,最终确定了 flutter 方案(具体选型过程不在本文讨论之内)。
为了让客户端更专注业务实现,降低接口联调测试成本,我们选用了 gRPC 方案。gRPC是一个高性能、通用的开源 RPC 框架,由 Google 开发并基于 HTTP/2 协议标准而设计,基于 ProtoBuf(Protocol Buffers)序列化协议开发,且支持当前主流开发语言。gRPC通过定义一个服务并指定一个可以远程调用的带有参数和返回类型的的方法,使客户端可以直接调用不同机器上的服务应用的方法,就像是本地对象一样。在服务端,服务实现这个接口并且运行 gRPC 服务处理客户端调用。在客户端,有一个stub提供和服务端相同的方法。
特点
gRPC-Web 为前端浏览器提供了 Javascript 库用来访问 gRPC 服务,但是需要通过 Envoy 提供代理服务。相比 JSON 的方式对前端不够友好,同时也增加了服务端的部署成本。因此在这次项目中前端未使用 gRPC 服务,而是由 gRPC-Gateway 提供代理的 RESTful 接口。
grpc-gateway 是 protoc 的一个插件,它能读取 gRPC 的服务定义并生成反向代理服务器,将 RESTful 的 JSON 请求转换为 gRPC 的方式。这样无需太多工作即可实现一套基于 gRPC 服务的 RESTful 接口,方便前端使用调用接口,同时也方便开发过程中通过 Postman/Paw 之类的工具调试接口。
gateway -> gRPC 映射方式:
例如,gRPC 接口要求的通用的 metadata 参数(如 platform, device_id 等)在 HTTP RESTful 的传递方式如下:
dart
为了便于客户端调用,连接复用及通用参数传递,我们封装了 dart 的基础库。
BaseClient 维护了针对 HOST 缓存的连接池,同时也提供了接口需要传递的 metadata 信息。
golang
golang 后端服务需要同时支持 gRPC 和 gateway 两种请求方式。为了简化部署和上线依赖,gateway 和 gRPC 的功能放在了一起,并通过拦截器注入对应的功能,主要包括 gRPC 统计,访问日志,接口鉴权,请求参数校验,gateway JSON 编码等。
为了提高开发效率,方便维护及模块复用,服务端按功能进行组件化开发。每个组件可以单独运行一个服务,也可以和其它组件共同组成一个服务。每个组件都需要实现 Component 接口:
对应组件开发完成后,需要开发对应的服务容器,步骤如下。
1 base.Init(context.TODO(), cfg, &global.Callback{ 2 Authenticator: &auth.Callback{}, 3 LogCapture: &log.Capture{}, 4 })
base.DefaultServer.AddPublicServer(rpcPort, gatewayPort, setting.TLSConfig)
1 base.DefaultServer.RegisterComponent(&user.Component{}) 2 base.DefaultServer.RegisterComponent(&push.Component{}) 3 ...
base.DefaultServer.Serve()
proto 规范
gRPC 基于标准化的 IDL(ProtoBuf)来生成服务器端和客户端代码,我们决定将所有的接口描述及文档说明都放到 proto 文件中,便于查看及修改。对 proto 的接口描述及注释的规范如下:
代码生成
golang
1 gengo: 2 @protoc -Iproto \ 3 -I${GOPATH}/src/github.com/grpc-ecosystem/grpc-gateway/third_party/googleapis \ 4 -I${GOPATH}/src/github.com/lnnujxxy/protoc-gen-validate \ 5 -I${GOPATH}/src/github.com/youlu-cn/grpc-gen/protoc-gen-auth \ 6 --go_out=plugins=grpc:go/pb \ 7 --grpc-gateway_out=logtostderr=true:go/pb \ 8 --validate_out="lang=go:go/pb" \ 9 --auth_out="lang=go:go/pb" \ 10 proto/*.proto
golang 使用 go mod 的方式直接引入 pb 生成的 .go 文件
dart
修改 pubspec.yaml,执行 flutter packages get 或 flutter packages upgrade
1 dependencies: 2 flutter: 3 sdk: flutter 4 5 protobuf: ^0.13.4 6 grpc: ^1.0.1 7 user: 8 git: 9 url: git@github.com:project/repo.git 10 path: dart/user
gRPC gateway 提供了通过 proto 文件生成 swagger API 文档,缺点是只支持 gateway 的 RESTful 接口,并且默认的展示方式有点不符合我们的常规文档使用方式。
我们基于 protoc 插件开发了 protoc-gen-markdown 工具,可以由 proto 文件生成 markdown 文档,提供 gRPC 接口描述,以及 RESTful 接口描述及 JSON 示例,提供全文目录,支持锚点导航等。生成方式如下:
1gendoc: 2 @protoc -Iproto \ 3 -I${GOPATH}/src/github.com/grpc-ecosystem/grpc-gateway/third_party/googleapis \ 4 -I${GOPATH}/src/github.com/lnnujxxy/protoc-gen-validate \ 5 -I${GOPATH}/src/github.com/youlu-cn/grpc-gen/protoc-gen-auth \ 6 --markdown_out=":doc" \ 7 proto/*.proto
文档会在对应路径生成接口列表 README.md,以及每个 protobuf 对应的接口文档。
传统的 RESTful 接口在调试及问题排查时,可以通过抓包或者 MitM(中间人攻击)的方式,配置也比较容易。而 gRPC 因为使用了 HTTP2 及 protobuf 二进制流,抓包及数据流反解难度相对较高,调试及问题排查时会比较复杂。为了解决这个问题,我们通过服务端注入的方式,配合查询后台过滤对应的请求日志,从而实现如下类似抓包的效果。
Go语言中文网,致力于每日分享编码、开源等知识,欢迎关注我,会有意想不到的收获!
本文由花椒服务端团队原创授权发布
近在给项目代码完善单元测试,发现go语言单元测试相关的资料都是零零散散的,所以在这儿整理总结一下。项目中使用的是goconvey+monkey+sqlmock (项目的web框架为gin, 持久层框架为gorm), 使用时也碰到一些坑,也会在这篇文章中做一些相关的记录。 文章大约4200字,囿于篇幅,很多地方都是一笔带过,不过在每一部分之后提供了一些笔者读过觉得不错的资料的链接,大家可以根据需要查看。
1.1 testing——Go内置的单元测试库。
要编写一个新的测试,需要创建一个以 _test.go 结尾的文件,该文件包含 TestXxx 函数。 将该文件放在与被测试的包相同的包中。
通过 go test 命令,能够自动执行如下形式的任何函数:
func TestXxx(*testing.T)
注意:Xxx 可以是任何字母数字字符串,但是第一个字母不能是小写字母(一般接被测试函数名字,不强求)。传递给测试函数的参数是 *testing.T 类型。它用于管理测试状态并支持格式化测试日志。测试日志会在执行测试的过程中不断累积,并在测试完成时转储至标准输出。
详情参见:The-Golang-Standard-Library-by-Example
https://books.studygolang.com/The-Golang-Standard-Library-by-Example/chapter09/09.1.html
1.2 TestMain
在写测试时,有时需要在测试之前或之后进行额外的设置(setup)或拆卸(teardown);有时,测试还需要控制在主线程上运行的代码。为了支持这些需求,testing 提供了 TestMain 函数:
func TestMain(m *testing.M)
如果测试文件中包含该函数,那么生成的测试将调用 TestMain(m),而不是直接运行测试。
TestMain 运行在主 goroutine 中, 可以在调用 m.Run 前后做任何设置和拆卸。注意,在 TestMain 函数的最后,应该使用 m.Run 的返回值作为参数调用 os.Exit。
详情参见:TestMain
https://books.studygolang.com/The-Golang-Standard-Library-by-Example/chapter09/09.5.html#testmain
1.3 httptest——HTTP测试辅助工具
Go 标准库专门提供了 httptest 包专门用于进行 http Web 开发测试。
httptest包最关键的是提供了一个 http.ReponseWriter接口的实现结构:httptest.ReponseRecorder,通过它可以得到一个http.ReponseWriter,并以此来接收服务器返回的响应包。
详情参见:httptest - HTTP 测试辅助工具
https://books.studygolang.com/The-Golang-Standard-Library-by-Example/chapter09/09.6.html
1.4 测试覆盖率
Go 从 1.2 开始,引入了测试覆盖率的支持,使用的是 cover 相关的工具(go test -cover、go tool cover)。
详情参见:The cover story
https://blog.golang.org/cover
Go标准包里并没有断言库,但是不使用断言库进行结果校验的话,测试代码将会变得非常臃肿,可读性和可维护性都会很差。不过好在有第三方框架可以让我们使用。
2.1 testify
github地址:https://github.com/stretchr/testify
特性:
2.2 gocheck
godoc地址:https://godoc.org/gopkg.in/check.v1
特性:
详情参见:gocheck 使用介绍
https://zhuanlan.zhihu.com/p/45570168
2.3 goconvey
github地址: https://github.com/smartystreets/goconvey
特性:
详情参见:GoConvey框架使用指南
https://www.jianshu.com/p/e3b2b1194830
2.4 比较
其实gocheck我没怎么用过,只是当时调研的时候看到了,在断言方面看起来和其他的差不多。
testify和goconvey都有尝试,最后采用的是goconvey,所以对goconvey更熟悉一点。
3.1 识别依赖
普遍来说,我们遇到最常见的依赖无非下面几种:
3.2 mock和stub的区别
这个话题也算是老生常谈了。几句话很难解释清楚,有兴趣可以阅读Martin Fowler的文章。
stub本质上是对真实对象的一个模拟,比如调用者需要一个值,那就让stub输出一个值,如果调用者需要传递一个值给stub,那就在stub中定义一个方法接受该参数,相当于“依赖部分”的一个简化实现。mock则是在程序代码中向被测试代码注入“依赖部分”,模拟出函数调用返回的结果。
个人认为两者最大的区别在于依赖对象是否和被测对象有交互,从结果来看,stub不会使测试失败,它只是为被测对象提供依赖的对象,并不改变测试结果,而mock则会根据不同的交互测试要求,很可能会更改测试的结果。stub是state-based,关注的是输入和输出。mock是interaction-based,关注的是交互过程。
mock和stub还有一个重要的区别就是expectiation。对于mock来说,expectiation是重中之重:我们期待方法有没有被调用,期待适当的参数,期待调用的次数,甚至期待多个mock之间的调用顺序。所有的一切期待都是事先准备好,在测试过程中和测试结束后验证是否和预期的一致。而对于stub,通常都不会关注expectiation,没有任何代码来帮助判断这个stub类是否被调用。虽然理论上某些stub实现也可以通过自己编码的方式增加对expectiation的内容,比如增加一个计数器,每次调用+1之类,但是实际上极少这样做。
在Go中,如果要用stub,那将是侵入式的,必须将代码设计成可以用stub方法替换的形式。为了测试,需要专门用一个全局变量 来保存具有外部依赖的方法。然而在不提倡使用全局变量的Go语言当中,这显然是不合适的。所以,并不提倡这种Stub方式。
但其实这两种方法并不是割裂的,例如像下文提到的gomock框架除了像其名字一样可以mock对象以外,还提供了stub的功能。软件工程没有银弹,我们需要根据合适的场景选用合适的方法,甚至可以结合多种方法使用。
详情参见:Mocks Aren't Stubs(Martin Fowler)
https://martinfowler.com/articles/mocksArentStubs.html
以及 中文翻译
https://www.cnblogs.com/anf/archive/2006/03/27/360248.html
3.3 gostub
github地址:https://github.com/prashantv/gostub
特性:
缺陷:
详情参见:GoStub框架使用指南
3.4 gomock
github地址:https://github.com/golang/mock
特性:
缺陷:
详情参见:使用Golang的官方mock工具—gomock
https://www.jianshu.com/p/598a11bbdafb
和 GoMock框架使用指南
https://www.jianshu.com/p/f4e773a1b11f
3.5 gomonkey
github地址:https://github.com/bouk/monkey
特性:
缺陷:
详情参见:Monkey框架使用指南
https://www.jianshu.com/p/2f675d5e334e
3.6 sqlmock
github地址: https://github.com/DATA-DOG/go-sqlmock
特性:
缺陷:
3.7 httpexpect
github地址:https://github.com/gavv/httpexpect
特性:
sqlmock和httpexpect都蛮简单的,看完github主页的QuickStart基本就会用了~~
4.1 选择原因
newDB=MysqlDB.ModelTable(c, &Basexxx{}, c.AppID()).Where("type=?", libType).Limit(limit).Offset(offset).Order("created_at desc").Find(&libxxxs)
4.2 gorm+sqlmock使用方法
初始化sqlmock后,然后使用dialect和dsn打开一个新的gorm连接并赋值给数据库操作实例
_, mock, _=sqlmock.NewWithDSN("sqlmock_db") MysqlDB.DB, _=gorm.Open("sqlmock", "sqlmock_db")
接下来就和sqlmock的普通使用没什么区别了,只要mock时能够成功的匹配gorm生成的sql语句即可
详情参见:Stub database connection with GORM
https://blog.valletta.io/blog/2018-07-05-stub-database-connection-with-gorm/
4.3 踩坑记录(持续更新~)
func A(arg string) error { return B(arg) }
原因:
在不改动原有代码的情况下,有2种解决方案:
5.1 单元测试的粒度
对于刚开始做单元测试的同学来说,如何把握单元测试的粒度是一个让人头疼的问题。
测试粒度做的太细,会耗费大量的开发以及维护时间,每改一个方法,都要改动其对应的测试方法。当发生代码重构的时候那简直就是噩梦(因为所有的单元测试又都要写一遍了…)。
如果单元测试粒度太粗,一个测试方法测试了n多方法,那么单元测试将显的非常臃肿,脱离了单元测试的本意,容易把单元测试写成集成测试。
5.2 单元测试的成本和收益
在受益于单元测试的好处的同时,也必然增加了代码量以及维护成本。
下面这张成本/价值象限图清晰阐述了在不同性质的系统中单元测试的成本和价值之间的关系。
参考链接
下面是一些其他的参考资料:
1. The-Golang-Standard-Library-by-Example
https://books.studygolang.com/The-Golang-Standard-Library-by-Example/
2. 搞定Go单元测试(一)——基础原理
https://juejin.im/post/5ce93447e51d45775746b8b0#heading-12
*请认真填写需求信息,我们会在24小时内与您取得联系。