时间:2023-07-24 04:39:01 | 来源:网站运营
时间:2023-07-24 04:39:01 来源:网站运营
自己动手写WEB框架-GO语言版:import ( "fmt" "net/http")func main() { //注册路由 http.HandleFunc("/", func(w http.ResponseWriter, req *http.Request) { fmt.Fprintf(w, "hello go web") }) //启动 http.ListenAndServe(":8088", nil)}
运行后通过访问 http://localhost:8088/ ,页面上展示 hello go web // 启动方法func ListenAndServe(addr string, handler Handler) error { server := &Server{Addr: addr, Handler: handler} return server.ListenAndServe()}//我们需要手动实现这个接口type Handler interface { ServeHTTP(ResponseWriter, *Request)}
通过以下代码,我们使用自己定义的实例来管理所有请求,此时我们可以浏览器输入"http://localhost:8088/"或者是在其后面拼接任意请求,页面上都会将我们请求路径打印出来。import ( "fmt" "net/http")type Entity struct {}// http.ResponseWriter 用于响应请求// *http.Request 包含了请求的详细信息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)}
到现在为止,我们就实现了对请求的统一管理。// http.HandleFunc()方法的源码func HandleFunc(pattern string, handler func(ResponseWriter, *Request)) { DefaultServeMux.HandleFunc(pattern, handler)}
首先我们需要检查用户访问的路由是否存在,因此我们需要使用一个map来存储路由和他的映射。首先我么为func(w http.ResponseWriter, r *http.Request)取一个别名,在接下来的代码中,我们以别名来表示此函数。// 取别名type MyHandler func(w http.ResponseWriter, r *http.Request)type Entity struct { router map[string]*MyHandler}// 对外暴露初始化的操作func New() *Entity { return &Entity{router: make(map[string]MyHandler)}}
接下来我们需要将方法添加到路由中。// method:指请求方式GET或POST// pattern :指请求路由func (e *Entity) addRoute(method, pattern string, handler MyHandler) { key := method + "-" + pattern e.router[key] = handler}
为了更加方便使用,我们需要再把addRoute方法封装为GET()和POST方法。这样我们在使用时只需要传入两个参数且不容易出错。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)}
在前文中我们说到,在项目启动时我们需要传入实现了ServeHTTP()的实例,现在我们来对这个方法进行完善,最起码我们需要对用户访问的路由进行一个验证,最起码只能访问已注册了的路由。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") }}
此时我们再加上web项目启动方法,一个web的框架基本上就具备雏形了。func (e *Entity) Run(port string) (err error) { return http.ListenAndServe(port, e)}
在main()函数中进行测试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")}
截止到当前的代码存入网盘中,需要的可以下载。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) 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类型的数据。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)}
为了使代码结构更加清晰,将路由相关的方法提取出来放在一个新文件中,同时修改与之相关的方法。修改后的文件及目录结构见链接,提取码:ccintype node struct { pattern string // 待匹配路由 part string // 路由中的一部分 children []*node // 子节点 isWild bool // 是否精确匹配,part 含有 : 或 * 时为true}
我们定义上述结构体,同时定义以下两个方法分别用于插入和查找。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}
接下来,我们编写一个路由插入的方法和路由查询的方法。//插入func (n *node) insert(pattern string, parts []string, height int) { // 如果入参的路由的深度(数组parts长度)与入参的深度相同,则直接将这个路由插入 if len(parts) == height { n.pattern = pattern return } //查看当前路由是否存在 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) } //递归调用插入方法 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,使其应用前缀树维护路由表。修改路由的代码。我们需要维护两个变量,分别用于存储handler映射和前缀树节点。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), }}
在我们前缀树路由的相关方法中,使用到了一个数组变量,这是由全路由以 "/"分割,将分割后的组成一个数组。例如全路由为/a/b/c,其数组就由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的结构发生了变化,因此重写handle方法,在此之前也要修改context的结构和相关方法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) }}
接下来进行相关测试提取码: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}
新增创建分组的方法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}
修改注册路由的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的结构和初始化方法,并新增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关键词:语言,动手