Go net/http,web server

2019/07/12 00:31
阅读数 57

net/http 包实现 HTTP Server

Go 中,实现一个最简单的 http server 非常容易,代码如下:

package main
import (
	"fmt"
	"net/http"
)

func IndexHandlers(w http.ResponseWriter, r *http.Request){
	fmt.Fprintln(w, "hello, world")
}

func main (){
	http.HandleFunc("/", IndexHandlers)
	err := http.ListenAndServe("127.0.0.1:8088", nil)
	if err != nil {
		fmt.Printf("listen error:[%v]", err.Error())
	}
}

  

HTTP

通过上面这个简单的例子,来一点一点学习 Go 的 net/http 实现的 web 服务的原理

理解 HTTP 相关的网络应用,主要关注两个地方:客户端(client)和服务端(server)

两者的交互主要是 client 的 request 以及 server 的 response,主要就在于如何接受 client 的 request 并向 client 返回 response

 

接收 request 的过程中,最重要的莫过于路由(router),即实现一个 Multiplexer(多路复用的路由系统),Multiplexer 路由系统的目的就是为了找到处理器函数(handler),handler 将对 request 进行处理,同时构建 response

流程如下:

Clinet -> Requests ->  [Multiplexer(router) -> handler  -> Response -> Clinet

  

理解 Go 中的 http 服务,最重要的就是要理解 Multiplexer 和 handler,下面对 handler 的几个重要概念进行说明

  • handler 函数:具有 func(w http.ResponseWriter, r *http.Requests) 签名的函数
  • handler 处理器(函数):经过 HandlerFunc() 函数包装处理后的 handler 函数,就成为实现了 ServeHTTP() 接口方法的函数,调用 handler 处理器的 ServeHTTP() 方法时,即调用 handler 函数本身
  • handler 对象:实现了 Handler 接口中 ServeHTTP() 方法的结构

 

Handler 接口

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

Go 没有继承,类多态的方式可以通过接口实现,所谓接口则是定义了声明了函数的签名,任何结构只要实现了与接口函数签名相同的方式,就等同于实现了接口,Go 的 http 服务都是基于 handler 进行处理

任何结构体,只要实现了 ServeHTTP() 方法,这个结构就可以称之为 handler 对象,ServeMux 会使用 handler 并调用其 ServeHTTP() 方法处理请求并返回响应

 

ServeMux 结构体

Go 中的 Multiplexer 是基于 ServeMux 结构体,DefaultServeMux是ServeMux的一个实例

Go 中既可以使用内置的 DefaultServeMux(默认的路由系统),也可以自定义

 

ServeMux 结构中最重要的字段是 m,这是一个 map,key是一些 url 模式,value 则是一个 muxEntry 结构体,定义存储了具体的 url 模式 和 Handler 接口

ServeMux 同时也实现了 Handler 接口,不过 ServeMux 的 ServeHTTP() 方法不是用来处理 request 和 构建 response 的,而是用来找到路由注册的 hanlder() 函数

 

ServeMux 的源码

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

type muxEntry struct {
    explicit bool
    h        Handler
    pattern  string
}

 

Server

除了 ServeMux 和 Handler,还有一个 Server 结构体需要了解,从 http.ListenAndServe 的源码可以看出,它创建了一个 server 对象,并调用 server 对象的 ListenAndServe() 方法

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

  

查看 Server 的结构体如下:

type Server struct {
    Addr         string        
    Handler      Handler       
    ReadTimeout  time.Duration 
    WriteTimeout time.Duration 
    TLSConfig    *tls.Config   

    MaxHeaderBytes int

    TLSNextProto map[string]func(*Server, *tls.Conn, Handler)

    ConnState func(net.Conn, ConnState)
    ErrorLog *log.Logger
    disableKeepAlives int32     nextProtoOnce     sync.Once 
    nextProtoErr      error     
}

Server 结构存储了服务器处理请求常见的字段,其中 Handler 字段也保留了 Handler 接口类型,如果 Server 接口没有提供 Handler 接口类型,那么会使用内置的 DefaultServeMux(默认的路由系统),后面再做分析

 

创建 HTTP 服务

创建一个 http 服务,大致需要经历两个过程,首先需要注册路由,即提供 url 模式 和 handler 函数的映射,其次就是实例化一个 Server 的对象,并开启对客户端的监听

再看 http 服务中的代码:

//注册路由
http.HandleFunc("/", indexHandler)

//实例化 Server 类型,并开启对客户端的监听
http.ListenAndServe("127.0.0.1:8000", nil)

或者:

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

  

注册路由,开启服务监听,处理http请求的过程 源码分析

net/http 包提供的注册路由的 api 很简单,http.HandleFunc 选择了 DefaultServeMux 作为 Multiplexer

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

  

DefaultServeMux 是 ServeMux 结构体的实例,当然 http 包也提供了 NewServeMux() 方法创建一个 ServeMux 实例,默认则创建一个 DefaultServeMux

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

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

var defaultServeMux ServeMux

  

DefaultServeMux 的 HandlerFunc(parrern, handler) 方法实际是定义在 ServeMux 下的

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

  

HandlerFunc() 是一个函数,同时实现了 Handler 接口的 ServeHTTP() 方法,使用 HandlerFunc() 函数包装路由定义的 IndexHandlers() 函数,其目的就是为了让这个函数也实现 ServeHTTP() 方法,从而实现 Handler 接口,即转变成一个 handler 处理器(函数)

发生函数类型转换的源码:

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

 

HandlerFunc 函数实现 Handler 接口的源码

type HandlerFunc func(ResponseWriter, *Request)

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

  

最开始写的例子中

http.HandleFunc("/",Indexhandler)

这样 IndexHandler() 函数实现了 Handler 接口,接下来,ServeMux 的 Handle() 方法将会对 pattern 和 IndexHandler() 函数做一个 map 映射

Handler() 函数的主要目的在于把 IndexHandler() 函数 和 pattern 模式绑定到 map[string]muxEntry 这样一个 map

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

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

 

此时,pattern 和 IndexHandler() 的路由注册完成,接下来就是如何开启 Server 的监听,以接收客户端的请求

注册好路由之后,启动 web 服务还需要开启服务器监听,http 包中的 ListenAndServe() 方法中可以看到创建一个 Server 类型对象,并调用了 Server 类型对象的同名方法

func ListenAndServe(addr string, handler Handler) error {
    server := &Server{Addr: addr, Handler: handler}
    return server.ListenAndServe()
}
// ListenAndServe listens on the TCP network address srv.Addr and then
// calls Serve to handle requests on incoming connections.
// Accepted connections are configured to enable TCP keep-alives.
// If srv.Addr is blank, ":http" is used.
// ListenAndServe always returns a non-nil error.
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)})
}

 

