神甫 发表于 2018-9-20 06:53:00

golang中ServeMux解析

ServeMux解析


[*]总览
[*]ServeMux结构体
[*]NewServeMux
[*]pathMatch
[*]* ServeMux.Handler
[*]* ServeMux.handler
[*]* ServeMux.Handle
[*]* ServeMux.ServeHTTP
总览
  本来是想做一个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   mapmuxEntry
  hosts bool // whether any patterns contain hostnames
  }
  

  
type muxEntry struct {
  explicit bool
  h      Handler
  patternstring
  }
  

  这样看起来还蛮直观的,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 != '/' {
  return pattern == path
  }
  return len(path) >= n && path == 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 != '/' {  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方法


页: [1]
查看完整版本: golang中ServeMux解析