時(shí)間:2023-07-24 04:39:01 | 來源:網(wǎng)站運(yùn)營
時(shí)間:2023-07-24 04:39:01 來源:網(wǎng)站運(yùn)營
自己動(dòng)手寫WEB框架-GO語言版:import ( "fmt" "net/http")func main() { //注冊(cè)路由 http.HandleFunc("/", func(w http.ResponseWriter, req *http.Request) { fmt.Fprintf(w, "hello go web") }) //啟動(dòng) http.ListenAndServe(":8088", nil)}
運(yùn)行后通過訪問 http://localhost:8088/ ,頁面上展示 hello go web // 啟動(dòng)方法func ListenAndServe(addr string, handler Handler) error { server := &Server{Addr: addr, Handler: handler} return server.ListenAndServe()}//我們需要手動(dòng)實(shí)現(xiàn)這個(gè)接口type Handler interface { ServeHTTP(ResponseWriter, *Request)}
通過以下代碼,我們使用自己定義的實(shí)例來管理所有請(qǐng)求,此時(shí)我們可以瀏覽器輸入"http://localhost:8088/"或者是在其后面拼接任意請(qǐng)求,頁面上都會(huì)將我們請(qǐng)求路徑打印出來。import ( "fmt" "net/http")type Entity struct {}// http.ResponseWriter 用于響應(yīng)請(qǐng)求// *http.Request 包含了請(qǐng)求的詳細(xì)信息func (e *Entity) ServeHTTP(w http.ResponseWriter, r *http.Request) { fmt.Fprintf(w, "你訪問的路徑是: %s/n", r.URL.Path)}func main() { e := &Entity{} http.ListenAndServe(":8088", e)}
到現(xiàn)在為止,我們就實(shí)現(xiàn)了對(duì)請(qǐng)求的統(tǒng)一管理。// http.HandleFunc()方法的源碼func HandleFunc(pattern string, handler func(ResponseWriter, *Request)) { DefaultServeMux.HandleFunc(pattern, handler)}
首先我們需要檢查用戶訪問的路由是否存在,因此我們需要使用一個(gè)map來存儲(chǔ)路由和他的映射。首先我么為func(w http.ResponseWriter, r *http.Request)取一個(gè)別名,在接下來的代碼中,我們以別名來表示此函數(shù)。// 取別名type MyHandler func(w http.ResponseWriter, r *http.Request)type Entity struct { router map[string]*MyHandler}// 對(duì)外暴露初始化的操作func New() *Entity { return &Entity{router: make(map[string]MyHandler)}}
接下來我們需要將方法添加到路由中。// method:指請(qǐng)求方式GET或POST// pattern :指請(qǐng)求路由func (e *Entity) addRoute(method, pattern string, handler MyHandler) { key := method + "-" + pattern e.router[key] = handler}
為了更加方便使用,我們需要再把a(bǔ)ddRoute方法封裝為GET()和POST方法。這樣我們?cè)谑褂脮r(shí)只需要傳入兩個(gè)參數(shù)且不容易出錯(cuò)。func (e *Entity) GET(pattern string, handler MyHandler) { e.addRoute("GET", pattern, handler)}func (e *Entity) POST(pattern string, handler MyHandler) { e.addRoute("POST", pattern, handler)}
在前文中我們說到,在項(xiàng)目啟動(dòng)時(shí)我們需要傳入實(shí)現(xiàn)了ServeHTTP()的實(shí)例,現(xiàn)在我們來對(duì)這個(gè)方法進(jìn)行完善,最起碼我們需要對(duì)用戶訪問的路由進(jìn)行一個(gè)驗(yàn)證,最起碼只能訪問已注冊(cè)了的路由。func (e *Entity) ServeHTTP(w http.ResponseWriter, r *http.Request) { key := r.Method + "-" + r.URL.Path if handler, ok := e.router[key]; ok { handler(w, r) } else { fmt.Fprintf(w, "404 Not Found") }}
此時(shí)我們?cè)偌由蟱eb項(xiàng)目啟動(dòng)方法,一個(gè)web的框架基本上就具備雛形了。func (e *Entity) Run(port string) (err error) { return http.ListenAndServe(port, e)}
在main()函數(shù)中進(jìn)行測試import ( "cin" "fmt" "net/http")func main() { c := cin.New() c.GET("/", func(w http.ResponseWriter, r *http.Request) { fmt.Fprintf(w, "hello world") }) c.Run(":8081")}
截止到當(dāng)前的代碼存入網(wǎng)盤中,需要的可以下載。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, }}
編寫設(shè)置狀態(tài)碼、設(shè)置自定義頭部的方法。func (c *Context) Status(code int) { c.StatusCode = code c.Writer.WriteHeader(code)}func (c *Context) setHeader(k, v string) { c.Writer.Header().Set(k, v)}
提供公共方法,分別用于返回string、json以及html類型的數(shù)據(jù)。func (c *Context) String(code int, format string, values ...interface{}) { c.SetHeader("Content-Type", "text/plain") c.Status(code) c.Writer.Write([]byte(fmt.Sprintf(format, values...)))}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) HTML(code int, html string) { c.SetHeader("Content-Type", "text/html") c.Status(code) c.Writer.Write([]byte(html))}func (c *Context) DATA(code int, data []byte) { c.Status(code) c.Writer.Write(data)}
添加訪問Query和PostForm方法func (c *Context) Query(s string) string { return c.Req.URL.Query().Get(s)}func (c *Context) PostForm(key string) string { return c.Req.FormValue(key)}
為了使代碼結(jié)構(gòu)更加清晰,將路由相關(guān)的方法提取出來放在一個(gè)新文件中,同時(shí)修改與之相關(guān)的方法。修改后的文件及目錄結(jié)構(gòu)見鏈接,提取碼:ccintype node struct { pattern string // 待匹配路由 part string // 路由中的一部分 children []*node // 子節(jié)點(diǎn) isWild bool // 是否精確匹配,part 含有 : 或 * 時(shí)為true}
我們定義上述結(jié)構(gòu)體,同時(shí)定義以下兩個(gè)方法分別用于插入和查找。func (n *node) matchChild(part string) *node { for _, child := range n.children { if child.part == part || child.isWild { return child } } return nil}func (n *node) matchChildren(part string) []*node { nodes := make([]*node, 0) for _, child := range n.children { if child.part == part || child.isWild { nodes = append(nodes, child) } } return nodes}
接下來,我們編寫一個(gè)路由插入的方法和路由查詢的方法。//插入func (n *node) insert(pattern string, parts []string, height int) { // 如果入?yún)⒌穆酚傻纳疃?數(shù)組parts長度)與入?yún)⒌纳疃认嗤?,則直接將這個(gè)路由插入 if len(parts) == height { n.pattern = pattern return } //查看當(dāng)前路由是否存在 part := parts[height] child := n.matchChild(part) if child == nil { child = &node{ part: part, isWild: part[0] == '*' || part[0] == ':', } n.children = append(n.children, child) } //遞歸調(diào)用插入方法 child.insert(pattern, parts, height+1)}//查詢func (n *node) search(parts []string, height int) *node { // if len(parts) == height || strings.HasPrefix(n.part, "*") { if n.pattern == "" { return nil } return n } part := parts[height] children := n.matchChildren(part) for _, child := range children { result := child.search(parts, height+1) if result != nil { return result } } return nil}
接下來就可以修改router,使其應(yīng)用前綴樹維護(hù)路由表。修改路由的代碼。我們需要維護(hù)兩個(gè)變量,分別用于存儲(chǔ)handler映射和前綴樹節(jié)點(diǎn)。type router struct { handlers map[string]HandlerFunc roots map[string]*node}func newRouter() *router { return &router{ handlers: make(map[string]HandlerFunc), roots: make(map[string]*node), }}
在我們前綴樹路由的相關(guān)方法中,使用到了一個(gè)數(shù)組變量,這是由全路由以 "/"分割,將分割后的組成一個(gè)數(shù)組。例如全路由為/a/b/c,其數(shù)組就由a、b、c組成。func parsePattern(pattern string) []string { vs := strings.Split(pattern, "/") parts := make([]string, 0) for _, v := range vs { if v != "" { parts = append(parts, v) if v[0] == '*' { break } } } return parts}
編寫新增路由和獲取路由的方法func (r *router) addRoute(method, pattern string, handler HandlerFunc) { parts := parsePattern(pattern) key := method + "-" + pattern _, ok := r.roots[method] if !ok { r.roots[method] = &node{} } r.roots[method].insert(pattern, parts, 0) r.handlers[key] = handler}func (r *router) getRoute(method, path string) (*node, map[string]string) { searchParts := parsePattern(path) params := make(map[string]string) root, ok := r.roots[method] if !ok { return nil, nil } n := root.search(searchParts, 0) if n != nil { parts := parsePattern(n.pattern) for i, part := range parts { if part[0] == ':' { params[part[1:]] = searchParts[i] } if part[0] == '*' && len(part) > 1 { params[part[1:]] = strings.Join(searchParts[i:], "/") break } } return n, params } return nil, nil}
由于router的結(jié)構(gòu)發(fā)生了變化,因此重寫handle方法,在此之前也要修改context的結(jié)構(gòu)和相關(guān)方法type Context struct { Writer http.ResponseWriter Req *http.Request Path string Method string StatusCode int Params map[string]string}func (c *Context) Param(key string) string { value, _ := c.Params[key] return value}
以下是修改后的handle方法func (r *router) handle(c *Context) { n, params := r.getRoute(c.Method, c.Path) if n != nil { c.Params = params key := c.Method + "-" + n.pattern r.handlers[key](c) } else { c.String(http.StatusNotFound, "404 NOT FOUND: %s/n", c.Path) }}
接下來進(jìn)行相關(guān)測試提取碼:ccintype RouterGroup struct { prefix string middlewares []HandlerFunc parent *RouterGroup engine *Engine }type Engine struct { router *router *RouterGroup groups []*RouterGroup}
修改Engine的初始化方法func New() *Engine { engine := &Engine{router: newRouter()} engine.RouterGroup = &RouterGroup{} engine.groups = []*RouterGroup{engine.RouterGroup} return engine}
新增創(chuàng)建分組的方法func (group *RouterGroup) Group(pre string) *RouterGroup { engine := group.engine newGroup := &RouterGroup{ prefix: group.prefix + pre, parent: group, engine: engine, } engine.groups = append(engine.groups, newGroup) return newGroup}
修改注冊(cè)路由的GET和POST方法以及addroute方法func (group *RouterGroup) addRoute(method string, pattern string, handler HandlerFunc) { newPattern := group.prefix + pattern log.Printf("Route %4s - %s", method, newPattern) group.engine.router.addRoute(method, newPattern, handler)}func (group *RouterGroup) GET(pattern string, handler HandlerFunc) { group.addRoute("GET", pattern, handler)}func (group *RouterGroup) POST(pattern string, handler HandlerFunc) { group.addRoute("POST", pattern, handler)}
提取碼:ccinfunc Logger() HandlerFunc { return func(c *Context) { // Start timer t := time.Now() // Process request c.Next() // Calculate resolution time log.Printf("[%d] %s in %v", c.StatusCode, c.Req.RequestURI, time.Since(t)) }}
首先修改context的結(jié)構(gòu)和初始化方法,并新增next方法。type Context struct { Writer http.ResponseWriter Req *http.Request Path string Method string StatusCode int Params map[string]string handlers []HandlerFunc index int}func newContext(w http.ResponseWriter, req *http.Request) *Context { return &Context{ Writer: w, Req: req, Path: req.URL.Path, Method: req.Method, index: -1, }}func (c *Context) Next() { c.index++ s := len(c.handlers) for ; c.index < s; c.index++ { c.handlers[c.index](c) }}
為GroupRouter新增方法func (group *RouterGroup) Use(middlewares ...HandlerFunc) { group.middlewares = append(group.middlewares, middlewares...)}
修改ServeHTTP方法func (engine *Engine) ServeHTTP(w http.ResponseWriter, req *http.Request) { var middlewares []HandlerFunc for _, group := range engine.groups { if strings.HasPrefix(req.URL.Path, group.prefix) { middlewares = append(middlewares, group.middlewares...) } } c := newContext(w, req) c.handlers = middlewares engine.router.handle(c)}
修改handle方法func (r *router) handle(c *Context) { n, params := r.getRoute(c.Method, c.Path) if n != nil { key := c.Method + "-" + n.pattern c.Params = params c.handlers = append(c.handlers, r.handlers[key]) } else { c.handlers = append(c.handlers, func(c *Context) { c.String(http.StatusNotFound, "404 NOT FOUND: %s/n", c.Path) }) } c.Next()}
新增Fail方法func (c *Context) Fail(code int, err string) { c.index = len(c.handlers) c.JSON(code, H{"message": err})}
提取碼:ccin關(guān)鍵詞:語言,動(dòng)手
客戶&案例
營銷資訊
關(guān)于我們
微信公眾號(hào)
版權(quán)所有? 億企邦 1997-2025 保留一切法律許可權(quán)利。