golang 建立web服务器 http包源码详解 - Go语言中文社区

golang 建立web服务器 http包源码详解


golang 建立web服务器 http包源码详解

首先,熟悉http协议的都知道,http协议是基于TCP实现的。

http服务器的工作方式大概就是监听socket端口,接受连接,获取到请求,处理请求,返回响应。

所以,对应的会有几个部分

  • Request:用户请求的信息。post、get、url等这些信息

  • Response: 返回给客户端的信息

  • Conn: 用户每次的连接请求

  • Handler:处理请求和返回信息的逻辑处理

1. 搭建简单的web服务器

用golang可以很快地建立一个Web服务器

// httpWeb project main.go
package main

import (
    "fmt"
    "log"
    "net/http"
    "strings"
)

func HandleRequest(w http.ResponseWriter, r *http.Request) {
    r.ParseForm()
    fmt.Println(r.Form)
    fmt.Println("path", r.URL.Path)
    fmt.Println("scheme", r.URL.Scheme)
    fmt.Println(r.Form["bookId"])
    for k, v := range r.Form {
        fmt.Printf("key: %s, value: %s n", k, strings.Join(v, " "))
    }
    fmt.Fprintf(w, "Respone message: Server get bookId successed....")
}

func main() {
    http.HandleFunc("/", HandleRequest)
    err := http.ListenAndServe(":6666", nil)
    if err != nil {
        log.Fatal("ListenError:", err)
    }
}

当输入 localhost:6060/?bookId=2222&bookId=6666 ,就会连接到该服务器,效果如下:

request

服务接收请求。至于二次请求favicon.ico的问题暂不讨论。

server

这样子,简单的一个web服务器就可以运行,处理简单的用户请求。


2. http包源码探究

只是这样子知道怎么搭起一个web服务器并不能满足对技术的好奇心

想要了解简单的几行代码是如何就建立起一web服务器的?

最好的方法就是直接读源代码,让代码来告诉我们实现的原理

回到一开始的web服务器,仔细想想,基本上是两个函数撑起整个Web服务器啊!

居然如此简洁!!

http.HandleFunc("/", HandleRequest)

http.ListenAndServe(":6666", nil)

不难理解,大概就是处理请求的函数,和监听端口。

首先,先分析一下http.HandleFunc()这个函数。

直接进入函数HandleFunc的声明,源码如下

// HandleFunc registers the handler function for the given pattern
// in the DefaultServeMux.
// The documentation for ServeMux explains how patterns are matched.
func HandleFunc(pattern string, handler func(ResponseWriter, *Request)) {
    DefaultServeMux.HandleFunc(pattern, handler)
}

这里DefaultServeMux调用了HandleFunc()

参数就是传进来的“/”HandleFunc(定义一个函数类型,就可以把函数作为参数传入)

对于DefaultServeMux在源代码中的声明如下

type ServeMux struct {
    mu    sync.RWMutex
    m     map[string]muxEntry
    hosts bool // whether any patterns contain hostnames
}

type muxEntry struct {
    explicit bool
    h        Handler
    pattern  string
}

// NewServeMux allocates and returns a new ServeMux.
func NewServeMux() *ServeMux { 
    return &ServeMux{m: make(map[string]muxEntry)} 
}

// DefaultServeMux is the default ServeMux used by Serve.
var DefaultServeMux = NewServeMux()

可以看到,DefaultServeMux是一个默认的ServerMux,而ServeMux的结构体中,mu sync.RWMutex是一个并发控制的锁,一个装有muxEntry结构的map,一个hosts判断是否包含主机名。

!!!仔细一看,这个muxEntry结构体,其中一个变量h Handler和另一个变量pattern string

而这个Handler定义如下

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

是一个接口!!方法是ServeHTTP(ResponseWriter, *Request)

总结一下

DefaultServeMux的代码看完,大概清楚了。DefaultServeMux是一个ServerMux结构,这个结构里面最最主要包含一个map[string]muxEntry,map里面的每个muxEntry又包含一个string类型的变量pattern和一个接口Handler,这个接口里面的方法是ServeHTTP(ResponseWriter, *Request)。

好了,了解完DefaultServeMux,继续进入它调用的函数。

// HandleFunc registers the handler function for the given pattern.
func (mux *ServeMux) HandleFunc(pattern string, handler func(ResponseWriter, *Request)) {
    mux.Handle(pattern, HandlerFunc(handler))
}

