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 加入
还未添加个人简介
评论