haiyux's blog

路漫漫其修远兮,吾将上下而求索


  • 首页

  • 归档

  • 关于我

  • 公益404

  • 搜索

Go web源码解析

时间: 2021-03-13 分类: go   network   字数: 3713 字 阅读: 8分钟 阅读次数:

Go的web工作原理

在Go中使用及其简单的代码即可开启一个web服务。如下:

//开启web服务
func test(){
    http.HandleFunc("/", sayHello)
    err := http.ListenAndServe(":9090",nil)
    if err!=nil {
        log.Fatal("ListenAndServer:",err)
    }
}

func sayHello(w http.ResponseWriter, r *http.Request){
    r.ParseForm()
    fmt.Println("path",r.URL.Path)
    fmt.Println("scheme",r.URL.Scheme)

    fmt.Fprintf(w, "Hello Guest!")
}

在使用ListenAndServe这个方法时,系统就会给我们指派一个路由器,DefaultServeMux是系统默认使用的路由器,如果ListenAndServe这个方法的第2个参数传入nil,系统就会默认使用DefaultServeMux。当然,这里也可以传入自定义的路由器。

先来看http.HandleFunc("/", sayHello),从HandleFunc方法点进去,如下:

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

在这里调用了DefaultServeMux的HandleFunc方法,这个方法有两个参数,pattern是匹配的路由规则,handler表示这个路由规则对应的处理方法,并且这个处理方法有两个参数。

在我们书写的代码示例中,pattern对应/,handler对应sayHello,当我们在浏览器中输入http://localhost:9090时,就会触发sayHello方法。

我们再顺着DefaultServeMux的HandleFunc方法继续点下去,如下:

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

在这个方法中,路由器又调用了Handle方法,注意这个Handle方法的第2个参数,将之前传入的handler这个响应方法强制转换成了HandlerFunc类型。

这个HandlerFunc类型到底是个什么呢?如下:

type HandlerFunc func(ResponseWriter, *Request)

看来和我们定义的SayHello方法的类型都差不多。但是!!! 这个HandlerFunc默认实现了ServeHTTP接口!这样HandlerFunc对象就有了ServeHTTP方法!如下:

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

这个细节是十分重要的,因为这一步关乎到当路由规则匹配时,相应的响应方法是否会被调用的问题!这个方法的调用时机会在下一小节中讲到。

接下来,我们返回去继续看mux的Handle方法,也就是这段代码mux.Handle(pattern, HandlerFunc(handler))。这段代码做了哪些事呢?源码如下:

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
    }

    // Helpful behavior:
    // If pattern is /tree/, insert an implicit permanent redirect for /tree.
    // It can be overridden by an explicit registration.
    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}
    }
}

代码挺多,其实主要就做了一件事,向DefaultServeMux的map[string]muxEntry中增加对应的路由规则和handler。

map[string]muxEntry是个什么鬼?

  • map是一个字典对象,它保存的是key-value。
  • [string]表示这个字典的key是string类型的,这个key值会保存我们的路由规则。
  • muxEntry是一个实例对象,这个对象内保存了路由规则对应的处理方法。

找到相应代码,如下:

//路由器
type ServeMux struct {
    mu    sync.RWMutex
    m     map[string]muxEntry //路由规则,一个string对应一个mux实例对象,map的key就是注册的路由表达式(string类型的)
    hosts bool // whether any patterns contain hostnames
}

//muxEntry
type muxEntry struct {
    explicit bool
    h        Handler //这个路由表达式对应哪个handler
    pattern  string
}

//路由响应方法
type Handler interface {
    ServeHTTP(ResponseWriter, *Request)  //handler的路由实现器
}

ServeMux就是这个系统默认的路由器。

最后,总结一下这个部分:

  1. 调用http.HandleFunc("/", sayHello)
  2. 调用DefaultServeMux的HandleFunc(),把我们定义的sayHello()包装成HandlerFunc类型
  3. 继续调用DefaultServeMux的Handle(),向DefaultServeMux的map[string]muxEntry中增加路由规则和对应的handler

OK,这部分代码做的事就这么多,第一部分结束。

第二部分主要就是研究这句代码err := http.ListenAndServe(":9090",nil),也就是ListenAndServe这个方法。从这个方法点进去,如下:

func ListenAndServe(addr string, handler Handler) error {
    server := &Server{Addr: addr, Handler: handler}
    return server.ListenAndServe()
}

在这个方法中,初始化了一个server对象,然后调用这个server对象的ListenAndServe方法,在这个方法中,如下:

func (srv *Server) ListenAndServe() error {
    addr := srv.Addr
    if addr == "" {
        addr = ":http"
    }
    ln, err := net.Listen("tcp", addr)
    if err != nil {
        return err
    }
    return srv.Serve(tcpKeepAliveListener{ln.(*net.TCPListener)})
}

在这个方法中,调用了net.Listen("tcp", addr),也就是底层用TCP协议搭建了一个服务,然后监控我们设置的端口。

代码的最后,调用了srv的Serve方法,如下:

func (srv *Server) Serve(l net.Listener) error {
    defer l.Close()
    if fn := testHookServerServe; fn != nil {
        fn(srv, l)
    }
    var tempDelay time.Duration // how long to sleep on accept failure

    if err := srv.setupHTTP2_Serve(); err != nil {
        return err
    }

    srv.trackListener(l, true)
    defer srv.trackListener(l, false)

    baseCtx := context.Background() // base is always background, per Issue 16220
    ctx := context.WithValue(baseCtx, ServerContextKey, srv)
    ctx = context.WithValue(ctx, LocalAddrContextKey, l.Addr())
    for {
        rw, e := l.Accept()
        if e != nil {
            select {
            case <-srv.getDoneChan():
                return ErrServerClosed
            default:
            }
            if ne, ok := e.(net.Error); ok && ne.Temporary() {
                if tempDelay == 0 {
                    tempDelay = 5 * time.Millisecond
                } else {
                    tempDelay *= 2
                }
                if max := 1 * time.Second; tempDelay > max {
                    tempDelay = max
                }
                srv.logf("http: Accept error: %v; retrying in %v", e, tempDelay)
                time.Sleep(tempDelay)
                continue
            }
            return e
        }
        tempDelay = 0
        c := srv.newConn(rw)
        c.setState(c.rwc, StateNew) // before Serve can return
        go c.serve(ctx)
    }
}

最后3段代码比较重要,也是Go语言支持高并发的体现,如下:

c := srv.newConn(rw)
c.setState(c.rwc, StateNew) // before Serve can return
go c.serve(ctx)

上面那一大坨代码,总体意思是进入方法后,首先开了一个for循环,在for循环内时刻Accept请求,请求来了之后,会为每个请求创建一个Conn,然后单独开启一个goroutine,把这个请求的数据当做参数扔给这个Conn去服务:go c.serve()。用户的每一次请求都是在一个新的goroutine去服务,每个请求间相互不影响。

在conn的serve方法中,有一句代码很重要,如下:

serverHandler{c.server}.ServeHTTP(w, w.req)

表示serverHandler也实现了ServeHTTP接口,ServeHTTP方法实现如下:

func (sh serverHandler) ServeHTTP(rw ResponseWriter, req *Request) {
    handler := sh.srv.Handler
    if handler == nil {
        handler = DefaultServeMux
    }
    if req.RequestURI == "*" && req.Method == "OPTIONS" {
        handler = globalOptionsHandler{}
    }
    handler.ServeHTTP(rw, req)
}

在这里如果handler为空(这个handler就可以理解为是我们自定义的路由器),就会使用系统默认的DefaultServeMux,代码的最后调用了DefaultServeMux的ServeHTTP()

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是Handler接口对象
    h.ServeHTTP(w, r)       //调用Handler接口对象的ServeHTTP方法实际上就调用了我们定义的sayHello方法
}