这里相当于调用了ServeMux的方法Handle()。(涉及到golang中的面向对象的方法)

这里先分析HandlerFunc(handler),也就是把func(ResponseWriter, *Request)函数类型转换为HandlerFunc类型(注意!是HandlerFunc,不是HandleFunc)

先来看看HandlerFunc是什么。

// The HandlerFunc type is an adapter to allow the use of
// ordinary functions as HTTP handlers.  If f is a function
// with the appropriate signature, HandlerFunc(f) is a
// Handler object that calls f.
type HandlerFunc func(ResponseWriter, *Request)

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

HandlerFunc这里居然定义了一个func(ResponseWriter, *Request)的函数类型,HandlerFunc(handler)实际上就是handler本身。为什么这么做?

接看下面的函数。

HandlerFunc实现了ServeHTTP(w ResponseWriter, r *Request) 这个方法!!!!里面只有一行代码f(w, r),也就是说实际上是调用handler,也就是我们一开始在http.HandleFunc("/", HandleRequest)中自己定义的函数HandleRequest。

关键来了!!!!

ServeHTTP(w ResponseWriter, r *Request)这个方法不就是上面Handler接口里面的方法吗!

HandlerFunc实现Handler接口里面的方法。跟Java里面的接口一样,任何实现接口的类型,都可以向上转换为该接口类型。这就意味着HandlerFunc类型的值可以自动转换为Handler类型的值作为参数传进任何函数中。这很重要!回头看看muxEntry结构体里面的两个变量pattern stringh Handler,不正好对应刚才传入的参数pattern, HandlerFunc(handler)吗!!

总结一下

所以,HandlerFunc(handler)就是一个适配器模式!HandlerFunc实现Handler接口,ServeHTTP方法里面调用的实际上是我们一开始定义的函数HandleRequest。

这样的一个好处就是,func(ResponseWriter, *Request) -> HandlerFunc -> Handler ,那定义的函数HandleRequest可以作为Handler类型的一个参数。调用Handler的ServeHTTP方法,也就是调用定义的函数HandleRequest。

理解完HandlerFunc(handler),再来看看整句mux.Handle(pattern, HandlerFunc(handler))

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 " + pattern)
    }
    if handler == nil {
        panic("http: nil handler")
    }
    if mux.m[pattern].explicit {
        panic("http: multiple registrations for " + pattern)
    }

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

这里的代码直接关注到第17行和37行

由于37行的代码是经过判断后进入if的语句,所以别扣细节,直接关注第17行。

mux.m[pattern] = muxEntry{explicit: true, h: handler, pattern: pattern}

都是赋值语句,基本逻辑都是为DefaultServeMux里面的map赋值。

总结一下

这里就是把传进来的pattern和handler保存在muxEntry结构中,并且pattern作为key,把muxEntry装入到DefaultServeMux的Map里面。

至此,第一个关键的函数http.HandleFunc("/", HandleRequest)就分析完了,就是把当前的参数"/", HandleRequest保存在http包里面的默认的一个ServeMux结构中的map中,简单来说就是保存当前路由和自己定义的那个处理函数。虽然理顺思路感觉挺简单,但是不得不赞叹设计得实在太妙了

那么接下来就是http.ListenAndServe(":6666", nil)

依旧,直接进入ListenAndServe函数

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

函数小而精巧,把addr放到一个Server结构中,并且调用ListenAndServer()。这里面向对象的方法,相当于Java中new一个对象的实例,并且调用该实例的方法。

继续进入函数

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

可以看出HTTP协议也是基于TCP实现的。

监听端口,使用tcp协议,并把tcp的监听传入到Serve()函数中

Server()源码如下:

/ Serve accepts incoming connections on the Listener l, creating a
// new service goroutine for each.  The service goroutines read requests and
// then call srv.Handler to reply to them.
func (srv *Server) Serve(l net.Listener) error {
    defer l.Close()
    var tempDelay time.Duration // how long to sleep on accept failure
    for {
        rw, e := l.Accept()
        if e != nil {
            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, err := srv.newConn(rw)
        if err != nil {
            continue
        }
        c.setState(c.rwc, StateNew) // before Serve can return
        go c.serve()
    }
}

这个函数就体现了golang并发性。

在for循环里面,直接看到第8行、第26行和第31行

for {
        rw, e := l.Accept()
        ...
        c, err := srv.newConn(rw)
        ...
        go c.serve()
    }

简化之后,看上去逻辑就清晰多了

首先,tcp在监听,然后循环接受请求,建立连接,并且用关键字go开启一个服务并发地处理每一个连接。

关于gorutine,作为一个轻量级的线程——协程,每一个消耗资源很小,能够有效地处理并发的问题。这里对每一个服务开启一个gorutine来处理,这里就是高并发的体现!

那么继续往下,到底是怎样处理每个的连接?

这个server()代码很长,源码如下:

// Serve a new connection.
func (c *conn) serve() {
    origConn := c.rwc // copy it before it's set nil on Close or Hijack
    defer func() {
        if err := recover(); err != nil {
            const size = 64 << 10
            buf := make([]byte, size)
            buf = buf[:runtime.Stack(buf, false)]
            c.server.logf("http: panic serving %v: %vn%s", c.remoteAddr, err, buf)
        }
        if !c.hijacked() {
            c.close()
            c.setState(origConn, StateClosed)
        }
    }()

    if tlsConn, ok := c.rwc.(*tls.Conn); ok {
        if d := c.server.ReadTimeout; d != 0 {
            c.rwc.SetReadDeadline(time.Now().Add(d))
        }
        if d := c.server.WriteTimeout; d != 0 {
            c.rwc.SetWriteDeadline(time.Now().Add(d))
        }
        if err := tlsConn.Handshake(); err != nil {
            c.server.logf("http: TLS handshake error from %s: %v", c.rwc.RemoteAddr(), err)
            return
        }
        c.tlsState = new(tls.ConnectionState)
        *c.tlsState = tlsConn.ConnectionState()
        if proto := c.tlsState.NegotiatedProtocol; validNPN(proto) {
            if fn := c.server.TLSNextProto[proto]; fn != nil {
                h := initNPNRequest{tlsConn, serverHandler{c.server}}
                fn(c.server, tlsConn, h)
            }
            return
        }
    }

    for {
        w, err := c.readRequest()
        if c.lr.N != c.server.initialLimitedReaderSize() {
            // If we read any bytes off the wire, we're active.
            c.setState(c.rwc, StateActive)
        }
        if err != nil {
            if err == errTooLarge {
                // Their HTTP client may or may not be
                // able to read this if we're
                // responding to them and hanging up
                // while they're still writing their
                // request.  Undefined behavior.
                io.WriteString(c.rwc, "HTTP/1.1 413 Request Entity Too Largernrn")
                c.closeWriteAndWait()
                break
            } else if err == io.EOF {
                break // Don't reply
            } else if neterr, ok := err.(net.Error); ok && neterr.Timeout() {
                break // Don't reply
            }
            io.WriteString(c.rwc, "HTTP/1.1 400 Bad Requestrnrn")
            break
        }

        // Expect 100 Continue support
        req := w.req
        if req.expectsContinue() {
            if req.ProtoAtLeast(1, 1) && req.ContentLength != 0 {
                // Wrap the Body reader with one that replies on the connection
                req.Body = &expectContinueReader{readCloser: req.Body, resp: w}
            }
            req.Header.Del("Expect")
        } else if req.Header.get("Expect") != "" {
            w.sendExpectationFailed()
            break
        }

        // HTTP cannot have multiple simultaneous active requests.[*]
        // Until the server replies to this request, it can't read another,
        // so we might as well run the handler in this goroutine.
        // [*] Not strictly true: HTTP pipelining.  We could let them all process
        // in parallel even if their responses need to be serialized.
        serverHandler{c.server}.ServeHTTP(w, w.req)
        if c.hijacked() {
            return
        }
        w.finishRequest()
        if !w.shouldReuseConnection() {
            if w.requestBodyLimitHit || w.closedRequestBodyEarly() {
                c.closeWriteAndWait()
            }
            break
        }
        c.setState(c.rwc, StateIdle)
    }
}

实际上,大概思路也是只要关注for循环里面的第40行、第82行和第86行

for{
  w, err := c.readRequest()
  ...
  serverHandler{c.server}.ServeHTTP(w, w.req)
  ...
  w.finishRequest()
}

这样就清晰很多了。

循环地读取请求,并且开始处理请求的服务,最后请求完。

先直接进入ServeHTTP

type serverHandler struct {
    srv *Server
}

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

因为一开始http.ListenAndServe(":6666", nil)第二个参数Handler设置为nil,而sh.srv.Handler就是第二个参数的值。

所以handler := sh.srv.Handler这里获取的handler为nil,接着进入if,执行handler = DefaultServeMux

这里居然把DefaultServeMux赋值给handler,那么DefaultServeMux也实现了Handler的接口。

在源码中找到DefaultServeMux实现的ServeHTTP函数

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

同样,跳过这里的if判断部分,假设程序正常执行。

所以直接关注最后两句

h, _ := mux.Handler(r)
h.ServeHTTP(w, r)

第一句,直接找到ServeMux结构体拥有的Handler方法。

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

继续跳过这里的if判断部分,假设程序正常执行,r.Method是”CONNECT”。

直接进入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
}

看到这里,已经很清楚了,正常执行主要就是执行mux.match()这个函数,并且返回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
        }
        if h == nil || len(k) > n {
            n = len(k)
            h = v.h
            pattern = v.pattern
        }
    }
    return
}

对一个map循环,匹配出key为pattern的值,值为Handler

到这里!已经完全明白了,一层一层函数看回头。

也就是在 DefaultServeMux中的map中找到个pattern相对应的Handler(其实值就是我们自定义的处理函数)。

第一句h, _ := mux.Handler(r)就是根据请求的路径来匹配一个路由,并且返回一个Handler值。

接着第二句h.ServeHTTP(w, r)

又是熟悉的ServeHTTP函数。看回第一部分析http.HandleFunc("/", HandleRequest)的时候,此时h值就是那时候保存的HandlerFunc类型,使用了适配器模式,而调用ServeHTTP就是直接调用我们定义的函数HandleRequest。

到这里是不是就恍然大悟了!原来是这样。监听,建立连接,获取请求,通过匹配路由,来返回相应的处理函数。在处理函数里面处理请求的信息,并且返回消息给客户端。

3.3.illustrator

那么整for循环里面,最后执行w.finishRequest()

func (w *response) finishRequest() {
    w.handlerDone = true

    if !w.wroteHeader {
        w.WriteHeader(StatusOK)
    }

    w.w.Flush()
    putBufioWriter(w.w)
    w.cw.close()
    w.conn.buf.Flush()

    // Close the body (regardless of w.closeAfterReply) so we can
    // re-use its bufio.Reader later safely.
    w.req.Body.Close()

    if w.req.MultipartForm != nil {
        w.req.MultipartForm.RemoveAll()
    }
}

这里思路非常简单,直接关注其中的两句

w.w.Flush()
w.conn.buf.Flush()

由于,在自定义的处理函数HandleRequest中,会写出数据

fmt.Fprintf(w, "Respone message: Server get bookId successed....")

http.ResponseWriter已经写出数据

这时候http.ResponseWriter再Flush(),把缓冲里的东西也Flush()

最后就可以往客户端发回信息了。

这时候有突然又有个好奇

HandleRequest(w http.ResponseWriter, r *http.Request) 

这里函数的参数为什么一个是值传递,一个是引用传递?

直接看源代码就知道答案了

查看两个参数的结构

type ResponseWriter interface {
    Header() Header
    Write([]byte) (int, error)
    WriteHeader(int)
}

type Request struct {
    Method string
    URL *url.URL
    ...
    Header Header
    Body io.ReadCloser
    ContentLength int64
    ...
    Host string
    Form url.Values
    ...
}

一个是接口,一个是结构体

对于接口,可以直接传进来,然后调用其方法

对于结构体,如果直接传值,把整个结构体传进来有点消耗太大了,把它的引用传进来开销就小了很多。

整个http包实现web部分的源代码已经分析完了

设计实在非常妙

版权声明:本文来源CSDN,感谢博主原创文章,遵循 CC 4.0 by-sa 版权协议,转载请附上原文出处链接和本声明。
原文链接:https://blog.csdn.net/qq_24850089/article/details/52713701
站方申明:本站部分内容来自社区用户分享,若涉及侵权,请联系站方删除。
  • 发表于 2019-08-27 19:57:14
  • 阅读 ( 885 )
  • 分类:Go

0 条评论

请先 登录 后评论

官方社群

GO教程

猜你喜欢