在本节,最终的代码目录结构如下:
1 2 3 4 5 6 7
| dain/ |--context.go |--dain.go |--router.go |--go.mod main.go go.mod
|
实现目标
最终实现得效果如下,此时更加接近于 GIN:
- Handler 得参数变为 dain.Context,同时提供了对表单和 URL 的查询
PostForm/Query
- dain.Context 同 GIN 一样,封装了
HTML/String/JSON/Data
函数,快速构建 HTTP 响应。
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 26 27
| package main
import ( "DawnGin/dain" "net/http" )
func main() { e := dain.New()
e.Get("/", func(c *dain.Context) { c.HTML(http.StatusOK, "<h1>Hello Dawn</h1>") })
e.Get("/hello", func(c *dain.Context) { c.String(http.StatusOK, "Hello World, URL path = %v", c.Path) })
e.Post("/login", func(c *dain.Context) { c.JSON(http.StatusOK, dain.H{ "username": c.PostForm("username"), "password": c.PostForm("password"), }) })
e.Run(":9000") }
|
构建上下文
dain/context.go
上下文 Context 即一个请求的上下文,它随着每一个请求的出现而产生,响应的结束而销毁。
Context 结构
Context 可以对一些代码进行封装,使用起来更加简便。首先看 Context 的结构:
- dain.H 同 gin.H 的作用一样,是
map[string]interface{}
的简便写法。
- 在 Context 中,保存了一次HTTP的请求
Req
和响应 Writer
,同时保存了一些请求和响应信息。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| type H map[string]interface{}
type Context struct { Writer http.ResponseWriter Req *http.Request Path string Method string StatusCode int }
func NewContext(w http.ResponseWriter, r *http.Request) *Context { return &Context{ Writer: w, Req: r, Path: r.URL.Path, Method: r.Method, } }
|
获取请求数据
封装关于获取请求数据的函数 PostForm/Query
:
1 2 3 4 5 6 7 8 9
| func (c *Context) PostForm(key string) string { return c.Req.FormValue(key) }
func (c *Context) Query(key string) string { return c.Req.URL.Query().Get(key) }
|
改变 HTTP 响应头
封装改变 HTTP 响应的函数 Status/SetHeader
:
1 2 3 4 5 6 7 8 9 10
| func (c *Context) Status(code int) { c.StatusCode = code c.Writer.WriteHeader(code) }
func (c *Context) SetHeader(key, value string) { c.Writer.Header().Set(key, value) }
|
快速响应
封装快速响应的函数 String/JSON/Data/HTML
:
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
| func (c *Context) String(code int, format string, values ...interface{}) error { c.SetHeader("Content-Type", "text/plain") c.Status(code) _, err := c.Writer.Write([]byte(fmt.Sprintf(format, values...))) return err }
func (c *Context) JSON(code int, obj interface{}) error { c.SetHeader("Content-Type", "application/json") c.Status(code) encoder := json.NewEncoder(c.Writer) if err := encoder.Encode(obj); err != nil { return err }
return nil }
func (c *Context) Data(code int, data []byte) error { c.Status(code) _, err := c.Writer.Write(data) return err }
func (c *Context) HTML(code int, html string) error { c.SetHeader("Content-Type", "text/html") c.Status(code) _, err := c.Writer.Write([]byte(html)) return err }
|
路由器
dain/router.go
router 结构
于此同时,将路由器从 dain/dain.go
中分离出来。路由器的结构为:
1 2 3 4 5 6 7 8 9
| type router struct { handlers map[string]HandlerFunc }
func NewRouter() *router { return &router{ handlers: make(map[string]HandlerFunc), } }
|
路由功能
需要稍微更改路由注册和路由功能:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| func (r *router) addRouter(method, pattern string, handler HandlerFunc) { log.Printf("Router %v - %v\n", method, pattern) key := method + "-" + pattern r.handlers[key] = handler }
func (r *router) handle(c *Context) { key := c.Method + "-" + c.Path if handler, ok := r.handlers[key]; ok { handler(c) } else { fmt.Fprintf(c.Writer, "404 NOT FOUND FOR PATH: %v", c.Path) } }
|
框架入口
dain/dain.go
与此同时,也要更改框架入口。
首先需要将 HandlerFunc 的参数改变为 Context:
1 2
| type HandlerFunc func(c *Context)
|
改变 Engine 的内部结构,使之内嵌 router 结构体:
1 2 3 4 5 6 7 8 9 10 11
| type Engine struct { router *router }
func New() *Engine { return &Engine{ router: NewRouter(), } }
|
更改路由注册的内部逻辑为,调用 router 的路由注册:
1 2 3 4
| func (e *Engine) addRouter(method, pattern string, handler HandlerFunc) { e.router.addRouter(method, pattern, handler) }
|
最后,需要更改 http.Handler 接口函数 ServeHTTP 的实现:
1 2 3 4 5
| func (e *Engine) ServeHTTP(w http.ResponseWriter, r *http.Request) { c := NewContext(w, r) e.router.handle(c) }
|