Server 的 ListenAndServe() 方法中,会初始化监听地址 Addr,同时调用 Listen() 方法设置监听,最后将监听的 TCP 对象传入 Serve() 方法

// 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.
//
// For HTTP/2 support, srv.TLSConfig should be initialized to the
// provided listener's TLS Config before calling Serve. If
// srv.TLSConfig is non-nil and doesn't include the string "h2" in
// Config.NextProtos, HTTP/2 support is not enabled.
//
// Serve always returns a non-nil error. After Shutdown or Close, the
// returned error is ErrServerClosed.
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)
    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)
    }
}
View Code

 

监听开启之后,一旦有客户端请求过来,Go 就开启一个协程处理请求,主要逻辑都在 Serve() 方法中

Serve() 方法比较长,其主要职能就是,创建一个上下文对象,然后调用 Listener 的 Accept() 方法用来获取连接数据并使用 newConn() 方法创建连接对象,最后使用 goroutine 协程的方式处理连接请求,因为每一个连接都开启了一个协程,请求的上下文都不同,同时又保证了 Go 的高并发

 

Serve()方法的源码:

使用 defer 定义了函数退出时,连接关闭相关的处理,然后就是读取连接的网络数据,并处理读取完毕时候的状态,接下来就是调用 serverHandler{c.server}.ServeHTTP(w, w.req) 方法处理请求了,最后就是请求处理完毕的逻辑

