httprouter 源码刨析
作者:王博
- 2021 年 12 月 29 日
本文字数:3989 字
阅读完需:约 13 分钟
首先第一个问题,我们为什么需要 httprouter?
也就是为什么需要所谓的 web 框架,如果没有使用 httprouter 而是使用的官方的框架会有什么问题?
其实看过 go 官方提供的 http 代码的同学都知道,go 官方提供了DefaultServeMux的路由规则,但是其路由规则先对简单,同时官方大大也提供了接口的方式允许我们自己定制路由规则
//只需要实现这个接口,就可以定制自己的路由规则,将实现通过ListenAndServe传入进来,type Handler interface { ServeHTTP(ResponseWriter, *Request)}func ListenAndServe(addr string, handler Handler) error { server := &Server{Addr: addr, Handler: handler} return server.ListenAndServe()}
func (sh serverHandler) ServeHTTP(rw ResponseWriter, req *Request) { handler := sh.srv.Handler//如果有定制,使用定制的,否则使用默认的 if handler == nil { handler = DefaultServeMux } if req.RequestURI == "*" && req.Method == "OPTIONS" { handler = globalOptionsHandler{} } handler.ServeHTTP(rw, req)}复制代码
当然,在 httprouter 里面也有一句很有意思的代码,就是保证自己定义的 Router 实现了 Handler 接口
var _ http.Handler = New()func New() *Router { return &Router{ RedirectTrailingSlash: true, RedirectFixedPath: true, HandleMethodNotAllowed: true, HandleOPTIONS: true, }}复制代码
从上面可以看到核心代码就是 ServeHTTP 方法,但是在看这个方法之前,我们先看一下 Router 的数据结构定义
type Router struct { trees map[string]*node
paramsPool sync.Pool maxParams uint16
// If enabled, adds the matched route path onto the http.Request context // before invoking the handler. // The matched route path is only added to handlers of routes that were // registered when this option was enabled. // 如果是true,会把匹配到的path在调用handler之前加到context里 // 只有当这个值是true的时候,匹配到的path才会被加到handler的routes里面 SaveMatchedRoutePath bool
// Enables automatic redirection if the current route can't be matched but a // handler for the path with (without) the trailing slash exists. // For example if /foo/ is requested but a route only exists for /foo, the // client is redirected to /foo with http status code 301 for GET requests // and 308 for all other request methods. // 作用是如果后面多了个下划线,也可以匹配的到,但是对于GET会返回301,其他的会返回308 RedirectTrailingSlash bool
// If enabled, the router tries to fix the current request path, if no // handle is registered for it. // First superfluous path elements like ../ or // are removed. // Afterwards the router does a case-insensitive lookup of the cleaned path. // If a handle can be found for this route, the router makes a redirection // to the corrected path with status code 301 for GET requests and 308 for // all other request methods. // For example /FOO and /..//Foo could be redirected to /foo. // RedirectTrailingSlash is independent of this option. // 如果匹配不上,尝试修复path,比如删除/ ../之类的,但是对于GET会返回301,其他的会返回308 RedirectFixedPath bool
// If enabled, the router checks if another method is allowed for the // current route, if the current request can not be routed. // If this is the case, the request is answered with 'Method Not Allowed' // and HTTP status code 405. // If no other Method is allowed, the request is delegated to the NotFound // handler. // 如果没有找到对应的http方法,也会调用某个方法 HandleMethodNotAllowed bool
// If enabled, the router automatically replies to OPTIONS requests. // Custom OPTIONS handlers take priority over automatic replies. // 如果是true,那么router自动回复OPTIONS的request // 用户自定义的比上面提到的自动回复的函数优先级更高 HandleOPTIONS bool
// An optional http.Handler that is called on automatic OPTIONS requests. // The handler is only called if HandleOPTIONS is true and no OPTIONS // handler for the specific path was set. // The "Allowed" header is set before calling the handler. GlobalOPTIONS http.Handler
// Cached value of global (*) allowed methods globalAllowed string
// Configurable http.Handler which is called when no matching route is // found. If it is not set, http.NotFound is used. // 路由没有匹配上时调用这个 handler 。 // 如果没有定义这个 handler ,就会返回标准库中的 http.NotFound 。 NotFound http.Handler
// Configurable http.Handler which is called when a request // cannot be routed and HandleMethodNotAllowed is true. // If it is not set, http.Error with http.StatusMethodNotAllowed is used. // The "Allow" header with allowed request methods is set before the handler // is called. // 当一个请求是不被允许的,并且上面的 HandleMethodNotAllowed 设置为 ture 的时候, // 如果这个参数没有设置,将使用状态为 with http.StatusMethodNotAllowed 的 http.Error // 在 handler 被调用以前,为允许请求的方法设置 "Allow" header MethodNotAllowed http.Handler
// Function to handle panics recovered from http handlers. // It should be used to generate a error page and return the http error code // 500 (Internal Server Error). // The handler can be used to keep your server from crashing because of // unrecovered panics. // panic recover的,相当于其他框架的recover middleware PanicHandler func(http.ResponseWriter, *http.Request, interface{})}复制代码
下面我们来分析一下 ServeHTTP 做了哪些事
func (r *Router) ServeHTTP(w http.ResponseWriter, req *http.Request) { // panic处理模块,相当于一个recover middleware if r.PanicHandler != nil { defer r.recv(w, req) } //获取path path := req.URL.Path //根据HTTP方法找到节点(httprouter的路由存储结构为一颗Radix树,根节点连接不同的请求方法, //每个方法的子节点负责存储自己的路径) if root := r.trees[req.Method]; root != nil { // 如果路由匹配上了,从基数树中将路由取出 if handle, ps, tsr := root.getValue(path, r.getParams); handle != nil { if ps != nil { //调用处理函数 handle(w, req, *ps) r.putParams(ps) } else { handle(w, req, nil) } return } else if req.Method != http.MethodConnect && path != "/" { //如果路由没有匹配上,请求方法不是CONNECT并且路径不是/ //在前面提到过,Get方法会返回301状态码,其他的会返回308 // Moved Permanently, request with GET method code := http.StatusMovedPermanently if req.Method != http.MethodGet { // Permanent Redirect, request with same method code = http.StatusPermanentRedirect }
// 尝试删除末尾的/,然后进行请求转发 // tsr 返回值是一个 bool 值,用来判断是否需要重定向, getValue 返回来的 // RedirectTrailingSlash我们上面也有提到过 if tsr && r.RedirectTrailingSlash { if len(path) > 1 && path[len(path)-1] == '/' { req.URL.Path = path[:len(path)-1] } else { req.URL.Path = path + "/" } http.Redirect(w, req, req.URL.String(), code) return }
// Try to fix the request path // RedirectFixedPath同样上面提到过,这里尝试做路径fix,然后进行请求转发 if r.RedirectFixedPath { fixedPath, found := root.findCaseInsensitivePath( CleanPath(path), r.RedirectTrailingSlash, ) if found { req.URL.Path = fixedPath http.Redirect(w, req, req.URL.String(), code) return } } } }
// 如果没有任何路由匹配,请求方式又是 OPTIONS, 并且允许响应 OPTIONS // 就会给 Header 设置一个 Allow 头,返回去 if req.Method == http.MethodOptions && r.HandleOPTIONS { // Handle OPTIONS requests if allow := r.allowed(path, http.MethodOptions); allow != "" { w.Header().Set("Allow", allow) if r.GlobalOPTIONS != nil { r.GlobalOPTIONS.ServeHTTP(w, req) } return } } else if r.HandleMethodNotAllowed { // Handle 405 //如果自定义了MethodNotAllowed方法,则会调用自定义的,否则会返回error if allow := r.allowed(path, req.Method); allow != "" { w.Header().Set("Allow", allow) if r.MethodNotAllowed != nil { r.MethodNotAllowed.ServeHTTP(w, req) } else { http.Error(w, http.StatusText(http.StatusMethodNotAllowed), http.StatusMethodNotAllowed, ) } return } }
// Handle 404 // 如果自定义了NotFound,则会调用自定义的方法,否则会返回http.NotFound if r.NotFound != nil { r.NotFound.ServeHTTP(w, req) } else { http.NotFound(w, req) }}复制代码
划线
评论
复制
发布于: 刚刚
版权声明: 本文为 InfoQ 作者【王博】的原创文章。
原文链接:【http://xie.infoq.cn/article/073db99cd6ad755139268b7e3】。文章转载请联系作者。
王博
关注
我是一名后端,写代码的憨憨 2018.12.29 加入
还未添加个人简介











评论