Author:Wzy_CC

全文一共10000字

浏览工夫10~15分钟

前言

本文指标:

从路由注册到监听本地端口后申请路由的一系列动作的剖析,基本上仅限于net/http server.go这个包文件的路由相干局部解读

写作目标:

在应用原生库进行web开发的时候,很多初学者很容易被mux.Handle()/mux.HandlerFunc()/mux.Handler()/Handlerfunc/Handler/Handle()/Handlefunc()/handler给唬住,自身几个名称就相近,首字母有时候大写有时候小写,有时候是handle,有时候是handler,看起来类似然而类型和作用却齐全不同。因为命名类似容易混同,因而其实在含意也不容易搞清楚,对于开发者来说也不容易记忆 。有些命名甚至看不出来这个函数到底是干什么用的,有些属于设计库的时候的历史遗留问题,使得了解http库变得更加艰难。

很多网上的教程只是讲了某些货色是什么,用来干什么的,而没有讲为什么是这样的,为什么要这样设计,这样设计有什么益处。更重要的是有些教程曾经老了,2018年到当初曾经两年了,很多函数都通过优化重写了,至多server.go中的很多函数变动都很大,2018年的教程很多曾经过期了,可能2022年又须要从新写一篇http库的解读。不过有些货色是不变的,很多设计思维都是共通的,而这些思维才是初学者应该把握的。事实上死记硬背把握handle的四种写法对开发没有任何帮忙,如果不深刻了解当前还会经常性的把文档翻来翻去而一头雾水。

Go的有些设计哲学很乏味,不是简简单单几万字的篇幅就能够讲明确的。

浏览程序:

本文依照程序浏览会比拟有帮忙

目录

[TOC]

概述

官网示例

在官网示例中,应用go搭建一个稳固的高并发web服务器仅须要短短几行:

http.Handle("/foo", fooHandler)http.HandleFunc("/bar", func(w http.ResponseWriter, r *http.Request) {    fmt.Fprintf(w, "Hello, %q", html.EscapeString(r.URL.Path))})log.Fatal(http.ListenAndServe(":8080", nil))

当然对于大部分开发来说,这几行代码曾经足够在生产环境中应用了,然而如果对比拟底层的一些原理不解,那么还须要持续深究。

一个go服务器失常运行起来的步骤大抵有:注册函数、监听端口,承受申请,解决申请,提供服务,敞开链接

0.注册函数:首先往路由表中注册对应的路由规定

1.监听端口:创立listen socket,循环监听

2.承受申请:承受申请,创立网络链接conn对象,开启一个协程解决该链接(预计多路复用复用在这里了)每服务一个新的链接,在conn.connect()中就会调用serveHTTP来解决申请

3.解决申请:读取申请参数结构Request对象,依据申请门路在map路由表中查找对应的Handler。而后把申请调配给处理函数

4.提供服务:处理函数依据申请的参数等信息做解决,返回不同的信息

5.敞开链接:应用层解决完申请后敞开链接

前置常识

Go根底语法、web根底、*压缩字典树

源码剖析范畴/纲要

次要剖析net/http库中的server.go文件,然而篇幅无限重点剖析(应用mux.XX()简化代替ServeMux.XX()):

1.ServeMux构造体及其办法:mux.NewServeMux()mux.Handle()mux.HandleFunc()mux.Handler()/mux.handler()mux.ServeHTTP()

2.HandlerFunc构造体及其实现办法:HandlerFunc.ServeHTTP()

3.Handler接口类型

4.函数Handle()和函数HandleFunc()

路由局部就这么点货色

ServeMux

ServeMux是一个构造体

ServeMux定义

ServeMux是一个HTTP申请多路复用器。它依据注册模式列表(路由表)将每个传入申请的URL匹配,并为与URL最匹配的模式调用处理程序(handler)。

