关于golang:Introduction万字手撕Go-http源码servergo

41次阅读

共计 16854 个字符,预计需要花费 43 分钟才能阅读完成。

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 = &defaultServeMux

var 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; exist {return false}
    }

    n := len(path)
    if n == 0 {return false}
    for _, c := range p {
        if _, exist := mux.m; 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 main

import (
    "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 main

import (
    "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 给指定的 parttern
func (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.go
package main

import (
    "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…

正文完
 0