golang中ServeMux解析

brownpeacock 发布于1年前 阅读11851次
0 条评论

ServeMux解析

总览

本来是想做一个UML出来让这篇解析更清晰一点的,但是markdown的UML语法我一直捣鼓不出来。试了几个软件感觉也没有
想象中的好用和方便,看来是时候自己开发一个了(笑).
口述一个流程,具体的函数大家可以页内跳转去看.
首先我们是通过ListenAndServe来监听本地端口的,之后ListenAndServe将收到的新建一个Response连同收到的Request
作为参数调用ServeMux结构体ServeHTTP(省略了中间过程).ServeHTTP将
Request作为参数调用
Handler函数,Handler的返回值为一个Handler类型的接口,ServeHTTP会调用接口实现的ServeHTTP处理Response.

如果Request.URL.Path中有不合法的内容,则调用cleanPath清理,随后将Request.Host以及清理后的
内容传入handler函数,随后返回一个RedirectHandler以及handler所返回的路径。如果
Request.URL.Path合法,那么
直接调用handler,返回值与handler返回值相同。

handler中通过判断ServeMux.hosts来决定是否实现pattern = r.Host + r.URL.Path.之后将pattern作为参数调用match,并将
match的返回值返回.

match的判别方式比较"有趣",它虽然没实现为树形结构(只是用了映射),但是搜索的方法就是树形,因为URL路径就是个树形.它按照树的根节点
与子节点的关系进行判断,譬如路径"/home/select/usercourse",match在匹配的时候会首先匹配到"/"(假如我们注册了),其次是"/home",
之后逐层匹配下来,假如我们没注册过"/home/select/usercourse",但是注册了"/home/select/",那么match就会匹配到这一层.然后返回
"/home/select/"的Handler以及url(pattern).match函数的匹配规则实现在pathMatch

ServeMux结构体

type ServeMux struct {
        mu    sync.RWMutex
        m     map[string]muxEntry
        hosts bool // whether any patterns contain hostnames
    }

type muxEntry struct {
        explicit bool
        h        Handler
        pattern  string
    }

这样看起来还蛮直观的,mu是一个互斥锁,m则是我们所要用的路由(router),其中的键(key)则是我们挂载的路径,
对应的值则是响应的muxEntry,hosts指明了我们是否在每个路径中都声明了hostnames.比如我们平常用'/'来表示
挂载在域名根目录下的HandlerFunc,但是如果hosts=true,那么我们就需要'127.0.0.1/'来做同样的事情了.

下面我们来看muxEntry,h比较明确,就是我们注册的处理过程.pattern与ServeMux中m的key相同,也就是说是我们
注册的路径,而explicit的用途其实是比较隐晦的。程序会根据explicit判别这个路径的Handler是否是用户注册的。
如果是程序为了Redirect(详细点击这里)而设定的,那么在它是可以被覆盖的,否则就是不可以被覆盖的.

NewServeMux()

DefaultServeMux变量就是直接调用的这个函数。那么这个函数只make了一个变量m,也就是为map分配了空间。
在golang的语法中,结构体里未声明的变量也是存在的(即分配了内存),只不过是该类型的默认值,比如hosts就
被设置成了false.这个问题不过多解释,有兴趣的话可以看一下golang中的相关内容

pathMatch()

这个函数是ServeMux用来匹配路径的主要函数,所以看一下策略还是很重要的.
函数中的参数pattern是我们注册的路径,path是用户请求的路径

func pathMatch(pattern, path string) bool {
    if len(pattern) == 0 {
        // should not happen
        return false
    }
    n := len(pattern)
    if pattern[n-1] != '/' {
        return pattern == path
    }
    return len(path) >= n && path[0:n] == pattern
}

如果我们挂载的路径不是以'/'结尾的,那么就直接判断两个参数是否相同。如果是以'/'结尾的,只要path的路径包含
pattern那么就被判定是匹配。也就是说,如果我注册了/home/select/,那么/home/select/hello也会被定位到/home/select/
挂载的HandlerFunc上.这样做相当于为路径设置了一个index,不符合规则的URL都会被Redirect到这个index上

* ServeMux.Handler()

注释写的很清楚,这个函数就是处理URL,然后调用*ServeMux.handler().首先调用cleanPath清理请求URL中的不合法内容。如果存在不合法内容,
则将清理过的URL交由*ServeMux.handler()处理并获得匹配到的pattern,然后修改url.Path的内容并调用RedirectHandler.
如果内容合法,则直接调用*ServeMux.handler()并返回结果

* ServeMux.handler()

调用ServeMux.match()(封装了pathMatch函数)来获得匹配到的Handler以及对应pattern,如果ServeMux.hosts==true,那么
传入的参数为host + path,如果找不到的话,调用NotFoundHandler函数,并将其结果返回.

* ServeMux.Handle()

Handle函数是用来注册路径与处理过程的.如果该路径已经存在了一个用户注册的Handler则会panic(意思就是说不支持覆盖).判别了合法参数以后就将
pattern作为key,新建一个muxEntry类型变量作为value加入到map中。

if pattern[0] != '/' {
    mux.hosts = true
}

这是这个函数中比较有意思的一个部分,通过这里我们可以看到如果注册路径的时候并不是以'/'开头的,那么ServeMux就会开启hosts,然后会在
请求到达的时候将URL.Host和URL.Path连接在一起放入match中寻找,具体信息请看这里

接下来是关于路径的处理,也就是关于"/home"与"/home/"的区别.我们先来看看作者怎么说

    // Helpful behavior:
    // If pattern is /tree/, insert an implicit permanent redirect for /tree.
    // It can be overridden by an explicit registration.

如果路径的末尾是以'/'结尾并且该路径去掉末尾的'/'以后并没有被注册.那么将会去掉'/'并且为其绑定一个Redirect到现在的路径.
我自己写起来都觉得绕,举个例子就清楚了.
我注册了一个路径"/home/",但是没有注册"/home",那么如果用户访问了"/home"会发生什么呢?是的,会被Redirect到"/home/".
需要注意的是,这里的muxEntry中的explicit没有填,也就是说是false,那么即是可以覆盖的.

* ServeMux.ServeHTTP()

ServeHTTP会检测非法的URI(* )
如果通过检测就会调用自身的Handler()来返回注册的Handler,随后调用Handler的ServeHTTP方法

需要 登录 后回复方可回复, 如果你还没有账号你可以 注册 一个帐号。