首先要说的是虽然这一篇文章是想要能够分析mux的源码,但是可能因为个人水平原因同时也是我自己对于源码的态度,可能想要达到的效果是能够站在一个比较高层的角度去看这个源码,而不是说面面俱到地能够说清楚源码的每一行为什么要这么做。
Package gorilla/mux implements a request router and dispatcher for matching incoming requests to
their respective handler.
The name mux stands for “HTTP request multiplexer”. Like the standard http.ServeMux, mux.Router matches incoming requests against a list of registered routes and calls a handler for the route that matches the URL or other conditions. The main features are:
It implements the http.Handler interface so it is compatible with the standard http.ServeMux.
Requests can be matched based on URL host, path, path prefix, schemes, header and query values, HTTP methods or using custom matchers.
URL hosts, paths and query values can have variables with an optional regular expression.
Registered URLs can be built, or “reversed”, which helps maintaining references to resources.
Routes can be used as subrouters: nested routes are only tested if the parent route matches. This is useful to define groups of routes that share common conditions like a host, a path prefix or other repeated attributes. As a bonus, this optimizes request matching.
当面对一个新的源码时,在真正开始阅读代码之间,很重要的一步就是先要搞清楚这个源码是干什么的,要想解决这个需求,最理想的是这个源码的作者有良好的编程习惯,能够在readme里面写清楚源码的目的。
上面的部分就是mux库的作者所描述的mux库的作用和功能。
接下来我就概括一下作者说了些什么,首先就是作者写这个库的目的就是想要能够重复实现go的标准库net/http里面的mux和route的作用。
但是只是实现这些功能肯定是不可能被13.7k的projects所引用,这个mux库在原来标准库的基础上加入了一些新的功能其中分别有
· url中可以含有变量(以正则表达式的形式来概括)(当然是url中的path部分而不是域名是可变的,query部分没什么好说的标准的库可是可变的),从而可以更加方便地减少代码量和复用。
· route可以存在subroute,这就相当于是以一个树状的结构去匹配和分解在不同的handler上,同样也是非常实用的功能,因为原来的库的要求的route都只是扁平的一层的结构,究竟匹配到哪一个handler竟然还是要看编程时的顺序,这可是8
02年了啊,设计风格竟然还是c语言的那玩意怎么好意思让其他开发者买单,所以mux库树状的设计就可以大幅度减少设计者编程需要考虑的东西而是可以专注于编写逻辑功能的代码,真是开心极了。
enough talk about the readme,let’s get to the top of the source code.
import (
"errors"
"fmt"
"net/http"
"path"
"regexp"
)
首先看这个mux文件所引用的包,也不出所料,引用的正是readme所提到的一些功能的best solution。其中吸引我的是不知道go的regexp也就是正则表达式的库的设计是怎么样的,因为大家都知道,go是java和c一脉相传想来的,静态类型的语言的正则表达式有多难用相比大家是有目共睹的,而js和python的就好多了,不知道go会带给我们怎么样的惊喜。
// NewRouter returns a new router instance.
func NewRouter() *Router {
return &Router{namedRoutes: make(map[string]*Route), KeepContext: false}
}
// Router registers routes to be matched and dispatches a handler.
//
// It implements the http.Handler interface, so it can be registered to serve
// requests:
//
// var router = mux.NewRouter()
//
// func main() {
// http.Handle("/", router)
// }
//
// Or, for Google App Engine, register it in a init() function:
//
// func init() {
// http.Handle("/", router)
// }
//
// This will send all incoming requests to the router.
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
}
这就是这个库里面都核心的宝贝了,其中作者也介绍了这个东西应该怎么用,提到了同样也是按照go的标准http的规范,只有实现handler结构的struct都可以被绑定到http上,其他东西都还好说因为http没有继承这个东西所以所有类内变量都得自己一个个声明,至于类中含有的操作(函数)则是以接口的方式来在同一个文件里面实现的。存粹的java思想。
// 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
}
}
if match.MatchErr == ErrMethodMismatch {
if r.MethodNotAllowedHandler != nil {
match.Handler = r.MethodNotAllowedHandler
return true
}
return false
}
// Closest match for a router (includes sub-routers)
if r.NotFoundHandler != nil {
match.Handler = r.NotFoundHandler
match.MatchErr = ErrNotFound
return true
}
match.MatchErr = ErrNotFound
return false
}
以后就是这个mux改进标准库的地方了,通过引入了match这个操作可以实现进行不只是完美匹配而是可以判断是不是和subroute匹配了,具体细节上来说也没有什么好说的就是还是利用了标准库的match只不过在一个函数进行了所有的判断,这就是所谓的subroute的实现了。
// 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)
}
这个接口就是http需要的注册用的函数了,通过把输入进行预处理和判断,最后再调用http库的serverhttp来注册,相当于起到了一个中间的处理功能。
// Get returns a route registered with the given name.
func (r *Router) Get(name string) *Route {
return r.getNamedRoutes()[name]
}
// GetRoute returns a route registered with the given name. This method
// was renamed to Get() and remains here for backwards compatibility.
func (r *Router) GetRoute(name string) *Route {
return r.getNamedRoutes()[name]
}
同样的上面两个接口也是为了注册服务的接口,效果一看便知。
// StrictSlash defines the trailing slash behavior for new routes. The initial
// value is false.
//
// When true, if the route path is "/path/", accessing "/path" will perform a redirect
// to the former and vice versa. In other words, your application will always
// see the path as specified in the route.
//
// When false, if the route path is "/path", accessing "/path/" will not match
// this route and vice versa.
//
// The re-direct is a HTTP 301 (Moved Permanently). Note that when this is set for
// routes with a non-idempotent method (e.g. POST, PUT), the subsequent re-directed
// request will be made as a GET by most clients. Use middleware or client settings
// to modify this behaviour as needed.
//
// Special case: when a route sets a path prefix using the PathPrefix() method,
// strict slash is ignored for that route because the redirect behavior can't
// be determined from a prefix alone. However, any subrouters created from that
// route inherit the original StrictSlash setting.
func (r *Router) StrictSlash(value bool) *Router {
r.strictSlash = value
return r
}
以上是关于http url里面最后一个slash的设定,之前我搞后端的时候也没管这个多,原来这个玩意还是可以设置的啊,这个函数的作用就是如果设定时是true的就代表最后带有slash的也不会被re-direct到无slash的页面,这样做的用途可能是可以同时保证有两种handler,两种前缀都一样,唯一不一样的地方就在于只是最后有没有slash,但是真的在设计后端结构的时候真的会有人这么做吗,反正我是不会这么做的。
// SkipClean defines the path cleaning behaviour for new routes. The initial
// value is false. Users should be careful about which routes are not cleaned
//
// When true, if the route path is "/path//to", it will remain with the double
// slash. This is helpful if you have a route like: /fetch/http://xkcd.com/534/
//
// When false, the path will be cleaned, so /fetch/http://xkcd.com/534/ will
// become /fetch/http/xkcd.com/534
func (r *Router) SkipClean(value bool) *Router {
r.skipClean = value
return r
}
// UseEncodedPath tells the router to match the encoded original path
// to the routes.
// For eg. "/path/foo%2Fbar/to" will match the path "/path/{var}/to".
//
// If not called, the router will match the unencoded path to the routes.
// For eg. "/path/foo%2Fbar/to" will match the path "/path/foo/bar/to"
func (r *Router) UseEncodedPath() *Router {
r.useEncodedPath = true
return r
}
以上的代码还是和之前的判断是一样的,都是用来注册一些用来处理url的选项。
// ----------------------------------------------------------------------------
// parentRoute
// ----------------------------------------------------------------------------
func (r *Router) getBuildScheme() string {
if r.parent != nil {
return r.parent.getBuildScheme()
}
return ""
}
// getNamedRoutes returns the map where named routes are registered.
func (r *Router) getNamedRoutes() map[string]*Route {
if r.namedRoutes == nil {
if r.parent != nil {
r.namedRoutes = r.parent.getNamedRoutes()
} else {
r.namedRoutes = make(map[string]*Route)
}
}
return r.namedRoutes
}
// getRegexpGroup returns regexp definitions from the parent route, if any.
func (r *Router) getRegexpGroup() *routeRegexpGroup {
if r.parent != nil {
return r.parent.getRegexpGroup()
}
return nil
}
func (r *Router) buildVars(m map[string]string) map[string]string {
if r.parent != nil {
m = r.parent.buildVars(m)
}
return m
}
以上的代码也都是为了实现之前承诺的带有变量(正则表达式)的url的代码,其中可以注意到的就是利用了引用的regexp的包。
// Methods registers a new route with a matcher for HTTP methods.
// See Route.Methods().
func (r *Router) Methods(methods ...string) *Route {
return r.NewRoute().Methods(methods...)
}
// Path registers a new route with a matcher for the URL path.
// See Route.Path().
func (r *Router) Path(tpl string) *Route {
return r.NewRoute().Path(tpl)
}
// PathPrefix registers a new route with a matcher for the URL path prefix.
// See Route.PathPrefix().
func (r *Router) PathPrefix(tpl string) *Route {
return r.NewRoute().PathPrefix(tpl)
}
// Queries registers a new route with a matcher for URL query values.
// See Route.Queries().
func (r *Router) Queries(pairs ...string) *Route {
return r.NewRoute().Queries(pairs...)
}
// Schemes registers a new route with a matcher for URL schemes.
// See Route.Schemes().
func (r *Router) Schemes(schemes ...string) *Route {
return r.NewRoute().Schemes(schemes...)
}
// BuildVarsFunc registers a new route with a custom function for modifying
// route variables before building a URL.
func (r *Router) BuildVarsFunc(f BuildVarsFunc) *Route {
return r.NewRoute().BuildVarsFunc(f)
}
// Walk walks the router and all its sub-routers, calling walkFn for each route
// in the tree. The routes are walked in the order they were added. Sub-routers
// are explored depth-first.
func (r *Router) Walk(walkFn WalkFunc) error {
return r.walk(walkFn, []*Route{})
}
// SkipRouter is used as a return value from WalkFuncs to indicate that the
// router that walk is about to descend down to should be skipped.
var SkipRouter = errors.New("skip this router")
// WalkFunc is the type of the function called for each route visited by Walk.
// At every invocation, it is given the current route, and the current router,
// and a list of ancestor routes that lead to the current route.
type WalkFunc func(route *Route, router *Router, ancestors []*Route) error
func (r *Router) walk(walkFn WalkFunc, ancestors []*Route) error {
for _, t := range r.routes {
err := walkFn(t, r, ancestors)
if err == SkipRouter {
continue
}
if err != nil {
return err
}
for _, sr := range t.matchers {
if h, ok := sr.(*Router); ok {
ancestors = append(ancestors, t)
err := h.walk(walkFn, ancestors)
if err != nil {
return err
}
ancestors = ancestors[:len(ancestors)-1]
}
}
if h, ok := t.handler.(*Router); ok {
ancestors = append(ancestors, t)
err := h.walk(walkFn, ancestors)
if err != nil {
return err
}
ancestors = ancestors[:len(ancestors)-1]
}
}
return nil
}
以上的这些接口也都是为了handler注册时候使用的,有些是直接用了http库的参数,只不过是因为不能继承所以还是要写上去,真是麻烦。
// methodNotAllowed replies to the request with an HTTP status code 405.
func methodNotAllowed(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusMethodNotAllowed)
}
// methodNotAllowedHandler returns a simple request handler
// that replies to each request with a status code 405.
func methodNotAllowedHandler() http.Handler { return http.HandlerFunc(methodNotAllowed) }
终于快结束了,最后这两个函数就是针对http访问的错误处理的函数了,非常简单直接用写入头的方式来返回错误。