golang自带的http.SeverMux路由实现简单,本质是一个map[string]Handler,是请求路径与该路径对应的处理函数的映射关系。实现简单功能也比较单一:

  1. 不支持正则路由, 这个是比较致命的
  2. 只支持路径匹配,不支持按照Method,header,host等信息匹配,所以也就没法实现RESTful架构

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

使用示例

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的实现其实也比较简单,正则表达式的解析不太好理解,subrouter的实现更是需要好好研究代码,但是这些对理解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(r)则可以取出该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
}

  

gorilla/mux类库解析的更多相关文章

  1. gorilla/mux 的学习

    原文链接:gorilla/mux的学习 源代码: package main import ( "encoding/json" "fmt" "githu ...

  2. 使用google-gson类库解析json文件

    使用google-gson类库解析json文件 使用JsonParser解析器来解析字符串和输入流,变成json对象 代码如下: public class Readjson { public stat ...

  3. 从源码对比DefaultServeMux 与 gorilla/mux

    从源码对比DefaultServeMux 与 gorilla/mux DefaultServeMux Golang自带的net/http库中包含了DefaultServeMux方法,以此可以搭建一个稳 ...

  4. 浅谈 Java 主流开源类库解析 XML

    在大型项目编码推进中,涉及到 XML 解析问题时,大多数程序员都不太会选用底层的解析方式直接编码. 主要存在编码复杂性.难扩展.难复用....,但如果你是 super 程序员或是一个人的项目,也不妨一 ...

  5. Java-XML解析第一篇主流开源类库解析XML

    1.流行的XML解析框架 1>底层解析方式:存在编码复杂性.难扩展.难复用.....想了解底层解析方式请参考:浅谈 Java XML 底层解析方式 2>Dom4j:基于 JAXP 解析方式 ...

  6. iOS学习之数据解析

    解析:按照约定好的格式提取数据的过程叫做解析; 后台开发人员按照约定好的格式存入数据,前端开发人员按照约定的格式读取数据; 主流的格式: XML / JSON 前端和后台都能识别的格式;  XML解析 ...

  7. LIMS系统仪器数据采集-使用xpdf解析pdf内容

    不同语言解析PDF内容都有各自的库,比如Java的pdfbox,.net的itextsharp. c#解析PDF文本,关键代码可参考: http://www.cnblogs.com/mahongbia ...

  8. php 日志模块源码解析

    php日志模块设计 Monolog 是PHP的一个日志类库解析 整体介绍:monolog日志模块遵循 PSR3 的接口规范.主要有日志格式类接口(格式化日志信息),处理类接口(写日志的驱动,通过扩展写 ...

  9. Spring 中参数名称解析 - ParameterNameDiscoverer

    Spring 中参数名称解析 - ParameterNameDiscoverer Spring 系列目录(https://www.cnblogs.com/binarylei/p/10198698.ht ...

随机推荐

  1. hdu 6435 CSGO

    题意:现在有n个主武器, m个副武器, 你要选择1个主武器,1个副武器, 使得 题目给定的那个式子最大. 题解:这个题目困难的地方就在于有绝对值,| a - b | 我们将绝对值去掉之后 他的值就为 ...

  2. JavaScript中的Cookie 和 Json的使用

    JavaScript中的Cookie 和 Json的使用 JSON JSON(JavaScript Object Notation)是一种轻量级的数据交换格式.采用的是完全独立于编程语言的文本格式来存 ...

  3. SVN更新失败

    一.svn更新失败 使用svn遇到的问题是,更新失败,代码被锁定. 解决办法: 在项目上右键,如图所示: 图一: ​ 图二: ​ 之后再更新,基本上都没有问题了.如果还有问题,看下面. 二.工具清理 ...

  4. vue 页面跳转传参

    页面之间的跳转传参,正常前端js里写 window.location.href="xxxxx?id=1" 就可以了: 但是vue不一样 需要操作的是路由history,需要用到 V ...

  5. Android之MVP设计模式

    一.概述 MVP设计模式的前身是MVC,这个无需再议 在安卓工程中MVC对应关系如下: Layout->View : 对应布局文件Activity->Controller,View (其中 ...

  6. .Net基础篇_学习笔记_第六天_For循环语法

    For循环:专门处理已知循环次数的循环.  小技巧:连续敲击两下TAB键循环体自动搭建完成. For循环语法: for(表达式1;表达式2;表达式3){ 循环体;}表达式1一般为声明循环变量,记录循环 ...

  7. 模板引擎Velocity学习系列-#set指令

    #set指令 #set指令用于向一个变量或者对象赋值. 格式: #set($var = value) LHS是一个变量,不要使用特殊字符例如英文句号等,不能用大括号括起来.测试发现#set($user ...

  8. 2019-2020-1 20199314 <Linux内核原理与分析>第一周作业

    前言 本周对实验楼的Linux基础入门进行了学习,目前学习到实验九完成到挑战二. 学习和实验内容 快速学习了Linux系统的发展历程及其简介,学习了下的变量.用户权限管理.文件打包及压缩.常用命令的和 ...

  9. 让我们一起学习如何使用AIDL,它其实并不难(Android)

    前言 该篇文件讲述的是AIDL最基本的使用(创建.调用),关于对于AIDL更深的认识,在后续的随笔中,会持续与大家分享并探讨. 正文 AIDL的定义(什么是AIDL?) AIDL的应用场景(AIDL可 ...

  10. Vue学习之vue属性绑定和双向数据绑定

    <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8&quo ...