1. 程式人生 > >服務計算作業--mux原始碼

服務計算作業--mux原始碼

mux原始碼分析

文章大概內容介紹

首先要說的是雖然這一篇文章是想要能夠分析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訪問的錯誤處理的函數了,非常簡單直接用寫入頭的方式來返回錯誤。