// Serve a new connection.
func (c *conn) serve(ctx context.Context) {
    c.remoteAddr = c.rwc.RemoteAddr().String()
    ctx = context.WithValue(ctx, LocalAddrContextKey, c.rwc.LocalAddr())
    defer func() {
        if err := recover(); err != nil && err != ErrAbortHandler {
            const size = 64 << 10
            buf := make([]byte, size)
            buf = buf[:runtime.Stack(buf, false)]
            c.server.logf("http: panic serving %v: %v\n%s", c.remoteAddr, err, buf)
        }
        if !c.hijacked() {
            c.close()
            c.setState(c.rwc, 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
        }
    }

    // HTTP/1.x from here on.

    ctx, cancelCtx := context.WithCancel(ctx)
    c.cancelCtx = cancelCtx
    defer cancelCtx()

    c.r = &connReader{conn: c}
    c.bufr = newBufioReader(c.r)
    c.bufw = newBufioWriterSize(checkConnErrorWriter{c}, 4<<10)

    for {
        w, err := c.readRequest(ctx)
        if c.r.remain != c.server.initialReadLimitSize() {
            // If we read any bytes off the wire, we're active.
            c.setState(c.rwc, StateActive)
        }
        if err != nil {
            const errorHeaders = "\r\nContent-Type: text/plain; charset=utf-8\r\nConnection: close\r\n\r\n"

            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.
                const publicErr = "431 Request Header Fields Too Large"
                fmt.Fprintf(c.rwc, "HTTP/1.1 "+publicErr+errorHeaders+publicErr)
                c.closeWriteAndWait()
                return
            }
            if isCommonNetReadError(err) {
                return // don't reply
            }

            publicErr := "400 Bad Request"
            if v, ok := err.(badRequestError); ok {
                publicErr = publicErr + ": " + string(v)
            }

            fmt.Fprintf(c.rwc, "HTTP/1.1 "+publicErr+errorHeaders+publicErr)
            return
        }

        // 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}
            }
        } else if req.Header.get("Expect") != "" {
            w.sendExpectationFailed()
            return
        }

        c.curReq.Store(w)

        if requestBodyRemains(req.Body) {
            registerOnHitEOF(req.Body, w.conn.r.startBackgroundRead)
        } else {
            if w.conn.bufr.Buffered() > 0 {
                w.conn.r.closeNotifyFromPipelinedRequest()
            }
            w.conn.r.startBackgroundRead()
        }

        // 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.
        // But we're not going to implement HTTP pipelining because it
        // was never deployed in the wild and the answer is HTTP/2.
        serverHandler{c.server}.ServeHTTP(w, w.req)
        w.cancelCtx()
        if c.hijacked() {
            return
        }
        w.finishRequest()
        if !w.shouldReuseConnection() {
            if w.requestBodyLimitHit || w.closedRequestBodyEarly() {
                c.closeWriteAndWait()
            }
            return
        }
        c.setState(c.rwc, StateIdle)
        c.curReq.Store((*response)(nil))

        if !w.conn.server.doKeepAlives() {
            // We're in shutdown mode. We might've replied
            // to the user without "Connection: close" and
            // they might think they can send another
            // request, but such is life with HTTP/1.1.
            return
        }

        if d := c.server.idleTimeout(); d != 0 {
            c.rwc.SetReadDeadline(time.Now().Add(d))
            if _, err := c.bufr.Peek(4); err != nil {
                return
            }
        }
        c.rwc.SetReadDeadline(time.Time{})
    }
}
View Code

 

serverHandler 是一个重要的结构体类型,它只有一个字段,即 Server 结构体类型,同时 serverHandler 实现了 Handler 接口,并在该接口方法中做了一个重要的事情,初始化 Multiplexer 路由多路复用器,如果 Server 类型没有指定 handler 类型对象(实现了 Handler 接口的类型),则使用内置的 DefaultServeMux 作为 Multiplexer,并调用初始化 Handler 类型对象的 ServeHTTP() 方法

// serverHandler delegates to either the server's Handler or
// DefaultServeMux and also handles "OPTIONS *" requests.
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)
}
View Code

 

这里 DefaultServeMux 的 ServeHTTP() 方法其实也是定义在 ServeMux 结构体中的,相关代码如下:

ServeMux 的 ServeHTTP() 方法通过调用其 handler() 方法寻找注册到路由上的 handler() 处理函数,并调用该函数的 ServeHTTP() 方法,在上面的例子中则是 IndexHander() 函数

ServeMux 的 handler() 方法对 URL 做了简单的处理,然后调用 handler() 函数,后者会创建一个锁,同时调用 match() 方法返回一个 handler 和 pattern

在 match() 方法中,ServeMux 的 m 字段是 map[string]muxEntry,后者存储了 pattern 和 handler 处理器函数,因此通过迭代 m 寻找出注册路由的 pattern 模式与实际 url 匹配的 handler() 函数并返回

返回的结构一直传递到 ServeMux 的 ServerHTTP() 方法,接下来调用 handler() 函数的 ServeHTTP() 方法,即 IndexHandler() 函数,然后把 response 写到 http.RequestWriter 对象返回给客户端

IndexHandler() 函数运行结束,即  serverHandler{c.server}.ServeHTTP(w, w.req) 运行结束,接下来就是对请求处理完毕之后,断开连接的相关逻辑

// 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.
    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
}
func (mux *ServeMux) Handler(r *Request) (h Handler, pattern string) {

    // CONNECT requests are not canonicalized.
    if r.Method == "CONNECT" {
        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 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)
}

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

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

 

至此,Go 中一个完整的 http 服务介绍完毕,包括 注册路由,开启监听,处理连接,路由处理函数

多数的 web 应用基于 HTTP 协议,客户端和服务端通过 request 和 response 的方式交互,一个 server 必不可少的两部分莫过于路由注册和连接处理,Go 通过一个 ServeMux 实现了 Multiplexer 路由多路复用器来管理路由,同时提供一个 Handler 接口提供 ServeHTTP() 方法实现了包装 handler() 处理器函数,handler() 函数处理 request 并构造 response

ServeMux 和 handler() 处理器函数的连接桥梁就是 Handler 接口,ServeMux 的 ServeHTTP() 方法实现了寻找注册路由的 handler() 处理器函数,并调用该函数的 ServeHTTP() 方法,ServeHTTP() 方法就是真正处理请求和构造响应的地方

 

 

 

参考链接:https://www.cnblogs.com/zhaof/p/8569743.html

 

ending ~

 

展开阅读全文
mux
打赏
0
0 收藏
分享
加载中
更多评论
打赏
0 评论
0 收藏
0
分享
返回顶部
顶部