上下文Context
目标
- 将
路由(router)
独立出来,方便之后增强。
- 设计
上下文(Context)
,封装 Request 和 Response ,提供对 JSON、HTML 等返回类型的支持。
设计Context
栗子:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| obj = map[string]interface{}{ "name": "geektutu", "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) }
c.JSON(http.StatusOK, gee.H{ "username": c.PostForm("username"), "password": c.PostForm("password"), })
|
- Context 随着每一个请求的出现而产生,请求的结束而销毁,和当前请求强相关的信息都应由 Context 承载。
- 设计 Context 结构,扩展性和复杂性留在了内部,而对外简化了接口。路由的处理函数,以及将要实现的中间件,参数都统一使用 Context 实例, Context 就像一次会话的百宝箱,可以找到任何东西。
具体实现
项目结构
1 2 3 4 5 6 7
| gee/ |--gee.go |--context.go |--router.go |--go.mod main.go go.mod
|
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 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83
| package gee
import ( "encoding/json" "fmt" "net/http" )
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, req *http.Request) *Context { return &Context{ Writer: w, Req: req, Path: req.URL.Path, Method: req.Method, } }
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) }
func (c *Context) Status(code int) { c.StatusCode = code c.Writer.WriteHeader(code) }
func (c *Context) SetHeader(key string, value string) { c.Writer.Header().Set(key, value) }
func (c *Context) String(code int, format string, value ...interface{}) { c.SetHeader("Content-Type", "text/plain") c.Status(code) c.Writer.Write([]byte(fmt.Sprintf(format, value))) }
func (c *Context) JSON(code int, obj interface{}) { c.SetHeader("Content-Type", "application/json") c.Status(code) encoder := json.NewEncoder(c.Writer) if err := encoder.Encode(obj); err != nil { http.Error(c.Writer, err.Error(), 500) } }
func (c *Context) Data(code int, data []byte) { c.Status(code) c.Writer.Write(data) }
func (c *Context) HTML(code int, html string) { c.SetHeader("Content-Type", "text/html") c.Status(code) c.Writer.Write([]byte(html)) }
|
router.go
路由:将和路由相关的方法和结构提取了出来,放到了一个新的文件中router.go
,方便我们下一次对 router 的功能进行增强,例如提供动态路由的支持。
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
| package gee
import ( "log" "net/http" )
type router struct { handlers map[string]HandlerFunc }
func newRouter() *router { return &router{handlers: make(map[string]HandlerFunc)} }
func (r *router) addRoute(method string, pattern string, handler HandlerFunc) { log.Printf("Route %4s - %s", 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 { c.String(http.StatusNotFound, "404 NOT FOUND: %s\n", c.Path) } }
|
gee.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
| package gee
import "net/http"
type HandlerFunc func(*Context)
type Engine struct { router *router }
func New() *Engine { return &Engine{router: newRouter()} }
func (engine *Engine) addRoute(method string, pattern string, handler HandlerFunc) { engine.router.addRoute(method, pattern, handler) }
func (engine *Engine) GET(pattern string, handler HandlerFunc) { engine.addRoute("GET", pattern, handler) }
func (engine *Engine) POST(pattern string, handler HandlerFunc) { engine.addRoute("POST", pattern, handler) }
func (engine *Engine) Run(addr string) (err error) { return http.ListenAndServe(addr, engine) }
func (engine *Engine) ServeHTTP(w http.ResponseWriter, req *http.Request) { c := newContext(w, req) engine.router.handle(c) }
|
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 ( "net/http" "gee" )
func main() { r := gee.New() r.GET("/", func(c *gee.Context) { c.HTML(http.StatusOK, "<h1>Hello Gee</h1>") }) r.GET("/hello", func(c *gee.Context) { c.String(http.StatusOK, "hello %s, you're at %s\n", c.Query("name"), c.Path) })
r.POST("/login", func(c *gee.Context) { c.JSON(http.StatusOK, gee.H{ "username": c.PostForm("username"), "password": c.PostForm("password"), }) })
r.Run(":9090") }
|
总结
- 将
router
相关的代码独立。
- 通过实现了 ServeHTTP 接口,接管了所有的 HTTP 请求。
- 在调用 router.handle 之前,构造了一个 Context 对象。