golang http 路由

栏目: Go · 发布时间: 5年前

内容简介:来看看 http 自带的路由功能。我们经常在示例中看到如下两种写法,示例来自于其实通过代码就可以发现,http 包直接调用 HandleFunc 使用的是DefaultServeMux,我们可以看到最后调用 ListenAndServe,后面一个参数传递的是

来看看 http 自带的路由功能。

阅读前留几个问题

  • 如果要自己实现路由,该怎么做?
  • 默认路由的结构,搜索方式是什么?

例子

我们经常在示例中看到如下两种写法,示例来自于 stackoverflow

// 使用 DefaultServeMux
func main() {
  http.HandleFunc("/page2", Page2)
  http.HandleFunc("/", Index)
  http.ListenAndServe(":3000", nil)
}

// 使用 ServeMux 路由
func main() {
  mux := http.NewServeMux()
  mux.HandleFunc("/page2", Page2)
  mux.HandleFunc("/", Index)
  http.ListenAndServe(":3000", mux)
}

其实通过代码就可以发现,http 包直接调用 HandleFunc 使用的是DefaultServeMux,我们可以看到最后调用 ListenAndServe,后面一个参数传递的是 nil ,会让 server 使用 DefaultServeMux,通过分析代码可以知道,DefaultServeMux 和 ServeMux的结构,path解析都是一样的。唯一的区别在于下面,

// DefaultServeMux is the default ServeMux used by Serve.
var DefaultServeMux = &defaultServeMux

可以看到这个 DefaultServeMux 是一个全局变量,那么意味这有安全风险,任何其他的第三方包都可以添加路由。这个存在的意义据介绍是为了一些自定义功能,我们大部分场景都可以不使用这个方法。那么从这里可以看出,如果我们要实现一个路由,得满足下面的接口。然后传递到 ListenAndServe 函数的第二个参数就行了。

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

结构介绍

type ServeMux struct {
    mu    sync.RWMutex
    m     map[string]muxEntry
    hosts bool // hostname 路由
}

type muxEntry struct {
    explicit bool  // 是否注册过
    h        Handler // 处理函数
    pattern  string // 路由 path 和 key 一致
}

看到上面的结构,可以知道路由的节点是使用 hash 存储的,key 为路由 path,value 为节点,其结构为 muxEntry

路由注册

我们直接看路由注册部分的代码。

// Handle 为给定路由 path 注册 handler。
// 如果该路由 path 的 handler 已存在会在编译时 panic。即 mux.m[pattern].explicit
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")
    }
    if mux.m[pattern].explicit {
        panic("http: multiple registrations for " + pattern)
    }

    if mux.m == nil {
        mux.m = make(map[string]muxEntry)
    }
    mux.m[pattern] = muxEntry{explicit: true, h: handler, pattern: pattern}

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

    // 如果 pattern 是 /tree/,则为 /tree 插入隐式永久重定向。
    // 这个规则可以被覆盖。
    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}
    }
}

从代码可以了解到,其实就是把路由信息放在了 hash 结构里,最后对 /tree 这种方式做了一些小优化,使它重定向到 /tree/。

路由查找

从请求分析路径可以知到路由的查找源头,如下

// ServeHTTP 调度请求给已注册模式最匹配 URL 的 handler
func (mux *ServeMux) ServeHTTP(w ResponseWriter, r *Request) {
    if r.RequestURI == "*" {
        if r.ProtoAtLeast(1, 1) {
            w.Header().Set("Connection", "close")
        }
        w.WriteHeader(StatusBadRequest)
        return
    }
    h, _ := mux.Handler(r)
    h.ServeHTTP(w, r)
}

再往下找,我们可以知道最终的匹配规则,

// 通过给定的 path 从 handler hash 结构中查找 handler
// 最长模式优先
func (mux *ServeMux) match(path string) (h Handler, pattern string) {
    var n = 0
    // 遍历
    for k, v := range mux.m {
        if !pathMatch(k, path) {
            continue
        }
        // len(k) > n 即还能找到更长的 pattern,满足最长模式优先
        if h == nil || len(k) > n {
            n = len(k)
            // 返回 handler
            h = v.h
            pattern = v.pattern
        }
    }
    return
}

// 匹配
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
    }
    // 是否匹配,mux 的 key(pattern)和给定的 path比较,按照最长原则
    return len(path) >= n && path[0:n] == pattern
}

回顾问题

  • 如果要自己实现路由,该怎么做?

    我们得实现 Handler 接口,即实现签名为 ServeHTTP(ResponseWriter, *Request) 的方法。

  • 默认路由的结构,搜索方式是什么?

    默认路由用的 ServeMux 结构,用 hash 存储了 path 和 handler。查找的时候会遍历这个 hash 结构,采用最长匹配的原则去查找 handler,当出现 /tree/ 和 /tree/sub/ 同时注册时,/tree/sub/ 会用 /tree/sub/ 注册的 handler 而不是 /tree/ 注册的 handler。同时,当 /tree/ 没有子路由注册时, /tree/ 的 handler 会匹配所有 /tree/sub/ 等这种子路由,一定程度满足了 * 这样的匹配模式。


以上所述就是小编给大家介绍的《golang http 路由》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

Bulletproof Web Design

Bulletproof Web Design

Dan Cederholm / New Riders Press / 28 July, 2005 / $39.99

No matter how visually appealing or packed with content a Web site is, it isn't succeeding if it's not reaching the widest possible audience. Designers who get this guide can be assured their Web site......一起来看看 《Bulletproof Web Design》 这本书的介绍吧!

图片转BASE64编码
图片转BASE64编码

在线图片转Base64编码工具

随机密码生成器
随机密码生成器

多种字符组合密码

HSV CMYK 转换工具
HSV CMYK 转换工具

HSV CMYK互换工具