Go标准库-ServeMux的使用与模式匹配深入探究

 更新时间:2024年01月15日 10:53:09   作者:凉凉的知识库  
这篇文章主要为大家介绍了Go标准库-ServeMux的使用与模式匹配深入探究,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪

引言

本篇为【深入理解Go标准库】系列第二篇

第一篇:http server的启动

第二篇:ServeMux的使用与模式匹配👈

根据 Golang 文档 中的介绍,ServeMux是一个 HTTP 请求多路复用器(HTTP Request multiplexer)。它按照一定规则匹配请求URL和已注册的模式,并执行其中最匹配的模式的Handler

如果你还不知道什么是Handler,强烈建议你先阅读下:第一篇:http server的启动

基本使用

http.ServeMux实现了Handler接口

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

http.ServeMux提供两个函数用于注册不同Path的处理函数

  • ServeMux.Handle 接收的是Handler接口实现

  • ServeMux.HandleFunc 接收的是匿名函数

type PathBar struct {
}

func (m PathBar) ServeHTTP(w http.ResponseWriter, r *http.Request) {
 w.Write([]byte("Receive path bar"))
 return
}

func main() {
 mx := http.NewServeMux()

 mx.Handle("/bar/", PathBar{})
 mx.HandleFunc("/foo", func(w http.ResponseWriter, r *http.Request) {
  w.Write([]byte("Receive path foo"))
 })

 http.ListenAndServe(":8009", mx)
}

🌲 通过类型转换实现接口

值得一提的是ServeMux.HandleFunc的实现,底层还是调用了ServeMux.Handle

func (mux *ServeMux) HandleFunc(pattern string, handler func(ResponseWriter, *Request)) {
 if handler == nil {
  panic("http: nil handler")
 }
 mux.Handle(pattern, HandlerFunc(handler))
}

HandlerFunc(handler)这里并不是函数调用,而是类型转换

type HandlerFunc func(ResponseWriter, *Request)

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

通过把handler func(ResponseWriter, *Request)转换成类型HandlerFunc,而类型HandlerFunc实现了Handler接口

🌲 全局默认值

当没有设置http.Server.Handler属性时,http.Server就会使用一个全局的变量DefaultServeMux *ServeMux来作为http.Server.Handler的值

下面的代码和上面的没有区别

func main() {
 http.Handle("/bar/", PathBar{})
 http.HandleFunc("/foo", func(w http.ResponseWriter, r *http.Request) {
  w.Write([]byte("Receive path foo"))
 })

 http.ListenAndServe(":8009", nil)
}

Pattern匹配

预处理

预处理的是请求的url,以方便匹配,在注册时是不会做任何处理的

  • 移除host中的端口号

  • 针对 URL 中包含..或者.的请求,ServeMux 会对其 Path 进行整理,并匹配到合适的路由模式上

  • 针对 URL 中包含重复/的请求,ServeMux 会对其进行重定向

func main() {
 mx := http.NewServeMux()

 mx.HandleFunc("/abc/def", func(writer http.ResponseWriter, request *http.Request) {
  fmt.Fprintln(writer, request.Host, request.URL.Path)
 })

 http.ListenAndServe(":8009", mx)
}

🌲 预处理的是请求的url

pattern是不会被处理的,而请求的url都是被处理成标准格式

所以如果注册如下的pattern,无论如何也是无法被命中的

func main() {
 mx := http.NewServeMux()

 mx.HandleFunc("/abc//def", func(writer http.ResponseWriter, request *http.Request) {
  fmt.Fprintln(writer, request.Host, request.URL.Path)
 })
}

无论是/abc/def还是/abc//def都无法被命中

$ curl 127.0.0.1:8009/abc/def
404 page not found
$ curl 127.0.0.1:8009/abc//def
<a href="/abc/def" rel="external nofollow"  rel="external nofollow"     >Moved Permanently</a>.

🌲 带 ..或者.请求与重复/请求的处理不同

包含..或者.整理之后匹配到合适的路由模式上,并不会重定向

$ curl  127.0.0.1:8009/ccc/../abc/./def
127.0.0.1:8009 /abc/def

含重复/,会重定向

