当前位置: 首页 > 工具软件 > issue9/mux > 使用案例 >

服务计算 gorilla/mux 源码分析

韦睿
2023-12-01

gorilla/mux 源码分析

gorilla/mux是一个强大的路由,小巧但是稳定高效,不仅可以支持正则路由还可以按照Method,header,host等信息匹配,可以从我们设定的路由表达式中提取出参数方便上层应用,而且完全兼容http.ServerMux

  • gorilla/mux源码地址

  • 官方提供的使用实例:

    r := mux.NewRouter()
    r.HandleFunc("/products/{key}", ProductHandler)
    r.HandleFunc("/articles/{category}/", ArticlesCategoryHandler)
    r.HandleFunc("/articles/{category}/{id:[0-9]+}", ArticleHandler)
    

1. gorilla/mux 的主要特点

  • 实现了http.Handler接口,兼容标准的http.ServeMux;
  • 可以匹配URL主机,路径,路径前缀,头部,查询值,HTTP方法或用户自定义的匹配。
  • 可以使用正则表达式匹配URL主机、路径、查询值。
  • 可以使用子路由。当父路由匹配成功后,子路由才会进行匹配(加快匹配速度)。

2. mux.go源码分析

2.1 Router类型

  • 实现了http.handler接口,定义了跟路由;
  • 可以通过 HandleFunc 注册多个path 和handler;
  • 变量 strictSlash 定义了对斜杠 “/” 的处理方式:
    • true:当path为“/path/”时,访问“/path”的时候会被重定向到“/path/”;
    • false:当path为“/path”时,访问“/path/”的时候将不会被此路由匹配;
    • 当路由设置了 PathPrefix 方法时,此路由将忽略此flag。
  • 变量 SkipClean 定义清洗path的行为:
    • true:当path为/path//to的时候,将会保留其中的多个斜杠;
    • false:当path为/path//to时,多余的斜杠将会被清理,变成/path/to
type Router struct {
	// Configurable Handler to be used when no route matches.
	NotFoundHandler http.Handler

	// Configurable Handler to be used when the request method does not match the route.
	MethodNotAllowedHandler http.Handler

	// Parent route, if this is a subrouter.
	parent parentRoute
	// Routes to be matched, in order.
	routes []*Route
	// Routes by name for URL building.
	namedRoutes map[string]*Route
	// See Router.StrictSlash(). This defines the flag for new routes.
	strictSlash bool
	// See Router.SkipClean(). This defines the flag for new routes.
	skipClean bool
	// If true, do not clear the request context after handling the request.
	// This has no effect when go1.7+ is used, since the context is stored
	// on the request itself.
	KeepContext bool
	// see Router.UseEncodedPath(). This defines a flag for all routes.
	useEncodedPath bool
	// Slice of middlewares to be called after a match is found
	middlewares []middleware
}

2.2 routeConf类型

routeConf 是“路由器”和“路由”之间共享的公共路由配置,内部结构中 [ ]matcher 用于存储多个匹配条件;routeRegexpGroup 用于保存匹配成功后提取的参数的实际值。http请求匹配成功后,从此结构可以获取需要传递到handler处理器中的参数。

type routeConf struct {
	// If true, "/path/foo%2Fbar/to" will match the path "/path/{var}/to"
	useEncodedPath bool

	// If true, when the path pattern is "/path/", accessing "/path" will
	// redirect to the former and vice versa.
	strictSlash bool

	// If true, when the path pattern is "/path//to", accessing "/path//to"
	// will not redirect
	skipClean bool

	// Manager for the variables from host and path.
	regexp routeRegexpGroup

	// List of matchers.
	matchers []matcher

	// The scheme used when building URLs.
	buildScheme string

	buildVarsFunc BuildVarsFunc
}

2.3 routeRegexp 类型

type routeRegexp struct {
    // The unmodified template.
    template string
    // The type of match
    regexpType regexpType
    // Options for matching
    options routeRegexpOptions
    // Expanded regexp.
    regexp *regexp.Regexp
    // Reverse template.
    reverse string
    // Variable names.
    varsN []string
    // Variable regexps (validators).
    varsR []*regexp.Regexp
}

2.4 NewRouter方法

该方法返回一个新的 Router 实例,Router 类型已经在上文分析过,此处不再赘述。

// NewRouter returns a new router instance.
func NewRouter() *Router {
	return &Router{namedRoutes: make(map[string]*Route), KeepContext: false}
}

2.5 ServeHTTP方法

skipCleanuseEncodedPath默认为false。核心部分是从 r.Match(req, &match)开始的。首先会遍历Router中的所有路由route的Match方法,如有匹配到,则直接返回,否则返回NotFoundHandler

// ServeHTTP dispatches the handler registered in the matched route.
//
// When there is a match, the route variables can be retrieved calling
// mux.Vars(request).
func (r *Router) ServeHTTP(w http.ResponseWriter, req *http.Request) {
	if !r.skipClean {
		path := req.URL.Path
		if r.useEncodedPath {
			path = req.URL.EscapedPath()
		}
		// Clean path to canonical form and redirect.
		if p := cleanPath(path); p != path {

			// Added 3 lines (Philip Schlump) - It was dropping the query string and #whatever from query.
			// This matches with fix in go 1.2 r.c. 4 for same problem.  Go Issue:
			// http://code.google.com/p/go/issues/detail?id=5252
			url := *req.URL
			url.Path = p
			p = url.String()

			w.Header().Set("Location", p)
			w.WriteHeader(http.StatusMovedPermanently)
			return
		}
	}
	var match RouteMatch
	var handler http.Handler
	if r.Match(req, &match) {
		handler = match.Handler
		req = setVars(req, match.Vars)
		req = setCurrentRoute(req, match.Route)
	}

	if handler == nil && match.MatchErr == ErrMethodMismatch {
		handler = methodNotAllowedHandler()
	}

	if handler == nil {
		handler = http.NotFoundHandler()
	}

	if !r.KeepContext {
		defer contextClear(req)
	}

	handler.ServeHTTP(w, req)
}

