发布于:,更新于:

03.Mygin实现上下文

本篇是Mygin的第三篇

目的

  • 将路由独立出来,方便后续扩展修改
  • 上下文Context,对http.ResponseWriter和http.Request进行封装,实现对JSON、HTML等的支持

路由

新建一个router文件,将 Mygin实现简单的路由 中将路由部分复制出来

  • 新建Mygin/router.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
49
50
51
52
package mygin

import (
"log"
"net/http"
)

// 路由节点
type methodTree struct {
method string
paths map[string]func(c *Context)
}

type methodTrees map[string]methodTree

type Router struct {
trees methodTrees
}

// 获取路由root根
func (r *Router) getRoot(method string) methodTree {
if root, ok := r.trees[method]; ok {
return root
}
r.trees[method] = methodTree{method: method, paths: make(map[string]func(c *Context))}
return r.trees[method]
}

// 添加路由方法
func (r *Router) addRoute(method, path string, ctx func(c *Context)) {
root := r.getRoot(method)

if _, ok := root.paths[path]; ok {
log.Default().Println(path, "is exist")
return
}

root.method = method
root.paths[path] = ctx

}

// Get Get方法
func (r *Router) Get(path string, handler func(c *Context)) {
r.addRoute(http.MethodGet, path, handler)
}

// Post Post方法
func (e *Engine) Post(path string, handler func(c *Context)) {
e.addRoute(http.MethodPost, path, handler)
}

  • 修改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
    36
    package 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
2
3
4
5
6
7
8
9
10
obj = map[string]interface{}{
"name": "scott",
"password": "1234",
}
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusOK)
encoder := json.NewEncoder(w)
if err := encoder.Encode(obj); err != nil {
http.Error(w, err.Error(), 500)
}

封装后

1
2
3
4
c.JSON(http.StatusOK,  map[string][string]{
"username": c.PostForm("username"),
"password": c.PostForm("password"),
})

封装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
    49
    package 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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
package main

import (
"gophp/mygin"
)

func main() {
engine := mygin.Default()
engine.Get("/json", func(c *mygin.Context) {
c.JSON(map[string]interface{}{
"name": "zs",
"password": "1234",
})
})

engine.Get("/html", func(c *mygin.Context) {
c.Html("<h1>hello html!</h1>")
})

engine.Get("/string", func(c *mygin.Context) {
c.String("string...")
})

engine.Run(":9501")
}

接下来看实际效果

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
curl -i http://localhost:9501/json
HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8
Date: Wed, 17 Jan 2024 05:52:28 GMT
Content-Length: 32

{"name":"zs","password":"1234"}

~ curl -i http://localhost:9501/html
HTTP/1.1 200 OK
Content-Type: text/html; charset=utf-8
Date: Wed, 17 Jan 2024 05:52:36 GMT
Content-Length: 20

<h1>hello html!</h1>

~ curl -i http://localhost:9501/string
HTTP/1.1 200 OK
Content-Type: text/plain; charset=utf-8
Date: Wed, 17 Jan 2024 05:52:42 GMT
Content-Length: 9

string...



本站由 [PengBin](/) 使用 [Stellar 1.26.3](https://github.com/xaoxuu/hexo-theme-stellar/tree/1.26.3) 主题创建。 本博客所有文章除特别声明外,均采用 [CC BY-NC-SA 4.0](https://creativecommons.org/licenses/by-nc-sa/4.0/) 许可协议,转载请注明出处。