# 一、执行流程
首先我们构建一个简单http server:

```go
package main

import (
"log"
"net/http"
)

func main() {
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("hello world"))
})
log.Fatal(http.ListenAndServe(":8080", nil))
}

```
使用`http://127.0.0.1:8080/` 就可以看到输出了

通过跟踪http.go包代码,可以发现执行流程基本如下:

- 1.创建一个`Listener`监听`8080`端口

- 2.进入`for`循环并Accept请求,没有请求则处于阻塞状态

- 3.接收到请求,并创建一个conn对象,放入goroutine处理(实现高并发关键)

- 4.解析请求来源信息获得请求路径等重要信息

- 5.请求ServerHTTP方法,已经通过上一步获得了ResponseWriter和Request对象

```go
func (sh serverHandler) ServeHTTP(rw ResponseWriter, req *Request) {
//此handler即为http.ListenAndServe 中的第二个参数
handler := sh.srv.Handler
if handler == nil {
//如果handler为空则使用内部的DefaultServeMux 进行处理
handler = DefaultServeMux
}
if req.RequestURI == "*" && req.Method == "OPTIONS" {
handler = globalOptionsHandler{}
}
//这里就开始处理http请求
//如果需要使用自定义的mux,就需要实现ServeHTTP方法,即实现Handler接口。
handler.ServeHTTP(rw, req)
}
```

- 6.进入DefaultServeMux中的逻辑就是根据请求path在map中匹配查找handler,并交由handler处理

