写点什么

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) }}
复制代码


发布于: 刚刚
用户头像

王博

关注

我是一名后端,写代码的憨憨 2018.12.29 加入

还未添加个人简介

评论

发布
暂无评论
httprouter源码刨析