golang http routing

Take a look at http built-in routing functionality.

Stay a few questions before reading

  • If you want to achieve routing themselves, how to do?
  • Default route structure, What way?

example

We often see written in the following two examples, the example from 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)
}

In fact, through code can be found, http packets directly call HandleFunc using DefaultServeMux, we can see the last call ListenAndServe, a parameter is passed back nil, make server use DefaultServeMux, can know through the analysis code, DefaultServeMux and ServeMux structure, path to resolve all the same. The only difference is the following,

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

DefaultServeMux can see this is a global variable, it means that there is a security risk that any other third party packages can be added to the route. According to reports, this is the meaning of existence for a number of custom features, most of the scenes we can not use this method. We can see from here, if we are to achieve a route, have to meet the following interface. ListenAndServe then passed to a function of the second parameter on the list.

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

Structure Introduction

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

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

See the above structure, it is known to use a hash node routing storage, key routing path, value of the node, having the structure muxEntry.

Routing Registry

We look at the code directly routing registration section.

// 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}
    }
}

Can learn from the code, in fact, the routing information in a hash structure, and finally on / tree this way made some small optimization, it is redirected to / tree /.

Find route

Analysis from the request path may be known to find the source of the route, the following

// 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)
}

Re looking down, we can know the final match rule,

// 通过给定的 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
}

Review questions

  • If you want to achieve routing themselves, how to do?
    We have to realize Handler interface, signature of achieving ServeHTTP(ResponseWriter, *Request)the method.

  • Default route structure, What way?
    Default route with the ServeMuxstructure, with the hash stored in the path and handler. When looking for will traverse the hash structure, using the principle of longest match to find the handler, when there is / tree / and / tree / sub / registration at the same time when, / tree / sub / will use / tree / sub / registered handler and not / tree / registered handler. Meanwhile, when / tree / registration route no child, / tree / will match all of the handler / tree / sub / sub routing, etc. This, to some extent, to meet such a match mode *.

Reproduced in: https: //www.jianshu.com/p/5a4b54f52019

Guess you like

Origin blog.csdn.net/weixin_33912453/article/details/91241883