2.6 match方法

遍历 Router 下的所有 route, 一个一个找,直到找到可以匹配上的。

// Match attempts to match the given request against the router's registered routes.
//
// If the request matches a route of this router or one of its subrouters the Route,
// Handler, and Vars fields of the the match argument are filled and this function
// returns true.
//
// If the request does not match any of this router's or its subrouters' routes
// then this function returns false. If available, a reason for the match failure
// will be filled in the match argument's MatchErr field. If the match failure type
// (eg: not found) has a registered handler, the handler is assigned to the Handler
// field of the match argument.
func (r *Router) Match(req *http.Request, match *RouteMatch) bool {
	for _, route := range r.routes {
		if route.Match(req, match) {
			// Build middleware chain if no error was found
			if match.MatchErr == nil {
				for i := len(r.middlewares) - 1; i >= 0; i-- {
					match.Handler = r.middlewares[i].Middleware(match.Handler)
				}
			}
			return true
		}
	}
...

3. route.go源码分析

3.1 Route类型

// Route stores information to match a request and build URLs.
type Route struct {
	// Parent where the route was registered (a Router).
	parent parentRoute
	// Request handler for the route.
	handler http.Handler
	// List of matchers.
	matchers []matcher
	// Manager for the variables from host and path.
	regexp *routeRegexpGroup
	// If true, when the path pattern is "/path/", accessing "/path" will
	// redirect to the former and vice versa.
	strictSlash bool
	// If true, when the path pattern is "/path//to", accessing "/path//to"
	// will not redirect
	skipClean bool
	// If true, "/path/foo%2Fbar/to" will match the path "/path/{var}/to"
	useEncodedPath bool
	// The scheme used when building URLs.
	buildScheme string
	// If true, this route never matches: it is only used to build URLs.
	buildOnly bool
	// The name used to build URLs.
	name string
	// Error resulted from building a route.
	err error

	buildVarsFunc BuildVarsFunc
}

3.2 Match方法

  • 对于buildonly模式,不做匹配,只生成url;
  • 添加路由限定条件时,就是往matcher数组中增加一个限定函数。当请求到来时,Route.Match()会遍历matcher数组,只有数组中所有的元素都返回true时则说明此请求满足该路由的限定条件。
// Match matches the route against the request.
func (r *Route) Match(req *http.Request, match *RouteMatch) bool {
	if r.buildOnly || r.err != nil {
		return false
	}

	var matchErr error

	// Match everything.
	for _, m := range r.matchers {
		if matched := m.Match(req, match); !matched {
			if _, ok := m.(methodMatcher); ok {
				matchErr = ErrMethodMismatch
				continue
			}
			matchErr = nil
			return false
		}
	}

	if matchErr != nil {
		match.MatchErr = matchErr
		return false
	}

	if match.MatchErr == ErrMethodMismatch {
		// We found a route which matches request method, clear MatchErr
		match.MatchErr = nil
		// Then override the mis-matched handler
		match.Handler = r.handler
	}

	// Yay, we have a match. Let's collect some info about it.
	if match.Route == nil {
		match.Route = r
	}
	if match.Handler == nil {
		match.Handler = r.handler
	}
	if match.Vars == nil {
		match.Vars = make(map[string]string)
	}

	// Set variables.
	if r.regexp != nil {
		r.regexp.setMatch(req, match, r)
	}
	return true
}

3.3 addMatcher方法

// addMatcher adds a matcher to the route.
func (r *Route) addMatcher(m matcher) *Route {
	if r.err == nil {
		r.matchers = append(r.matchers, m)
	}
	return r
}

3.4 handler的赋值接口

HandlerFunc是Route对象的方法,可以给一条Route注册一个回调函数。在r.Handler(http.HandlerFunc(f))中, 再次调用了Route的Handler函数

// Handler sets a handler for the route.
func (r *Route) Handler(handler http.Handler) *Route {
	if r.err == nil {
		r.handler = handler
	}
	return r
}

// HandlerFunc sets a handler function for the route.
func (r *Route) HandlerFunc(f func(http.ResponseWriter, *http.Request)) *Route {
	return r.Handler(http.HandlerFunc(f))
}

// GetHandler returns the handler for the route, if any.
func (r *Route) GetHandler() http.Handler {
	return r.handler
}

3.5 Subrouter方法

只有在父路由成功匹配后,才会转发给对应的子路由继续匹配。Router实现了一个Match方法,因此可以作为一个matcher对象加入到matchers中。当请求到达的时,router的ServeHTTP会找到Host注册的一个Route,Route在调用自己的Match方法时会遍历自己的matchers表,如果发现一个可以处理的子路由,就递归式地调用Match方法,直到找到对应的Handle。

// Subrouter creates a subrouter for the route.
//
// It will test the inner routes only if the parent route matched. For example:
//
//     r := mux.NewRouter()
//     s := r.Host("www.example.com").Subrouter()
//     s.HandleFunc("/products/", ProductsHandler)
//     s.HandleFunc("/products/{key}", ProductHandler)
//     s.HandleFunc("/articles/{category}/{id:[0-9]+}"), ArticleHandler)
//
// Here, the routes registered in the subrouter won't be tested if the host
// doesn't match.
func (r *Route) Subrouter() *Router {
	router := &Router{parent: r, strictSlash: r.strictSlash}
	r.addMatcher(router)
	return router
}
 类似资料: