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

gorilla/mux类库解析

娄学文
2023-12-01

简介

gorilla/mux实现了一个请求路由和分发的Go框架。“mux”的意思是“HTTP request multiplexer”,和标准包http.ServeMux类似,mux.Router根据已注册路由列表匹配传入请求,并调用与URL或其他条件匹配的路由的处理程序。

特点

  • 它实现了http.Handler接口,因此与标准的http.ServeMux兼容。
  • 可以基于URL主机,路径,路径前缀,方案,标头和查询值,HTTP方法或使用自定义匹配器来匹配请求。
  • URL主机,路径和查询值可以具有带可选正则表达式的变量。
  • 可以构建或“反转”已注册的URL,这有助于维护对资源的引用。
  • 路由可用作子路由:仅在父路由匹配时才测试嵌套路由。 这对于定义具有共同条件(例如主机,路径前缀或其他重复属性)的路由组很有用。 另外,这可以优化请求匹配

使用示例

r := mux.NewRouter()
    
//与http.ServerMux不同的是mux.Router是完全的正则匹配,设置路由路径/index/,如果访问路径/idenx/hello会返回404
//设置路由路径为/index/访问路径/index也是会报404的,需要设置r.StrictSlash(true), /index/与/index才能匹配
 r.HandleFunc("/index/", func(w http.ResponseWriter, r *http.Request) {
    w.Write([]byte("root path"))
})
     
//mux.Vars(r)会返回该请求所解析出的所有参数(map[string]string)
//访问/hello/ghbai 会输出 hello ghbai
 r.HandleFunc("/hello/{name:[a-zA-Z]+}", func(w http.ResponseWriter, r *http.Request) {
    w.Write([]byte(fmt.Sprintf("hello %s", mux.Vars(r)["name"])))
})
     
http.Handle("/", r)

源码实现

【Router的实现】

路由信息是存放在一个Route类型的数组([]Route)中,数组中的每一个Route对象都表示一条路由信息,其中包含匹配该路由应该满足的所有条件及对应的上层处理Hanlder。当请求到来是Router会遍历Route数组,找到第一个匹配的路由则执行对应的处理函数,如果找不到则执行NotFoundHandler。

type Router struct {
    routes []*Route
}
// Match matches registered routes against the request.
func (r *Router) Match(req *http.Request, match *RouteMatch) bool {
    for _, route := range r.routes {
        //Route.Match会检查http.Request是否满足其设定的各种条件(路径,Header,Host..)
        if route.Match(req, match) {
            return true
        }
    }
    return false
}
 
func (r *Router) ServeHTTP(w http.ResponseWriter, req *http.Request) {
    var match RouteMatch
    var handler http.Handler
    if r.Match(req, &match) {
        handler = match.Handler
    }
    if handler == nil {
        handler = http.NotFoundHandler()
    }
    handler.ServeHTTP(w, req)
}

【Route的实现】

每一个Route中包含一个matcher数组,是所有限定条件的集合,matcher是一个返回bool值的接口。当我们添加路由限定条件时,就是往matcher数组中增加一个限定函数。 当请求到来时,Route.Match()会遍历matcher数组,只有数组中所有的元素都返回true时则说明此请求满足该路由的限定条件。

假设我们规定只能以GET方式访问/user/{userid:[0-9]+}并且header中必须包含“Refer”:“example.com”,才能得到我们想要的结果,我们可以这样设置路由:

func userHandler(w http.ResponseWriter,r* http.Request) {
    w.write([]byte(fmt.Sprintf("user %s visited",mux.Vars(r)["userid"])))
}
r.HandleFunc("/user/{userid:[0-9]+}", userHandler)
.Methods("GET")
.Headers("Refer", "example.com")

然后我们来看下Route是如何保存这三个限定条件的:

type Route struct {
     // Request handler for the route.
    handler http.Handler
     
    // List of matchers.
    matchers []matcher
}
 
//添加Header限定条件,请求的header中必须含有“Refer”,值为“example.com”
func (r *Route) Headers(pairs ...string) *Route {
    if r.err == nil {
        var headers map[string]string
        //mapFromPairs返回一个map[string][string]{"Refer":"example.com"}
        headers, r.err = mapFromPairs(pairs...)
        return r.addMatcher(headerMatcher(headers))
    }
    return r
}
 
type headerMatcher map[string]string
func (m headerMatcher) Match(r *http.Request, match *RouteMatch) bool {
    //matchMap会判断r.Header是否含有“Refer”,并且值为“example.com”
    return matchMap(m, r.Header, true)
}
 
//methodMatcher就是取出r.Method然后判断该方式是否是设定的Method
type methodMatcher []string
func (m methodMatcher) Match(r *http.Request, match *RouteMatch) bool {
    return matchInArray(m, r.Method)
}
 
