首页
学习
活动
专区
圈层
工具
发布
16 篇文章
1
Go 语言 Web 编程系列(一)—— 快速入门:创建第一个 Web 应用
2
Go 语言 Web 编程系列(二)—— HTTP 请求处理的底层运行机制
3
Go 语言 Web 编程系列(三)—— 基于官方标准库自定义路由处理器
4
Go 语言 Web 编程系列(四)—— 基于 gorilla/mux 包实现路由定义:基本使用篇
5
Go 语言 Web 编程系列(五)—— 基于 gorilla/mux 包实现路由匹配:进阶使用篇
6
Go 语言 Web 编程系列(六)—— 基于 gorilla/mux 包实现路由匹配:路由中间件
7
Go 语言 Web 编程系列(七)—— 基于 gorilla/mux 包实现路由匹配:处理静态资源响应
8
Go 语言 Web 编程系列(八)—— 基于 gorilla/mux 包实现路由匹配:服务 SPA 应用
9
Go 语言 Web 编程系列(九)—— 基于 gorilla/mux 包实现路由匹配:通过 CORS 处理跨域请求
10
Go 语言 Web 编程系列(十)—— 基于 gorilla/mux 包实现路由匹配:健康检查与接口测试
11
Go 语言 Web 编程系列(十一)—— 仿照 Laravel 框架对 Go 路由代码进行拆分
12
Go 语言 Web 编程系列(十二)—— 通过 Request 读取 HTTP 请求报文
13
Go 语言 Web 编程系列(十三)—— 获取用户请求数据(上)
14
Go 语言 Web 编程系列(十四)—— 获取用户请求数据(下)
15
Go 语言 Web 编程系列(十五)—— 通过 ResponseWriter 接口创建 HTTP 响应
16
Go 语言 Web 编程系列(十六)—— 设置、读取和删除 Cookie

Go 语言 Web 编程系列(三)—— 基于官方标准库自定义路由处理器

1、引子

从这一篇教程起,我们将从自定义路由器开始探索 Go Web 编程之旅。

开始之前,我们还是回顾下创建第一个 Web 应用中的示例代码:

代码语言:javascript
复制
代码语言:javascript
复制
http.HandleFunc("/", sayHelloWorld)
err := http.ListenAndServe(":9091", nil)

我们在上篇教程介绍过这段代码的底层实现,这里 http.ListenAndServe 方法第二个参数传入的是 nil,表示底层会使用默认的 DefaultServeMux 实现将上述 HandleFunc 方法传入的处理函数转化为基于闭包方式定义的路由:

如上篇教程所言,如果我们想要实现自定义的路由处理器,则需要构建一个自定义的、实现了 Handler 接口的类实例作为 http.ListenAndServe 的第二个参数传入。

在开始介绍自定义路由处理器实现之前,我们先来看看 DefaultServeMux 是如何保存路由映射规则以及分发请求做路由匹配的。

2、路由映射规则

DefaultServeMux 所属的类型是 ServeMux

代码语言:javascript
复制
代码语言:javascript
复制
var DefaultServeMux = &defaultServeMux
var defaultServeMux ServeMux

首先我们来看一下 ServeMux 的数据结构:

代码语言:javascript
复制
type ServeMux struct {
    mu    sync.RWMutex. // 由于请求涉及到并发处理,因此这里需要一个锁机制
    m     map[string]muxEntry // 路由规则字典,存放URL路径与处理器的映射关系
    es    []muxEntry // MuxEntry 切片(按照最长到最短排序)
    hosts bool       // 路由规则中是否包含 host 信息
}

这里,我们需要重点关注的是 muxEntry 结构:

代码语言:javascript
复制
type muxEntry struct {
    h   Handler       // 处理器具体实现
    pattern string    // 模式匹配字符串
}

最后我们来看一下 Handler 的定义,这是一个接口:

代码语言:javascript
复制
代码语言:javascript
复制
type Handler interface {
    ServeHTTP(ResponseWriter, *Request) // 路由处理实现方法
}

我们之前定义的 sayHelloWorld 方法并没有实现 Handler 接口,之所以可以成功添加到路由映射规则中是因为在底层通过 HandlerFunc() 函数将其强制转化为了 HandlerFunc 类型,而 HandlerFunc 类型实现了 ServeHTTP 方法,这样,sayHelloWorld 方法也就实现了 Handler 接口:

代码语言:javascript
复制
代码语言:javascript
复制
func (mux *ServeMux) HandleFunc(pattern string, handler func(ResponseWriter, *Request)) {
    if handler == nil {
      panic("http: nil handler")
    }
    mux.Handle(pattern, HandlerFunc(handler))
}

...

type HandlerFunc func(ResponseWriter, *Request)

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

对于 sayHelloWorld 方法来说,它已然变成了 HandlerFunc 类型的函数类型,当我们在其实例上调用 ServeHTTP 方法时,调用的是 sayHelloWorld 方法本身。

当我们在 DefaultServeMux.HandleFunc 中调用 mux.Handle 方法时,实际上是将其路由映射规则保存到 DefaultServeMux 路由处理器的数据结构中:

代码语言:javascript
复制
func (mux *ServeMux) Handle(pattern string, handler Handler) {
  mux.mu.Lock()
  defer mux.mu.Unlock()

  if pattern == "" {
    panic("http: invalid pattern")
  }
  if handler == nil {
    panic("http: nil handler")
  }
  if _, exist := mux.m[pattern]; exist {
    panic("http: multiple registrations for " + pattern)
  }

  if mux.m == nil {
    mux.m = make(map[string]muxEntry)
  }
  e := muxEntry{h: handler, pattern: pattern}
  mux.m[pattern] = e
  if pattern[len(pattern)-1] == '/' {
    mux.es = appendSorted(mux.es, e)
  }

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

还是以 sayHelloWorld 为例,这里的 pattern 字符串对应的是请求路径 /handler 对应的是 sayHelloWorld 函数。

3、请求分发与路由匹配

保存好路由映射规则之后,客户端请求又是怎么分发的呢?或者说请求 URL 与 DefaultServeMux 中保存的路由映射规则是如何匹配的呢?

我们在上篇教程介绍过,处理客户端请求时,会调用默认 ServeMux 实现的 ServeHTTP 方法:

代码语言:javascript
复制
func (mux *ServeMux) ServeHTTP(w ResponseWriter, r *Request) {
    if r.RequestURI == "*" {
        w.Header().Set("Connection", "close")
        w.WriteHeader(StatusBadRequest)
        return
    }
    
    h, _ := mux.Handler(r)
    h.ServeHTTP(w, r)
}
代码语言:javascript
复制

如上所示,路由处理器接收到请求之后,如果 URL 路径是 *,则关闭连接,否则调用 mux.Handler(r) 返回对应请求路径匹配的处理器,然后执行 h.ServeHTTP(w, r),也就是调用对应路由 handlerServerHTTP 方法,以 / 路由为例,调用的就是 sayHelloWorld 函数本身。

至于 mux.Handler(r) 的底层匹配实现,感兴趣的同学可以去 net/http 包中查看对应的底层源码,这里就不详细展开了。

通过上面的介绍,我们了解了基于 DefaultServeMux 实现的整个路由规则存储(Web 应用启动期间进行)和请求匹配过程(客户端发起请求时进行),下面我们来看一下如何实现自定义的 路由处理器。

4、自定义路由处理器

如果你搞清楚了上面的默认实现,编写自定义的路由处理器就会非常简单,我们只需要定义一个实现了 Handler 接口的类,然后将其实例传递给 http.ListenAndServe 方法即可:

代码语言:javascript
复制
代码语言:javascript
复制
package main

import (
    "fmt"
    "net/http"
)

type MyHander struct {

}

func (handler *MyHander) ServeHTTP(w http.ResponseWriter, r *http.Request)  {
    if r.URL.Path == "/" {
        sayHelloGolang(w, r)
        return
    }
    http.NotFound(w, r)
    return
}

func sayHelloGolang(w http.ResponseWriter, r *http.Request) {
    fmt.Fprintf(w, "Hello Golang!")
}

func main()  {
    handler := MyHander{}
    http.ListenAndServe(":9091", &handler)
}

我们运行 go run router.go 来启动这个应用,然后在浏览器中就可以访问 / 路由了:

这个实现很简单,而且我们并没有在应用启动期间初始化路由映射规则,而是在应用启动之后根据请求参数动态判断来做分发的,这样做会影响性能,而且非常不灵活,我们当然可以仿照 DefaultServeMux 来实现自定义的 ServeMux,不过已经有非常好的第三方轮子可以直接拿来用了,比如 gorilla/mux,后续教程我们都将使用它作为路由器,下篇教程我们将简单介绍它的基本使用。

下一篇
举报
领券