03.Mygin实现上下文
本篇是Mygin的第三篇
目的
- 将路由独立出来,方便后续扩展修改
- 上下文Context,对http.ResponseWriter和http.Request进行封装,实现对JSON、HTML等的支持
路由
新建一个router文件,将 Mygin实现简单的路由 中将路由部分复制出来
- 新建Mygin/router.go
1 | package mygin |
- 修改Mygin/engine.go文件
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36package mygin
import (
"net/http"
)
type Engine struct {
Router
}
func (e *Engine) ServeHTTP(w http.ResponseWriter, r *http.Request) {
if tree, ok := e.trees[r.Method]; ok {
if handler, ok := tree.paths[r.URL.Path]; ok {
handler(w, r)
return
}
}
w.Write([]byte("404 Not found!\n"))
}
// Default returns an Engine
func Default() *Engine {
return &Engine{
Router{
trees: make(methodTrees, 2),
}}
}
// Run 启动方法start a http server
func (e *Engine) Run(addr string) {
err := http.ListenAndServe(addr, e)
if err != nil {
return
}
} - 相当于engine继承router,engine可以调用router中的方法。
文件拆分开来,就不那么乱了。以后路由相关的功能就在router文件中编写,更加清晰。
上下文
对于1个请求来说,无法就是接收http.Request的数据处理,然后将处理后的数据http.ResponseWriter返回。虽然响应的消息体(Body)不一样,但对于同一种类型比如JSON类型,消息头(Header)和消息体(Body),而 Header 包含了状态码(StatusCode),消息类型(ContentType)等几乎每次请求都需要重新写,所以进行封装,对于后续扩展和修改都非常必要。
用返回 JSON 数据作比较,感受下封装前后的差距。
封装前
1 | obj = map[string]interface{}{ |
封装后
1 | c.JSON(http.StatusOK, map[string][string]{ |
封装http.Request和*http.ResponseWriter**的方法,简化相关接口的调用,只是设计 Context 的原因之一。对于框架来说,还需要支撑额外的功能。例如,将来解析动态路由/hello/:name,参数:name的值放在哪呢?再比如,框架需要支持中间件,那中间件产生的信息放在哪呢?Context 随着每一个请求的出现而产生,请求的结束而销毁,和当前请求强相关的信息都应由 Context 承载。因此,设计 Context 结构,扩展性和复杂性留在了内部,而对外简化了接口。路由的处理函数,以及将要实现的中间件,参数都统一使用 Context 实例, Context 就像一次会话的百宝箱,可以找到任何东西。
- 新建mygin/context.go文件
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49package mygin
import (
"encoding/json"
"net/http"
)
type Context struct {
Request *http.Request
Writer http.ResponseWriter
}
func writeContentType(w http.ResponseWriter, value []string) {
header := w.Header()
if val := header["Content-Type"]; len(val) == 0 {
header["Content-Type"] = value
}
}
func (c *Context) Status(code int) {
c.Writer.WriteHeader(code)
}
func (c *Context) JSON(v interface{}) error {
writeContentType(c.Writer, []string{"application/json; charset=utf-8"})
encoder := json.NewEncoder(c.Writer)
err := encoder.Encode(v)
if err != nil {
c.Status(http.StatusInternalServerError)
}
c.Status(http.StatusOK)
return err
}
func (c *Context) Html(v string) error {
writeContentType(c.Writer, []string{"text/html; charset=utf-8"})
c.Status(http.StatusOK)
_, err := c.Writer.Write([]byte(v))
return err
}
func (c *Context) String(v string) error {
writeContentType(c.Writer, []string{"text/plain; charset=utf-8"})
c.Status(http.StatusOK)
_, err := c.Writer.Write([]byte(v))
return err
} - 在context中实现了JSON、Html、String方法
main.go文件修改
1 | package main |
接下来看实际效果
1 | curl -i http://localhost:9501/json |