type ServeMux struct {    // contains filtered or unexported fields}

构造体内黑盒,蕴含已过滤和未导出的字段,其实就是不想让你晓得外面的结构,事实上的结构如下:

type ServeMux struct {    mu    sync.RWMutex          // 读写互斥锁    m     map[string]muxEntry   // 路由表    es    []muxEntry            // 有序数组,从最长到最短排序    hosts bool                  // whether any patterns contain hostnames}

ServeMux构造体实质上是由mu读写互斥锁、m路由表、es数组(很多老教程都没有这个更新字段)和hosts布尔值组成

其中:

1.mu是读写互斥锁,详情见设计思维

2.m是路由表,路由表实质上就是一个map[string]muxEntry变量,键是门路字符串(由method和传入参数拼接字符串组成),值是对应的解决构造体muxEntry

3.es是一个有序数组,由长到短保护所有后缀为/的路由地址,至于为什么要这样设计,见设计思维

4.布尔类型的hosts属性标记路由中是否带有主机名,若hosts值为true,则路由的起始不能为/

m路由表中的muxEntry的构造体如下:

type muxEntry struct {    h        Handler // 处理程序    pattern  string  // 路由门路}

muxEntry实质上是由Handler类和路由门路字符串组成,其中:

1.h是Handler类型,Handler类型要求实现接口中的ServeHTTP办法

2.pattern实际上和路由表中的key雷同

默认多路复用器

net/http包中规定了默认的多路复用器,如果不本人手写指定则应用默认mux:

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

这里为什么能够在申明前应用变量?Dave Cheney通知我包级别的变量与申明程序无关,还通知我这种问题当前去slack上本人问,编译器做初始化工作的时候会首先初始化包级别的变量,因而无论申明在哪里都能够应用。

ServeMux办法

私有办法

mux.NewServeMux()

// NewServeMux allocates and returns a new ServeMux.func NewServeMux() *ServeMux { return new(ServeMux) }

新建并返回一个ServeMux构造体,为构造体内字段调配空间。值得注意的是,初始化并返回的构造体字段hosts默认值为false

mux.Handler()

对于给定的申请,mux.Handler()总是返回非空Handler来应用。如果办法是CONNECT则见公有办法mux.redirectToPathSlash()

mux.Handler()调用公有办法mux.handler(),在mux.handler()外部调用了mux.match()办法来返回匹配pattern的handler

func (mux *ServeMux) Handler(r *Request) (h Handler, pattern string) {    // CONNECT requests are not canonicalized.    if r.Method == "CONNECT" {        // If r.URL.Path is /tree and its handler is not registered,        // the /tree -> /tree/ redirect applies to CONNECT requests        // but the path canonicalization does not.        if u, ok := mux.redirectToPathSlash(r.URL.Host, r.URL.Path, r.URL); ok {            return RedirectHandler(u.String(), StatusMovedPermanently), u.Path        }        return mux.handler(r.Host, r.URL.Path)    }    // All other requests have any port stripped and path cleaned    // before passing to mux.handler.    host := stripHostPort(r.Host)    path := cleanPath(r.URL.Path)    // If the given path is /tree and its handler is not registered,    // redirect for /tree/.    if u, ok := mux.redirectToPathSlash(host, path, r.URL); ok {        return RedirectHandler(u.String(), StatusMovedPermanently), u.Path    }    if path != r.URL.Path {        _, pattern = mux.handler(host, path)        url := *r.URL        url.Path = path        return RedirectHandler(url.String(), StatusMovedPermanently), pattern    }    return mux.handler(host, r.URL.Path)}

对于r.hostr.URL.Path进行了简略解决,简要阐明一下两个函数cleanPath()stripHostPort()别离做了什么工作:

cleanPath()

1.解决有效路由

2.对于斜杠的解决,代替有效的多个斜杠

3.移除所有的.替换为等效path

简略来说就是对门路进行解决为等效最短门路,使之能够在后续查找路由表的过程中能够查找到相应键值对。

// cleanPath returns the canonical path for p, eliminating . and .. elements.func cleanPath(p string) string {    if p == "" {        return "/"    }    if p[0] != '/' {        p = "/" + p    }    np := path.Clean(p)    // path.Clean removes trailing slash except for root;    // put the trailing slash back if necessary.    if p[len(p)-1] == '/' && np != "/" {        // Fast path for common case of p being the string we want:        if len(p) == len(np)+1 && strings.HasPrefix(p, np) {            np = p        } else {            np += "/"        }    }    return np}

stripHostPort()

就是对host格局的标准

// stripHostPort returns h without any trailing ":<port>".func stripHostPort(h string) string {    // If no port on host, return unchanged    if strings.IndexByte(h, ':') == -1 {        return h    }    host, _, err := net.SplitHostPort(h)    if err != nil {        return h // on error, return unchanged    }    return host}

mux.ServeHTTP()

mux.ServeHTTP()给最能匹配申请URL的handler发出请求,最初调用实现ServeHTTP()办法的Handler类型的Handler.ServeHTTP()来解决申请

有点绕,总之HTTP的申请首先由mux.ServeHTTP()进行解决,在该函数外部调用了mux.Handler()(见公有办法mux.handler())来抉择处理程序handler(在这个过程中调用了mux.handler()/mux.RedirectHandler()来查找路由表,最初在mux.handler()的外部调用了mux.match()来最初对路由进行匹配查找返回Handler类型的h)

// ServeHTTP dispatches the request to the handler whose// pattern most closely matches the request URL.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)}

mux.Handle()

在注册路由/增加路由阶段,注册函数mux.Handle()负责将处理程序和门路注册到路由表中,实质上是一个写表的过程

// Handle registers the handler for the given pattern.// If a handler already exists for pattern, Handle panics.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    }}

对于路由在表中反复会引发panic,对于后缀为slash/的门路,依照长度大小写入mux.es中,之前剖析构造体mux时也提到过这一点。

简略看一下其实现的排序函数:

func appendSorted(es []muxEntry, e muxEntry) []muxEntry {    n := len(es)    i := sort.Search(n, func(i int) bool {        return len(es[i].pattern) < len(e.pattern)    })    if i == n {        return append(es, e)    }    // we now know that i points at where we want to insert    es = append(es, muxEntry{}) // try to grow the slice in place, any entry works.    copy(es[i+1:], es[i:])      // Move shorter entries down    es[i] = e    return es}

mux.HandleFunc()

mux.HandleFunc()是我认为最重要的一个办法,同样是将handler注册到路由表中,咱们应该比照mux.HandleFunc()mux.Handle()的区别,其实从函数体来看mux.HandleFunc()算是对mux.Handle()函数的一个再封装,调用了HandlerFunc())这个适配器函数,实质上是将一个一般函数作为HTTP申请handler的语法糖,咱们不再须要实现ServeHTTP()办法,取而代之的是传入的一般函数只有为func(ResponseWriter, *Request)类型的,就能够进行函数的路由,基本上一行代码就能够搞定,这也是为什么在官网示例中咱们能够轻而易举的构建简略的web程序的起因。在官网示例的HandleFunc()函数中调用了默认复用器的DefaultServeMux.HandleFunc()办法,开发者只须要本人定义一般函数即可:

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

公有办法

mux.match()

当调用mux.Handler()返回Handler类时在mux.handler()外部会调用mux.match()函数,实质上能够看作是路由查找的过程(Handle()是路由注册的过程)

// Find a handler on a handler map given a path string.// Most-specific (longest) pattern wins.func (mux *ServeMux) match(path string) (h Handler, pattern string) {    // Check for exact match first.    v, ok := mux.m[path]    if ok {        return v.h, v.pattern    }    // Check for longest valid match.  mux.es contains all patterns    // that end in / sorted from longest to shortest.    for _, e := range mux.es {        if strings.HasPrefix(path, e.pattern) {            return e.h, e.pattern        }    }    return nil, ""}

在进行匹配的过程中:

1.首先在路由表中进行准确匹配,匹配到muxEntry后返回

2.如果在路由表中没有查问到,则在有序数组es中进行匹配,从strings.HasPrefix()能够看出,实质上这是一种含糊匹配,只匹配了相应的前缀,就认定匹配胜利

3.如果相应前缀无奈查问,则认为匹配失败,返回nil handler

总结匹配规定一句话形容是: Longer patterns take precedence over shorter ones,长字符串模式优先级大于短字符串模式,优先匹配长字符串

mux.shouldRedirectRLocked()

mux.shouldRedirectRLocked()办法的作用较为简单,判断是否须要对像"/tree/"这种路由的重定向(在ServeMux中对于"/tree"会主动重定向到"/tree/",除非路由表中已有"/tree",此过程在mux.Handler()中调用mux.redirectToPathSlash()实现)

1.判断路由表中是否存在host+path或者path的组合,如果存在则不须要重定向

2.如果path为空字符串,则不须要重定向

3.如果以后路由表中存在path+“/”,则须要重定向(例如在注册时将"/tree/"注册到表中,则对于"/tree"的路由重定向到了"/tree/",)

func (mux *ServeMux) shouldRedirectRLocked(host, path string) bool {    p := []string{path, host + path}    for _, c := range p {        if _, exist := mux.m[c]; exist {            return false        }    }    n := len(path)    if n == 0 {        return false    }    for _, c := range p {        if _, exist := mux.m[c+"/"]; exist {            return path[n-1] != '/'        }    }    return false}

mux.redirectToPathSlash()

mux.redirectToPathSlash()函数确定是否须要在其门路后附加"/",一旦判断须要增加"/"则返回新的url和被重定向后的handler:

func (mux *ServeMux) redirectToPathSlash(host, path string, u *url.URL) (*url.URL, bool) {    mux.mu.RLock()    shouldRedirect := mux.shouldRedirectRLocked(host, path)    mux.mu.RUnlock()    if !shouldRedirect {        return u, false    }    path = path + "/"    u = &url.URL{Path: path, RawQuery: u.RawQuery}    return u, true}

判断应该重定向后,返回结尾带有/的门路

mux.Handler()中,如果Http.Method为CONNECT,则会返回RedirectHandler(也是Handler类型的一种)写入StatusMovedPermanently(见status.go中的定义),调用RedirectHandler.ServeHTTP()来对HTTP申请进行解决

    if u, ok := mux.redirectToPathSlash(r.URL.Host, r.URL.Path, r.URL); ok {            return RedirectHandler(u.String(), StatusMovedPermanently), u.Path        }        return mux.handler(r.Host, r.URL.Path)

mux.handler()

mux.handler()函数是mux.Handler()的一种实现

// handler is the main implementation of Handler.// The path is known to be in canonical form, except for CONNECT methods.func (mux *ServeMux) handler(host, path string) (h Handler, pattern string) {    mux.mu.RLock()    defer mux.mu.RUnlock()    // Host-specific pattern takes precedence over generic ones    if mux.hosts {        h, pattern = mux.match(host + path)    }    if h == nil {        h, pattern = mux.match(path)    }    if h == nil {        h, pattern = NotFoundHandler(), ""    }    return}

进行两种匹配后都没有找到相应的handler后,返回NotFoundHandler()

总结

Go 其实反对内部实现的路由器 ListenAndServe 的第二个参数就是用以配置内部路由器的,它是一个 Handler 接口,即内部路由器只有实现了 Handler 接口就能够,咱们能够在本人实现的路由器的 ServeHTTP 外面实现自定义路由性能

HandleFunc()

HandleFunc()是函数

HandleFunc定义

对于给定的模式字符串,HandleFunc将handler函数注册到相应的路由上。换句话说,当对不同的url门路申请时,给出不同的解决逻辑,而HandleFunc能够实现这种将解决逻辑和url绑定的关系。

函数定义:

func HandleFunc(pattern string, handler func(ResponseWriter, *Request)) {    DefaultServeMux.HandleFunc(pattern, handler)}

第一个参数是字符串,第二个参数是handler,HandleFunc解决匹配到的url门路申请。

HandleFunc()实质上调用了默认复用器的mux.HandleFunc()

例子:

package mainimport (    "io"    "log"    "net/http")func main() {    h1 := func(w http.ResponseWriter, _ *http.Request) {        io.WriteString(w, "Hello from a HandleFunc #1!\n")    }    h2 := func(w http.ResponseWriter, _ *http.Request) {        io.WriteString(w, "Hello from a HandleFunc #2!\n")    }    http.HandleFunc("/", h1)    http.HandleFunc("/endpoint", h2)    log.Fatal(http.ListenAndServe(":8080", nil))}

HandleFunc劣势

HandleFunc函数的存在使得咱们能够间接将一个func(ResponseWriter, *Request)类型的函数作为handler,而不再须要实现Handler这个接口和自定义一个实现ServeHTTP函数的类型了,HandleFunc能够十分简便的为url注册门路。

Handle()

Handle()是函数

Handle()定义

和HandleFunc相似,实质上也是调用了默认mux的mux.Handle()办法

函数定义:

// Handle registers the handler for the given pattern// in the DefaultServeMux.// The documentation for ServeMux explains how patterns are matched.func Handle(pattern string, handler Handler) { DefaultServeMux.Handle(pattern, handler) }

第一个参数是待匹配的字符串,第二个参数是Handler类型的handler,和下面例子的handler不同,须要本人实现一个新的类,并且实现类中的办法ServeHTTP

例子:

package mainimport (    "fmt"    "log"    "net/http"    "sync")type countHandler struct {    mu sync.Mutex // guards n    n  int}func (h *countHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {    h.mu.Lock()    defer h.mu.Unlock()    h.n++    fmt.Fprintf(w, "count is %d\n", h.n)}func main() {    http.Handle("/count", new(countHandler))    log.Fatal(http.ListenAndServe(":8080", nil))}

Handler

Handler是接口

Handler定义

在go package的官网文档中是这样定义Handler的“Handler响应HTTP申请”,了解成中文就是“处理程序”。

Handler是一个接口,定义在net/http原生包中:

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

对于任何实现了ServeHTTP办法的都属于Handler。

Handler性质

1.Handler只能用来读取request的body,而不能批改申请

2.先读取body,而后再写入resp

3.事实上在真正的开发过程中,咱们不太会常常应用Handler,因为net/http给咱们提供了更不便的HandleFunc()函数,而这个函数能够让咱们间接将一个函数作为handler,在这里handler是函数类型而非此Handle接口,这种实现较实现ServeHTTP()来说更加不便。

Handler用于解决申请并给予响应,更严格地说,用来读取申请体、并将申请对应的响应字段(respones header)写入ResponseWriter中,而后返回

HandlerFunc

http还提供了HandlerFunc类,和HandleFunc()函数仅有一字之差

HandlerFunc实质是一个适配器函数,这个类在外部实现了ServeHTTP()函数,因而这个类实质上是一个Handler类型

HandlerFunc()定义

HandlerFunc(f) 是调用f函数的处理程序

函数原型:

type HandlerFunc func(ResponseWriter, *Request)

源码

// 调用默认ServerMux的HandleFunc办法func HandleFunc(pattern string, handler func(ResponseWriter, *Request)) {                                                  DefaultServeMux.HandleFunc(pattern, handler)}// 把办法handler转换成HandlerFunc类型,即实现了Handler接口;再执行Handle办法func (mux *ServeMux) HandleFunc(pattern string, handler func(ResponseWriter, *Request)) {        mux.Handle(pattern, HandlerFunc(handler))}// 路由器注册一个handler给指定的partternfunc (mux *ServeMux) Handle(pattern string, handler Handler) {        ....}

执行HandleFunc()其实就是为某一规定的申请注册处理器。

Handler/HandlerFunc区别

Handler是一个接口

HandlerFunc是Handler类型,是一个适配器函数

Handle()/HandleFunc()区别

HandleFunc()Handle()都是调用DefaultServeMux对应的办法,而DefaultServeMux是ServeMux的实例,ServeMux实现了Handler接口。事实上,HandleFunc()最终还是会调用Handle办法,之前也提到过了:

func (mux *ServeMux) HandleFunc(pattern string, handler func(ResponseWriter, *Request)) {    ...    mux.Handle(pattern, HandlerFunc(handler))}

路由注册过程

路由注册过程是由mux构造体内的两个办法mux.Handle()mux.HandleFunc()实现的

mux.Handle()

之前大抵理解了函数体,路由注册过程次要由ServeMux 构造体内的Handle()办法实现的,如果该路由曾经存在在路由表内,则会引发Panic

ServeMux构造体内的Handle() 办法大抵上干了这样几件事:

1.查看路由合法性,若不非法则给出报错信息,报错信息multiple registrations for /xxx 就属于路由反复注册而引发的问题

2.若多路复用器中没有路由表,则创立路由表

3.若路由非法则创立路由条目muxEntry增加至路由表中

4.若路由最初一个字符带有"/",则依照自定义排序函数appendSorted()的规定定位索引、插入并排序(只管我对此函数的执行效率充斥纳闷),返回排序后的数组。

5.若路由的首字母不为"/",则蕴含主机名

mux.HandleFunc()

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

Helpful behavior

在之前版本的server.go中,注册函数mux.Handle是存在一些辅助行为的,当你将路由门路设置为/tree/时,Helpful behavior会隐式永恒的帮你将/tree注册到注册表中,当然也能够显式指定路由进行笼罩,在对/tree/进行拜访时,/tree的handler会主动将申请重定向到/tree/:

 状态代码: 301 / Moved Permanently

在当初server.go中,ServeMux构造体内保护了一个es类型的数组,就是从长到短记录最初一个字母是'/'路由字符串的

在应用mux.match()对路由path进行匹配的时候(详情见“路由查找过程”),首先查找路由表,当路由表中不存在该路由时,遍历es数组,匹配其最大长度:

// Find a handler on a handler map given a path string.// Most-specific (longest) pattern wins.func (mux *ServeMux) match(path string) (h Handler, pattern string) {    // Check for exact match first.    v, ok := mux.m[path]    if ok {        return v.h, v.pattern    }    // Check for longest valid match.  mux.es contains all patterns    // that end in / sorted from longest to shortest.    for _, e := range mux.es {        if strings.HasPrefix(path, e.pattern) {            return e.h, e.pattern        }    }    return nil, ""}

服务申请过程

参考官网的文档,咱们能够应用golang原生的库net/http来实现一个简略的web路由示例:

// serve.gopackage mainimport (    "fmt"    "net/http")func main() {    http.HandleFunc("/", HelloWorld)    http.ListenAndServe(":8080", nil)}func HelloWorld(w http.ResponseWriter, r *http.Request) {    fmt.Fprint(w, "Hello World")}

其实主函数中只有两个办法,HandleFunc()ListenAndServe(),整个申请响应的执行流程如下:

  1. 注册路由过程:首先调用HandleFunc():调用了默认复用器DefaultServeMuxHandleFunc(),调用了DefaultServeMuxHandle,往DefaultServeMux的map[string]muxEntry中减少对应的handler和路由规定。
  2. 实例化server并调用server.ListenAndServe(),调用net.Listen("tcp", addr)监听端口。启动一个for循环,在循环体中Accept申请
  3. 对每个申请实例化一个Conn,并且开启一个goroutine为这个申请进行服务go c.serve()
  4. 读取每个申请的内容w, err := c.readRequest()
  5. 进入serveHandler.ServeHTTP若有本人实现的mux则应用本人的mux。判断handler是否为空,如果没有设置handler(此例子中为nil handler),handler就设置为DefaultServeMux
  6. 调用handler的ServeHttp,进入到DefaultServeMux.ServeHttp
  7. 依据request抉择匹配handler,并且进入到这个handler的ServeHTTP

对于每一个HTTP申请,服务端都会起一个协程来进行服务,这部分不在本文探讨范畴。对于官网示例:

http.ListenAndServe(":8080", nil)

传入handler是nil,则应用默认复用器DefaultServeMux,调用HandleFunc时,就是向默认复用器注册了handler

// ServeHTTP dispatches the request to the handler whose// pattern most closely matches the request URL.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)}

鉴于本文篇幅太小,有机会再剖析一下server.go中server.Serve()函数

路由查找过程

如何保障确保拜访''/path/subpath'的时候是先匹配'/path/subpath'而不是匹配'/path/'',是因为在路由查找过程中的查找规定(之前同样提到过):

mux.ServerHTTP() -> mux.Handler() -> mux.handler() -> mux.match()

失去了解决申请的handler,再调用h.ServeHTTP(w, r),去执行相应的handler办法:

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的执行

设计思维

mu读写锁:申请设计并发解决,到底哪些地方用到了并发?

没工夫写了

hasPrefix() 能够做到精准匹配么?

能够,但没必要。因为须要对于含糊路由进行匹配,如果实现上只有路由表m,则对于拜访URL的人来说极不敌对,无奈做到无效的辅助提醒

为什么须要独自保护一个后缀为/的数组es?

1.同上,为了含糊匹配

2.最重要的是,不便插入排序,只管工夫复杂度没有那么乐观

总结

本文仅仅只是对http原生库的一小部分进行了解读,对于生产来说其实并没有特地大的帮忙,然而把握原生库的一些设计思维和设计模式对于了解其余框架是肯定有很大帮忙的,毕竟原生库的作者都是真正的大神。

很多本末倒置的程序员只重视框架的学习而不重视这种根底,那和培训班进去的又有什么区别呢?还是应该真正了解一下原生库,毕竟前人开发的第三方还是借鉴了这些设计哲学的。

将来瞻望

因为原生库自带的默认多路申请路由器性能无限,而催生了很多路由框架,例如比拟有名的框架httprouter,这篇文章比拟具体的解说了httprouter框架,感兴趣的能够先看看和原生库有什么不同,将来如果有工夫,也会更新对这些框架的学习。

对于目前曾经存在的web路由框架,曾经有人比拟过这些框架的优劣了,看这篇文章能够提前预习一下:

超全的Go Http路由框架性能比拟

很早就想零碎的解说一下智能爬虫和爬虫框架了,因为爬虫和http这个库关系十分大。基本上star较多的框架我也都多多少少应用过,局部源码也浏览过,当然因为框架自身的复杂性,手撕源码会更加艰难和消耗工夫。之前也陆陆续续的简略介绍过colly等罕用爬虫框架,将来会更新更多的对于爬虫框架的源码解读。爬虫自身的技术含量并不高,然而了解这个框架为什么好、哪里好,这个技术含量就高很多了,毕竟还是要和其余爬虫框架比照一下才晓得各自的优劣。

然而受限于每个开发者本身的程度,这些框架或多或少都有这那的问题,它们并没有github主页上宣传的那么“万能”,所以在这种比拟底层的中央,把握和应用的区别就很大了,大多数人只会应用而没有把握。

当然如果相熟http库你就会发现,应用原生函数来写爬虫一样十分晦涩。

我保持认为:爬虫应用框架是最初的斗争。

Go的有些设计哲学很乏味,不是简简单单几万字的篇幅就能够讲明确的

我不想把本人标榜成Go布道者或者卷入语言圣战,Gopher该当是乏味的地鼠:

Gopher is moe, but confusing

参考链接

Go Web:Handler

Package http

golang中ServeMux解析

http.ServeMux解析

Golang web路由实现形式整顿总结

GOLANG 中HTTP包默认路由匹配规定浏览笔记

*

handle、handlefunc和handler、handlerfunc的关系

Golang ServeMux 是如何实现多路解决的

[Golang 路由匹配浅析[1]](https://segmentfault.com/a/11...