>http请求处理流程更多信息可以参考[《Go Web 编程
》3.3 Go如何使得Web工作](https://github.com/astaxie/build-web-application-with-golang/blob/master/zh/03.3.md)

# 二、DefaultServeMux 路由匹配规则
先看几个路由规则:

```go
package main

import (
"log"
"net/http"
)

func main() {
//规则1
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("hello world"))
})

//规则2
http.HandleFunc("/path/", func(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("pattern path: /path/ "))
})

//规则3
http.HandleFunc("/path/subpath", func(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("pattern path: /path/subpath"))
})

log.Fatal(http.ListenAndServe(":8080", nil))
}

```
情景一:

访问:`http://127.0.0.1:8080/`

返回:`hello world`

情景二:

访问:`http://127.0.0.1:8080/path`

返回:`pattern path: /path/ `

情景三:

访问:`http://127.0.0.1:8080/path/subpath/`

返回:`pattern path: /path/ `

情景四:

访问:`http://127.0.0.1:8080/hahaha/`

返回:`hello world`

先说明一些规则吧,再看代码是怎么实现的:

1.如果匹配路径中后带有`/`,则会自动增加一个匹配规则不带`/`后缀的,并跳转转到`path/`,解释了情景二的场景,为什么匹配到的`/path/`

2.我设置了这么多规则为什么规则一可以通用匹配未设置的路由信息,而且又不影响已经存在路由, 内部是怎么实现的?

## 2.1 添加路由规则
先看两个struct,这是存放默认路由规则的:

```go
type ServeMux struct {
mu sync.RWMutex //处理并发,增加读写锁
m map[string]muxEntry //存放规则map,key即为设置的path
hosts bool // whether any patterns contain hostnames(是否包含host)
}

type muxEntry struct {
explicit bool //是否完全匹配
h Handler//相应匹配规则的handler
pattern string//匹配路径
}

```

通过跟踪`http.HandleFunc`定位到如下代码,正是往上面两个`struct`中增加规则:

```go
func (mux *ServeMux) Handle(pattern string, handler Handler) {
mux.mu.Lock()
defer mux.mu.Unlock()

if pattern == "" {
panic("http: invalid pattern " + pattern)
}
if handler == nil {
panic("http: nil handler")
}
//如果已经匹配到了则panic
if mux.m[pattern].explicit {
panic("http: multiple registrations for " + pattern)
}

//增加一个新的匹配规则
mux.m[pattern] = muxEntry{explicit: true, h: handler, pattern: pattern}

//根据path的第一个字母判断是否有host
if pattern[0] != '/' {
mux.hosts = true
}

//!!这里看清楚 就是实现了情景二的情况 ,看判断条件
n := len(pattern)
if n > 0 && pattern[n-1] == '/' && !mux.m[pattern[0:n-1]].explicit{
// If pattern contains a host name, strip it and use remaining
// path for redirect.
path := pattern
if pattern[0] != '/' {
// In pattern, at least the last character is a '/', so
// strings.Index can't be -1.
path = pattern[strings.Index(pattern, "/"):]
}
url := &url.URL{Path: path}
mux.m[pattern[0:n-1]] = muxEntry{h: RedirectHandler(url.String(), StatusMovedPermanently), pattern: pattern}
}
}

```

上面有个`Helpful behavior`的注释行为,就是实现了情景二的情况,他是判断如果匹配的路径中最后含有`/`,并且之前也不存在添加去除反斜杠的规则的话,就自动给他增加一个301的跳转指向`/path/`

## 2.2 查找路由规则

路由规则的查找就是从`ServeMux `中的map去匹配查找的,的到这个handler并执行,只是会有一些处理机制,比如怎么样确保访问`/path/subpath`的时候是先匹配`/path/subpath`而不是匹配`/path/`呢?

当一个请求过来的时候,跟踪到了`mux.match`方法:

>过程`mux.ServerHTTP`->`mux.Handler`->`mux.handler`->`mux.match`

```go
func (mux *ServeMux) match(path string) (h Handler, pattern string) {
var n = 0
for k, v := range mux.m {
if !pathMatch(k, path) {
continue
}
//如果匹配到了一个规则,并没有马上返回handler,而且继续匹配并且判断path的长度是否是最长的,这是关键!!!
if h == nil || len(k) > n {
n = len(k)
h = v.h
pattern = v.pattern
}
}
return
}
```

1.这里就解释了为什么设置的精确的path是最优匹配到的,因为它是根据path的长度判断。
当然也就解释了为什么`/`可以匹配所有(看`pathMatch`函数就知道了,`/`是匹配所有的,只是这是最后才被匹配成功)

2.得到了处理请求的handler,再调用`h.ServeHTTP(w, r)`,去执行相应的handler方法。

等一下,handler中哪里有`ServeHTTP`这个方法??

因为在调用 `http.HandleFunc`的时候已经将自定义的handler处理函数,强制转为`HandlerFunc`类型的,就拥有了`ServeHTTP`方法:

```go
type HandlerFunc func(ResponseWriter, *Request)

// ServeHTTP calls f(w, r).
func (f HandlerFunc) ServeHTTP(w ResponseWriter, r *Request) {
f(w, r)
}

```

`f(w,r)`就实现了handler的执行。

> 关注"学点程序"公众号,了解更多干货内容 ![学点程序](https://silenceper.oss-cn-beijing.aliyuncs.com/qrcode/qr_code_study_program_430.jpg
)

Go的http包中默认路由匹配规则的更多相关文章

  1. djanjo中url路由匹配规则是啥意思

    一,django路由匹配规则的本质是通过正则表达式对用户的url进行匹配. 1,r 是正则表达式中防止转义的符号,例如在python/n代表换行,加上r就不换行了. 2,$ 正则表达式中表示以什么什么 ...

  2. LinuxC下获取UDP包中的路由目的IP地址和头标识目的地址

    在接受到UDP包后,有时候我们需要根据所接收到得UDP包,获取它的路由目的IP地址和头标识目的地址. (一)主要的步骤: 在setsockopt中设置IP_PKTINFO,然后通过recvmsg来获取 ...

  3. JavaScript中正则表达式判断匹配规则以及常用的方法

    JavaScript中正则表达式判断匹配规则以及常用的方法: 字符串是编程时涉及到的最多的一种数据结构,对字符串进行操作的需求几乎无处不在. 正则表达式是一种用来匹配字符串的强有力的武器.它的设计思想 ...

  4. MVC路由中特殊URL匹配规则

    *匹配*用来匹配URL剩余部分 贪婪匹配规则贪婪匹配会找到最后一个符合条件的“字面量”为止

  5. js中的路由匹配

    routie插件:http://projects.jga.me/routie/ /** * 路由 * @example * routie( * { * '/':function(){ }, * '/m ...

  6. 【RegExp】JavaScript中正则表达式判断匹配规则以及常用方法

    字符串是编程时涉及到的最多的一种数据结构,对字符串进行操作的需求几乎无处不在. 正则表达式是一种用来匹配字符串的强有力的武器.它的设计思想是用一种描述性的语言来给字符串定义一个规则,凡是符合规则的字符 ...

  7. nginx中的location匹配规则

    概述: 1. location在nginx配置文件中的作用是根据用户请求的URI来执行不同的应用. 2.URI的定义:标识.定位任何资源的字符串 协议://域名/目录a/目录b/文件c http:// ...

  8. ASP.NET MVC教程五:ASP.NET MVC中的路由

    一.概述 在ASP.NET MVC架构中,控制器在3大核心构件中处于中心地位,通过控制器支配模型和视图,然而从浏览器发出的请求到控制器还需要路由的协助,路由将特定的请求和控制器的动作对应起来. 在AS ...

  9. ASP.NET Core 中的SEO优化(3):自定义路由匹配和生成

    前言 前两篇文章主要总结了CMS系统两个技术点在ASP.NET Core中的应用: <ASP.NET Core 中的SEO优化(1):中间件实现服务端静态化缓存> <ASP.NET ...

随机推荐

  1. ASP.NET MVC 实现页落网资源分享网站+充值管理+后台管理(5)之业务层

    业务层主要负责定义业务逻辑(规则.工作流.数据完整性等),接收来自表示层的数据请求,逻辑判断后,向数据访问层提交请求,并传递数据访问结果,业务逻辑层实际上是一个中间件,起着承上启下的重要作用. 在我们 ...

  2. JS异步解决方案

    前言 异步最早的解决方案是回调函数,如ajax,事件的回调,setInterval/setTimeout中的回调.但是回调函数有回调地狱的问题; 为了解决回调地狱的问题,社区提出了Promise解决方 ...

  3. Makefile记录

    需要把sum.c编译汇编成可执行程序zzj zzj:sum.o gcc -o zzj sum.osum.o:sum.c gcc -c -o sum.o sum.cclean: rm -rf *.o z ...

  4. Flutter 添加阴影效果

    Container( width: double.infinity, height: ScreenUtil.getInstance().setHeight(500), decoration: BoxD ...

  5. 使用Pandas加载数据

    1.dataframe对象简述: dataframe为pandas中一种有行列索引的二维数据结构,可以看成在普通二维结构上加上行列id标记 示例为创建一个2X3的dataframe: import s ...

  6. LabWindows/CVI基础

    1.LabWindows/CVI了解 提到NI公司,大家可能最先联想到的是NI公司推出的LabVIEW软件.LabWindows/CVI与LabVIEW相比,主要应用在各种测试.控制.故障分析及信息处 ...

  7. python基础[16]——解决django连接mysql数据库报错的问题

    Models.py #创建数据表 from django.db import models from django.utils import timezone from tinymce.models ...

  8. vue项目创建步骤 和 路由router知识点

    菜单快捷导航: vue项目创建 vue路由router知识点(路径参数.查询参数.命名路由.嵌套路由.命名视图.hash/history模式) 1.创建一个vue项目步骤 (windows环境下).创 ...

  9. 原生JS数组方法实现(一)————push()、unshift()、pop()和shift()

    push 向数组末尾添加一个或多个元素,并返回数组新的长度 ```javascript function push(){ for(let i=0;i<arguments.length;i++){ ...

  10. 【题解】[HNOI2015]菜肴制作(贪心+topo序)

    [题解][HNOI2015]菜肴制作(贪心+topo序) 题意:请你构造一个排列\(p[i]\)使得对于数组\(arc[i]=p[i]\)的字典序最小,并且对于给定的有序数对\((u,v)\)保证你给 ...