func (r *Route) Methods(methods ...string) *Route {
    for k, v := range methods {
        methods[k] = strings.ToUpper(v)
    }
    return r.addMatcher(methodMatcher(methods))
}
 
//带有正则表达式路径匹配是比较复杂的 tpl就是/user/{userid:[0-9]+}
func (r *Route) Path(tpl string) *Route {
    r.err = r.addRegexpMatcher(tpl, false, false, false)
    return r
}
 
func (r *Route) addRegexpMatcher(tpl string,strictSlash bool) error {
    //braceIndices判断{ }是否成对并且正确出现,idxs是'{' '}'在表达式tpl中的下标数组
    idxs, errBraces := braceIndices(tpl)
     
    template := tpl
    defaultPattern := "[^/]+"
    //保存所需要提取的所有变量名称,此例是userid
    varsN := make([]string, len(idxs)/2)
    var end int //end 此时为0
    pattern := bytes.NewBufferString("")
    for i := 0; i < len(idxs); i += 2 {
        raw := tpl[end:idxs[i]] //raw="/user/"
        end = idxs[i+1]
        parts := strings.SplitN(tpl[idxs[i]+1:end-1], ":", 2) //parts=[]{"userid","[0-9]+"}
        name := parts[0]  //name="userid"
        patt := defaultPattern
        if len(parts) == 2 {
            patt = parts[1] //patt="[0-9]+"
        }
        //构造出最终的正则表达式 /usr/([0-9]+)
        fmt.Fprintf(pattern, "%s(%s)", regexp.QuoteMeta(raw), patt)
        varsN[i/2] = name //将所要提取的参数名userid保存到varsN中
    }//如果有其他正则表达式继续遍历
      raw := tpl[end:]
    pattern.WriteString(regexp.QuoteMeta(raw))
    if strictSlash {
        pattern.WriteString("[/]?")
    }
    //编译最终的正则表达式
    reg, errCompile := regexp.Compile(pattern.String())
     
    rr = &routeRegexp{
        template:    template,
        regexp:      reg,
        varsN:       varsN,
    }
    r.addMatcher(rr)
}
 
func (r *routeRegexp) Match(req *http.Request, match *RouteMatch) bool {
    return r.regexp.MatchString(getHost(req))
}

【Context上下文】

路径匹配的最终正则表达式是/user/([0-9]+),参数名"userid"保存在varsN数组中,当正则匹配时提取出正则表达式中的参数值,并与varsN数组中的参数名称做关联,建立一个map[string][string]{“userid”:“123456”}

var Vars map[string]string
pathVars := regexp.FindStringSubmatch(req.URL.Path)
if pathVars != nil {
    for k, v := range varsN {
        Vars[v] = pathVars[k+1]
    }
}

因为gorilla/mux选择与http.ServerMux的接口保持一致,所以上层应用的处理函数也就变成了固定的 Hanlder。

type Handler interface {
    ServeHTTP(ResponseWriter, *Request)
}

正则匹配解析出的参数Vars怎么传递给上层处理函数呢?gorilla/mux使用了一个第三方模块gorilla/context。当http请求到来时,mux.Router会选择合适的路由,并提取出一些参数信息,将这些参数信息与http.Request对象在gorilla/context中建立映射关系,上层处理函数根据http.Request对象到context中找到该http.Request所对应的参数信息。

context的实现如下:

var data  = make(map[*http.Request]map[interface{}]interface{})
  
func Set(r *http.Request, key, val interface{}) {
    mutex.Lock()
    if data[r] == nil {
        data[r] = make(map[interface{}]interface{})
        datat[r] = time.Now().Unix()
    }
    data[r][key] = val
    mutex.Unlock()
}
 
func Get(r *http.Request, key interface{}) interface{} {
    mutex.RLock()
    if ctx := data[r]; ctx != nil {
        value := ctx[key]
        mutex.RUnlock()
        return value
    }
    mutex.RUnlock()
    return nil
}

上层处理函数中调用mux.Vars®则可以取出该http.Request所关联的参数信息:

//val实际上时一个map[string][string],存放该请求对应的变量值集合
func setVars(r *http.Request, val interface{}) {
    context.Set(r, varsKey, val)
}
 
func Vars(r *http.Request) map[string]string {
    if rv := context.Get(r, varsKey); rv != nil {
        //类型转换,如果失败直接panic
        return rv.(map[string]string)
    }
    return nil
}

参考博客

https://www.cnblogs.com/yjf512/archive/2012/08/22/2650873.html
https://www.cnblogs.com/renleimlj/p/7787220.html
https://www.cnblogs.com/foxy/p/8624434.html

 类似资料: