Go语言动手写Web框架 - Gee第二天 上下文Context
源代码/数据集已上传到 Github - 7days-golang
本文是 7天用Go从零实现Web框架Gee教程系列的第二篇。
- 将
路由(router)
独立出来,方便之后增强。 - 设计
上下文(Context)
,封装 Request 和 Response ,提供对 JSON、HTML 等返回类型的支持。 - 动手写 Gee 框架的第二天,框架代码140行,新增代码约90行
使用效果
为了展示第二天的成果,我们看一看在使用时的效果。
1 | func main() { |
Handler
的参数变成成了gee.Context
,提供了查询Query/PostForm参数的功能。gee.Context
封装了HTML/String/JSON
函数,能够快速构造HTTP响应。
设计Context
必要性
- 对Web服务来说,无非是根据请求
*http.Request
,构造响应http.ResponseWriter
。但是这两个对象提供的接口粒度太细,比如我们要构造一个完整的响应,需要考虑消息头(Header)和消息体(Body),而 Header 包含了状态码(StatusCode),消息类型(ContentType)等几乎每次请求都需要设置的信息。因此,如果不进行有效的封装,那么框架的用户将需要写大量重复,繁杂的代码,而且容易出错。针对常用场景,能够高效地构造出 HTTP 响应是一个好的框架必须考虑的点。
用返回 JSON 数据作比较,感受下封装前后的差距。
封装前
1 | obj = map[string]interface{}{ |
VS 封装后:
1 | c.JSON(http.StatusOK, gee.H{ |
- 针对使用场景,封装
*http.Request
和http.ResponseWriter
的方法,简化相关接口的调用,只是设计 Context 的原因之一。对于框架来说,还需要支撑额外的功能。例如,将来解析动态路由/hello/:name
,参数:name
的值放在哪呢?再比如,框架需要支持中间件,那中间件产生的信息放在哪呢?Context 随着每一个请求的出现而产生,请求的结束而销毁,和当前请求强相关的信息都应由 Context 承载。因此,设计 Context 结构,扩展性和复杂性留在了内部,而对外简化了接口。路由的处理函数,以及将要实现的中间件,参数都统一使用 Context 实例, Context 就像一次会话的百宝箱,可以找到任何东西。
具体实现
1 | type H map[string]interface{} |
- 代码最开头,给
map[string]interface{}
起了一个别名gee.H
,构建JSON数据时,显得更简洁。 Context
目前只包含了http.ResponseWriter
和*http.Request
,另外提供了对 Method 和 Path 这两个常用属性的直接访问。- 提供了访问Query和PostForm参数的方法。
- 提供了快速构造String/Data/JSON/HTML响应的方法。
路由(Router)
我们将和路由相关的方法和结构提取了出来,放到了一个新的文件中router.go
,方便我们下一次对 router 的功能进行增强,例如提供动态路由的支持。 router 的 handle 方法作了一个细微的调整,即 handler 的参数,变成了 Context。
1 | type router struct { |
框架入口
1 | // HandlerFunc defines the request handler used by gee |
将router
相关的代码独立后,gee.go
简单了不少。最重要的还是通过实现了 ServeHTTP 接口,接管了所有的 HTTP 请求。相比第一天的代码,这个方法也有细微的调整,在调用 router.handle 之前,构造了一个 Context 对象。这个对象目前还非常简单,仅仅是包装了原来的两个参数,之后我们会慢慢地给Context插上翅膀。
如何使用,main.go
一开始就已经亮相了。运行go run main.go
,借助 curl ,一起看一看今天的成果吧。
1 | $ curl -i http://localhost:9999/ |
last updated at 2023-11-15