$ curl -v  127.0.0.1:8009/abc//def
*   Trying 127.0.0.1:8009...
* Connected to 127.0.0.1 (127.0.0.1) port 8009 (#0)
> GET /abc//def HTTP/1.1
> Host: 127.0.0.1:8009
> User-Agent: curl/7.79.1
> Accept: */*
> 
* Mark bundle as not supporting multiuse
< HTTP/1.1 301 Moved Permanently
< Content-Type: text/html; charset=utf-8
< Location: /abc/def
< Date: Thu, 10 Nov 2022 16:05:13 GMT
< Content-Length: 43
< 
<a href="/abc/def" rel="external nofollow"  rel="external nofollow"     >Moved Permanently</a>.

* Connection #0 to host 127.0.0.1 left intact

路径匹配

ServeMux 注册路由模式的方式有两种,固定根路径例如"/favicon.ico",与以根路径开始的子树,例如"/images/"

🌲 固定路径(fixed, rooted paths)

固定根路径就是指定一个固定的 URL 和请求进行精确匹配

🌲 以根路径开始的子树(rooted subtrees)

以根路径开始的子树是符合最长路径匹配的原则的,例如我们注册了两个子路径,/image/gif//image/,URL 为/image/gif/的请求会优先匹配第一个路由模式,其他路径会匹配/image/

⚠️ 注意:

1、凡是/结尾的路径都被看作以根路径开始的子树,因此 / 也被看作以根路径开始的子树,它不仅匹配/,而且也会匹配所有未被其他路由模式匹配的请求。

func main() {
 mx := http.NewServeMux()

 mx.HandleFunc("/", func(writer http.ResponseWriter, request *http.Request) {
  fmt.Fprintln(writer, request.URL.EscapedPath())
 })

 http.ListenAndServe(":8009", mx)
}
$ curl 127.0.0.1:8009/abc
/abc

2、如果只注册了一个子树路径(/结尾)并且请求URL没有/结尾,ServeMux会返回重定向。如果再增加一个没有/结尾的模式的话,就会精确匹配,也就不会有这种行为了

例如我们只注册了子路径/abc/,服务器会自动将/abc请求重定向为/abc/

func main() {
 mx := http.NewServeMux()
 mx.HandleFunc("/abc/", func(writer http.ResponseWriter, request *http.Request) {
  fmt.Fprintln(writer, request.URL.EscapedPath())
 })
 http.ListenAndServe(":8009", mx)
}
$ curl -v 127.0.0.1:8009/abc
*   Trying 127.0.0.1:8009...
* Connected to 127.0.0.1 (127.0.0.1) port 8009 (#0)
> GET /abc HTTP/1.1
> Host: 127.0.0.1:8009
> User-Agent: curl/7.79.1
> Accept: */*
> 
* Mark bundle as not supporting multiuse
< HTTP/1.1 301 Moved Permanently
< Content-Type: text/html; charset=utf-8
< Location: /abc/
< Date: Thu, 10 Nov 2022 15:30:13 GMT
< Content-Length: 40
< 
<a href="/abc/" rel="external nofollow"   >Moved Permanently</a>.

* Connection #0 to host 127.0.0.1 left intact

如果我们不想让服务器自动重定向的话,只需要再添加一个/abc模式就好了

func main() {
 mx := http.NewServeMux()

 mx.HandleFunc("/abc/", func(writer http.ResponseWriter, request *http.Request) {
  fmt.Fprintln(writer, request.URL.EscapedPath())
 })

 mx.HandleFunc("/abc", func(writer http.ResponseWriter, request *http.Request) {
  fmt.Fprintln(writer, request.URL.EscapedPath())
 })

 http.ListenAndServe(":8009", mx)
}
$ curl  127.0.0.1:8009/abc 
/abc

域名匹配(Host-specific patterns)

ServeMux 还支持根据主机名精确匹配,匹配时会严格匹配host,path的匹配则还遵循上面的原则

⚠️ 注意:

有域名的优先级会更高,所以可以注册一个带域名的路径和不带域名的路径

func main() {
 mx := http.NewServeMux()

 mx.HandleFunc("example01.com/abc/",
  func(writer http.ResponseWriter, request *http.Request) {
   fmt.Fprintln(writer, request.Host, request.URL.EscapedPath())
  })

 mx.HandleFunc("/abc/", func(writer http.ResponseWriter, request *http.Request) {
  fmt.Fprintln(writer, request.Host, request.URL.EscapedPath())
 })

 http.ListenAndServe(":8009", mx)
}

example01.com会匹配第一个handler,其他域名则匹配第二个

$ curl -H 'HOST:example01.com'  127.0.0.1:8009/abc/
example01.com /abc/

$ curl -H 'HOST:example02.com'  127.0.0.1:8009/abc 
example02.com /abc

Method和路径参数匹配(method, path specificity patterns)

最新的特性还在讨论中,大致的patterns会像下面这样

https://github.com/golang/go/discussions/60227 

/item/
POST /item/{user}
/item/{user}
/item/{user}/{id}
/item/{$}
POST alt.com/item/{user}

以上就是Go标准库-ServeMux的使用与模式匹配深入探究的详细内容,更多关于Go ServeMux模式匹配的资料请关注脚本之家其它相关文章!

相关文章

  • golang类型转换之interface转字符串string简单示例

    golang类型转换之interface转字符串string简单示例

    在我们使用Golang进行开发过程中,总是绕不开对字符或字符串的处理,这篇文章主要给大家介绍了关于golang类型转换之interface转字符串string的相关资料,文中通过代码介绍的非常详细,需要的朋友可以参考下
    2024-01-01
  • Go语言学习之Switch语句的使用

    Go语言学习之Switch语句的使用

    这篇文章主要通过一些示例为大家介绍一下Go语言中Switch语句的基本语法以及使用,文中的示例代码讲解详细,感兴趣的小伙伴可以了解一下
    2022-06-06
  • Go语言操作金仓数据库的环境搭建与连接管理

    Go语言操作金仓数据库的环境搭建与连接管理

    这篇文章主要为大家详细介绍了使用Go语言开发数据采集服务时使用Gokb驱动连接金仓数据库的过程,文中的示例代码讲解详细,感兴趣的小伙伴可以了解下
    2026-05-05
  • Go语言使用字符串的几个技巧分享

    Go语言使用字符串的几个技巧分享

    这篇文章中小编将给出一些Go语言在处理字符串方面的技巧,对大家学习Go语言具有一定的参考借鉴价值,下面一起看看吧。
    2016-09-09
  • 使用go实现适配器模式

    使用go实现适配器模式

    这篇文章主要介绍了使用go实现适配器模式,这个模式就是用来做适配的,它将不兼容的接口转换为可兼容的接口,让原本由于接口不兼容而不能一起工作的类可以一起工作,需要的朋友可以参考下
    2021-11-11
  • Go外部依赖包从vendor,$GOPATH和$GOPATH/pkg/mod查找顺序

    Go外部依赖包从vendor,$GOPATH和$GOPATH/pkg/mod查找顺序

    这篇文章主要介绍了Go外部依赖包vendor,$GOPATH和$GOPATH/pkg/mod下查找顺序,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧
    2020-12-12
  • go panic时如何让函数返回数据?

    go panic时如何让函数返回数据?

    今天小编就为大家分享一篇关于go panic时如何让函数返回数据?,小编觉得内容挺不错的,现在分享给大家,具有很好的参考价值,需要的朋友一起跟随小编来看看吧
    2019-04-04
  • 浅谈Golang中创建一个简单的服务器的方法

    浅谈Golang中创建一个简单的服务器的方法

    这篇文章主要介绍了浅谈Golang中创建一个简单的服务器的方法,golang中的net/http包对网络的支持非常好,这样会让我们比较容易的建立起一个相对简单的服务器,有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2018-06-06
  • go mod 依赖管理的具体使用

    go mod 依赖管理的具体使用

    在Go语言开发中,依赖管理是一项非常重要的工作,Go mod作为官方的包管理工具已经成为了Go语言依赖管理的首选方式,本文就来介绍一下go mod 依赖管理的具体使用,具有一定的参考价值,感兴趣的可以了解一下
    2024-01-01
  • Golang解析JSON遇到的坑及解决方法

    Golang解析JSON遇到的坑及解决方法

    这篇文章主要为大家介绍了Golang解析JSON时会遇到的一些坑及解决方法,文中的示例代码讲解详细,对我们学习Go语言有一点的帮助,需要的可以参考一下
    2023-02-02

最新评论