路由器接收到请求之后,如果是*那么关闭链接,如果不是*就调用mux.Handler(r)返回该路由对应的处理Handler,然后执行该handler的ServeHTTP方法,也就是这句代码h.ServeHTTP(w, r),mux.Handler(r)做了什么呢?如下:

func (mux *ServeMux) Handler(r *Request) (h Handler, pattern string) {
    if r.Method != "CONNECT" {
        if p := cleanPath(r.URL.Path); p != r.URL.Path {
            _, pattern = mux.handler(r.Host, p)
            url := *r.URL
            url.Path = p
            return RedirectHandler(url.String(), StatusMovedPermanently), pattern
        }
    }

    return mux.handler(r.Host, r.URL.Path)
}

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
}

func (mux *ServeMux) match(path string) (h Handler, pattern string) {
    var n = 0
    for k, v := range mux.m {  //mux.m就是系统默认路由的map
        if !pathMatch(k, path) {
            continue
        }
        if h == nil || len(k) > n {
            n = len(k)
            h = v.h
            pattern = v.pattern
        }
    }
    return
}

它会根据用户请求的URL到路由器里面存储的map中匹配,匹配成功就会返回存储的handler,调用这个handler的ServeHTTP()就可以执行到相应的处理方法了,这个处理方法实际上就是我们刚开始定义的sayHello(),只不过这个sayHello()被HandlerFunc又包了一层,因为HandlerFunc实现了ServeHTTP接口,所以在调用HandlerFunc对象的ServeHTTP()时,实际上在ServeHTTP ()的内部调用了我们的sayHello()。

总结一下:

  1. 调用http.ListenAndServe(":9090",nil)
  2. 实例化server
  3. 调用server的ListenAndServe()
  4. 调用server的Serve方法,开启for循环,在循环中Accept请求
  5. 对每一个请求实例化一个Conn,并且开启一个goroutine为这个请求进行服务go c.serve()
  6. 读取每个请求的内容c.readRequest()
  7. 调用serverHandler的ServeHTTP(),如果handler为空,就把handler设置为系统默认的路由器DefaultServeMux
  8. 调用handler的ServeHTTP() =>实际上是调用了DefaultServeMux的ServeHTTP()
  9. 在ServeHTTP()中会调用路由对应处理handler
  10. 在路由对应处理handler中会执行sayHello()

有一个需要注意的点: DefaultServeMux和路由对应的处理方法handler都实现了ServeHTTP接口,他们俩都有ServeHTTP方法,但是方法要达到的目的不同,在DefaultServeMux的ServeHttp()里会执行路由对应的处理handler的ServeHttp()。

文章转自

https://juejin.cn/post/6844903550993039374

#golang# #http#
QQ扫一扫交流

标题:Go web源码解析

链接:https://www.zhaohaiyu.com/post/go/go-net-http/

作者:haiyux

声明: 本博客文章除特别声明外,均采用 CC BY-NC-SA 3.0许可协议,转载请注明出处!

创作实属不易,如有帮助,那就打赏博主些许茶钱吧 ^_^
WeChat Pay

微信打赏

Alipay

支付宝打赏

Go gc垃圾回收
Http Https
  • 文章目录
  • 站点概览
haiyux

haiyux

路漫漫其修远兮,吾将上下而求索

62 日志
8 分类
20 标签
GitHub Email
友情链接
  • kratos
  • farer
  • 我的博客园
  • 李文周
  • 凡梦星尘
标签云
  • Golang 30
  • 微服务 8
  • Mysql 7
  • HTTP 4
  • Protobuf 4
  • 运维 4
  • Grpc 3
  • 设计模式 3
  • Redis 2
  • Ddd 1
  • Docker 1
  • Kratos 1
  • Makefile 1
  • Mq 1
  • Nginx 1
  • Raft 1
  • Thrift 1
  • Traceing 1
  • Wire 1
  • 泛型 1
  • Go的web工作原理
  • 文章转自
© 2010 - 2022 haiyux's blog
Powered by - Hugo v0.98.0 / Theme by - NexT
Storage by 又拍云存储 / 京ICP备19052634号-2
0%