gorilla/mux(HTTP request multiplexer,github地址)是一个对请求进行路由的实现。它有以下这些特点:
http.Handler
接口,因此可以兼容标准的http.ServeMux
mux包的功能很多,下面列出几个重要的功能,下文将会尝试阅读这些主要功能实现的代码。当然mux的功能还有其他一些强大的功能(如静态文件,获取注册URL,遍历路由等),本文限于篇幅,暂不作更多阐述。
func main() {
r := mux.NewRouter()
r.HandleFunc("/", HomeHandler)
r.HandleFunc("/products", ProductsHandler)
r.HandleFunc("/articles", ArticlesHandler)
http.Handle("/", r)
}
r := mux.NewRouter()
r.HandleFunc("/products/{key}", ProductHandler)
r.HandleFunc("/articles/{category}/", ArticlesCategoryHandler)
r.HandleFunc("/articles/{category}/{id:[0-9]+}", ArticleHandler)
func ArticlesCategoryHandler(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)
w.WriteHeader(http.StatusOK)
fmt.Fprintf(w, "Category: %v\n", vars["category"])
}
r.Headers("X-Requested-With", "XMLHttpRequest")
r.MatcherFunc(func(r *http.Request, rm *RouteMatch) bool {
return r.ProtoMajor == 0
})
r.HandleFunc("/products", ProductsHandler).
Host("www.example.com").
Methods("GET").
Schemes("http")
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)
func loggingMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
// Do stuff here
log.Println(r.RequestURI)
// Call the next handler, which can be another middleware in the chain, or the final handler.
next.ServeHTTP(w, r)
})
}
r := mux.NewRouter()
r.HandleFunc("/", handler)
r.Use(loggingMiddleware)
代码主要部分有下面四个模块:
在整个mux中,核心类是一个叫Router的类,而另一个同样重要,名字类似的类是Route。从名字上看来,前者是一个路由器类型,后者是某一条具体的路由。也就是说,在一个路由器中,应该会包含多个路由(在代码中,这些Route被存放在了Router中一个名叫routes的数组中)。而一条路由记录将会保存一个处理函数句柄Handler,选择正确的句柄处理到达的请求,就是路由的工作。
在上面的Demo中可以看见,Router在http.Handle("/", r)
中,可以作为func Handle(pattern string, handler Handler)
的第二个参数传入,因此,Router事实上是Handler
的一个实现。注意到,Handler
接口的定义
// packet: net/http
type Handler interface {
ServeHTTP(ResponseWriter, *Request)
}
这个route实现了ServeHTTP(ResponseWriter, *Request)
方法,因此可以作为func Handle(pattern string, handler Handler)
函数的参数。
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)
}
我们现在先看看这个ServeHTTP的实现。里面对r.skipClean
的判断是用于判断是否跳过对路径进行清洗(比如,去掉不需要的双下滑线:把“/fetch/http://xkcd.com/534/”
清洗成“/fetch/http/xkcd.com/534/”
),并将重定向至清洗后的链接。
接下来,是利用一个r.Match
获取一个合适的匹配器RouteMatch
。在经过一系列的正确性确认后,方法将调用返回的handle服务发送到的请求。
这里我们可以研究一下Match的方法:
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
}
注意到,方法里的第一段就是Match的关键代码,程序遍历所有route,尝试找到一个能匹配当前请求的route。当确认匹配成功后,这里有一段有趣的代码,用中间件包装了处理函数:
for i := len(r.middlewares) - 1; i >= 0; i-- {
match.Handler = r.middlewares[i].Middleware(match.Handler)
}
这里可以把这段代码理解成在handle函数上面逐层包装构造出新的handle函数的过程。
[代码评价]因为将handle和middleware组装的工作可以在注册middleware时完成,mux在收到每一个请求时都重做这个工作显得多余。除非允许中间件的动态增删,否则这种重复工作不可避免地会带来性能的损失
位置:mux.go
func (r *Router) HandleFunc(path string, f func(http.ResponseWriter,
*http.Request)) *Route {
return r.NewRoute().Path(path).HandlerFunc(f)
}
HandleFunc是用于注册的函数,事实上这个函数相当简单,就是在r的routes表里添加新的一项,在设置这一项的Path以及处理函数。像定义Path这类型的函数有一整个系列:例如:
func (r *Route) Headers(pairs ...string) *Route
:增加头部筛选条件func (r *Route) Host(tpl string) *Route
:增加主机筛选条件func (r *Route) Methods(methods ...string) *Route
:增加Methods筛选条件(“GET",“POST”,“PUT”)func (r *Route) Queries(pairs ...string) *Route
:增加Query参数的筛选条件Headers
的添加采用的是同样的代码设计。这里反映的是一种组合的设计模式,同样的一个Route类型,通过注册不同类型的筛选器,可以组合出不同的筛选结果。[代码评价]组合优于继承,组合给代码带来极大的灵活性。
这里涉及到的另外一个技巧是链式的代码操作。即我们可以把代码写成这样的形式:
r.NewRoute().Path(path).HandlerFunc(f)
而不是
ro = r.NewRoute()
ro.Path(path)
ro.HandlerFunc(f)
前者显然比后者有着更简洁优雅的形式。我们之所以可以把代码写成这样的链式形式,是因为Path,HandleFunc等函数都返回了它要设置的对象本身。对需要一系列设置的对象,这种链式的设置使得代码更简洁易用。
位置:mux.go
func (r *Router) Headers(pairs ...string) *Route {
return r.NewRoute().Headers(pairs...)
}
这其实就是一种需要组合的设置,此处不多阐述。
位置:route.go
func (r *Route) Subrouter() *Router {
router := &Router{parent: r, strictSlash: r.strictSlash}
r.addMatcher(router)
return router
}
这里就是简单地创建一个新的子路由,挂载到父路由上面去的过程。我们好奇的问题是,下面这个功能是如何实现的:只有在父路由成功匹配后,才会转发给对应的子路由继续匹配?查看addMatcher
的代码
func (r *Route) addMatcher(m matcher) *Route {
if r.err == nil {
r.matchers = append(r.matchers, m)
}
return r
}
有意思的东西出现了,addMatcher
接受的是一个matcher参数,我们传入的却是一个router。找到matcher
的定义:
type matcher interface {
Match(*http.Request, *RouteMatch) bool
}
这就非常清楚了:Router
实现了一个Match
方法,因此可以作为一个matcher对象加入到matchers中。回过头看一下整个运作过程
r := mux.NewRouter()
s := r.Host("www.example.com").Subrouter()
s.HandleFunc("/products/", ProductsHandler)
当有一条请求"www.example.com/products/“到达的时候,r的ServeHTTP会找到Host注册的一个Route,Route在调用自己的Match方法时会遍历自己的matchers表,发现了一个可以处理”/products/"的子路由,并递归式地调用Match方法,直到找到了对应的Handle。
位置:middleware.go
func (r *Router) Use(mwf ...MiddlewareFunc) {
for _, fn := range mwf {
r.middlewares = append(r.middlewares, fn)
}
}
前面提到,这里注册中间件时,只是简单地把所有中间件放到一个数组里,直到使用的时候才对其进行组装。因此这里没有什么特别的东西。由于中间件只会增加不会取走,因此在注册时完成组装将是更好的选择。
所有路由器的不同都在于他们的ServeHTTP
不同,DefaultServeMux
的ServeHTTP
函数如下:
func (mux *ServeMux) ServeHTTP(w ResponseWriter, r *Request) {
if r.RequestURI == "*" {
if r.ProtoAtLeast(1, 1) {
w.Header().Set("Connection", "close")
}
w.WriteHeader(StatusBadRequest)
return
}
h, _ := mux.Handler(r)
h.ServeHTTP(w, r)
}
这里是从mux里面直接获得了处理函数,并交由函数处理,并没有作其他太多工作了。