Loading

Go Web: 搭建web服务器的底层实现流程

搭建一个简单的web服务

package main

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

func sayhelloName(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["num"])
	for k, v := range r.Form {
		fmt.Println("key:", k)
		fmt.Println("val:", strings.Join(v, ""))
	}
	fmt.Fprintf(w, "Hello World") // 这个写入到w的是输出到客户端的
}

func main() {
	http.HandleFunc("/", sayhelloName) // 注册了请求/的路由规则,当请求uri为"/",路由就会转到函数sayhelloName
	err := http.ListenAndServe(":9090", nil) // 设置监听的端口
	if err != nil {
		log.Fatal("ListenAndServe: ", err)
	}
}

在上述代码中,主函数调用了net/http包下的两个函数HandleFunc()ListenAndServe()

HandleFunc()

// http.HandleFunc("/", sayhelloName)
func HandleFunc(pattern string, handler func(ResponseWriter, *Request)) {
	DefaultServeMux.HandleFunc(pattern, handler)
}

在本例中pattern = "/", handler = sayhelloName(),代表HandleFunc() 注册了请求"/"的路由规则:当请求uri为"/",路由就会转到函数sayhelloName()

函数HandleFunc() 的底层实现步骤为:

  1. 调用DefaultServeMux.HandleFunc() 方法

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

    其中,DefaultServeMux的类型是*ServeMux:

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

    ServeMux类型是HTTP请求的多路转接器。它会将每一个接收的请求的URL与一个注册模式的列表进行匹配,并调用和URL最匹配的模式的处理器。

  2. 调用mux.Handle()方法,向DefaultServeMuxmap[string]muxEntry中增加对应的handler和路由规则。

    // mux.Handle(pattern, HandlerFunc(handler))
    func (mux *ServeMux) Handle(pattern string, handler Handler) {
        mux.mu.Lock()
        defer mux.mu.Unlock()
        ...
        if mux.m == nil {
            mux.m = make(map[string]muxEntry)
        }
        e := muxEntry{h: handler, pattern: pattern}
        mux.m[pattern] = e  // 增加对应的handler和路由规则
        ...
    }
    

    其中,ServeMux类型的属性如下:

    type ServeMux struct {
        mu sync.RWMutex   //锁,由于请求涉及到并发处理,因此这里需要一个锁机制
        m  map[string]muxEntry  // 路由规则,一个string对应一个mux实体,这里的string就是注册的路由表达式
        hosts bool // 是否在任意的规则中带有host信息
    }
    
    type muxEntry struct {
        explicit bool   // 是否精确匹配
        h        Handler // 这个路由表达式对应哪个handler
        pattern  string  //匹配字符串
    }
    
  3. 我们可以看到muxEntry类型的key[h]对应Handler类型:

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

    而我们定义的函数sayhelloName并未实现该接口,因此理论上是不能作为参数放入e := muxEntry{h: handler, pattern: pattern} 表达式中的。这时我们注意到在第二步调用mux.Handle(pattern, HandlerFunc(handler)) 时,其实已经通过 HandlerFunc(handler) 将函数 sayhelloName强制转换为HandlerFunc类型。而类型HandlerFunc默认拥有方法:ServeHTTP ,因此sayhelloName 就实现了Handler接口,从而可以为特定的路径处理HTTP请求。

    // HandlerFunc(handler)
    type HandlerFunc func(ResponseWriter, *Request)
    
    func (f HandlerFunc) ServeHTTP(w ResponseWriter, r *Request)
    

ListenAndServe()

// err := http.ListenAndServe(":9090", nil)
func ListenAndServe(addr string, handler Handler) error {
	server := &Server{Addr: addr, Handler: handler}
	return server.ListenAndServe()
}

其底层实现的步骤为:

  1. server := &Server{Addr: addr, Handler: handler}实例化了一个Server,它定义了运行HTTP服务端的参数:

    type Server struct {
        Addr           string        // 监听的TCP地址,如果为空字符串会使用":http"
        Handler        Handler       // 调用的处理器,如为nil会调用http.DefaultServeMux
    ...
    }
    
  2. 调用ServerListenAndServe() 方法,该方法分为两个部分:监听端口(ln, err := net.Listen("tcp", addr))和 提供服务(srv.Serve(ln))。

    // return 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(ln)
    }
    
  3. 调用net.Listen() 方法监听端口,返回一个Listener接口。

    // ln, err := net.Listen("tcp", addr)
    func Listen(network, address string) (Listener, error) {
    var lc ListenConfig
    return lc.Listen(context.Background(), network, address)
    }
    
    type Listener interface {
        // Addr返回该接口的网络地址
        Addr() Addr
        // Accept等待并返回下一个连接到该接口的连接
        Accept() (c Conn, err error)
        // Close关闭该接口,并使任何阻塞的Accept操作都会不再阻塞并返回错误。
        Close() error
    }
    

    Listener是一个用于面向流的网络协议的公用的网络监听器接口。多个线程可能会同时调用一个Listener的方法。

  4. 将上一步返回的Listener接口ln作为参数,调用Sever.Serve() 方法:

    启动一个for循环,在循环体中调用Listener接口的Accept() 方法,等待接收客户端发出的请求。

    // return srv.Serve(ln)
    func (srv *Server) Serve(l net.Listener) error {
    defer l.Close()
    var tempDelay time.Duration // how long to sleep on accept failure
    for {
            // 等待接收客户端发出的请求,返回一个接口类型Conn的实例rw
        rw, err := l.Accept()
        ...
            c := srv.newConn(rw)
        ...
        go c.serve(connCtx)
    }
    
  5. c := srv.newConn(rw)对客户端的每个请求实例化一个conn(此处的c为结构体类型conn,上文中的rw为接口类型Conn,注意大小写), 即创建了一个连接实体。这个连接实体里面保存了该次请求的信息。

    func (srv *Server) newConn(rwc net.Conn) *conn {
    c := &conn{
        server: srv,
        rwc:    rwc,
    }
    if debugServerConnections {
        c.rwc = newLoggingConn("server", c.rwc)
    }
    return c
    }
    
  6. go c.serve(connCtx)调用conn.Serve()方法开启一个goroutine为这个连接进行服务。这体现了go的支持高并发,用户的每一次请求都是在一个新的goroutine去服务,相互不影响。

    func (c *conn) serve(ctx context.Context) {
        ...
        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)
            }
    	...
    	// 处理请求
    	serverHandler{c.server}.ServeHTTP(w, w.req)
    	....
    }
    
  7. Serve()方法分为两个部分,分别是:w, err := c.readRequest(ctx)serverHandler{c.server}.ServeHTTP(w, w.req)。首先调用conn.readRequest() 方法对请求进行解析,返回了一个*response类型的响应w:

    // w, err := c.readRequest(ctx)
    func (c *conn) readRequest(ctx context.Context) (w *response, err error) {
        ...
            w = &response{
            conn:          c,
            cancelCtx:     cancelCtx,
            req:           req,
            reqBody:       req.Body,
    		...
        }
    	...
    }
    

    结构体类型response代表对一个HTTP请求的响应。由于该结构体首字母小写,因此无法在包外调用。而*response实现了ResponseWriter的全部方法。因此我们可以将*response当作一个ResponseWriter传入相应的函数,从而通过引用传递来操作response

    type ResponseWriter interface {
        Header() Header
        WriteHeader(int)
        Write([]byte) (int, error)
    }
    
    type response struct {
    	conn             *conn
    	req              *Request // request for this response
    	...
    }
    
    func (w *response) Header() Header {
        if w.cw.header == nil && w.wroteHeader && !w.cw.wroteHeader {
            w.cw.header = w.handlerHeader.Clone()
        }
        w.calledHeader = true
        return w.handlerHeader
    }
    
    func (w *response) WriteHeader(code int) {
        if w.conn.hijacked() {
            caller := relevantCaller()
            w.conn.server.logf("http: response.WriteHeader on hijacked connection from %s (%s:%d)", caller.Function, path.Base(caller.File), caller.Line)
            return
        }
        ...
    }
    
    func (w *response) Write(data []byte) (n int, err error) {
        return w.write(len(data), data, "")
    }
    

    这也解释了为什么ServeHTTP方法中的两个参数:ResponseWriter*Request,只有Request是按引用传递的,因为传入ResponseWriter的变量本质是*response

  8. 随后在Serve()方法中调用serverHandler结构体类型的ServeHTTP方法,并将w和w.req分别作为响应和请求传入:

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

    serverHandler结构体只有一个字段srv,它是一个指向Server结构体类型的指针。调用ServeHTTP方法时,会首先判断之前实例化得到的Server中Handler字段是否为空。若为空,则设置handler = DefaultServeMux

     type serverHandler struct {
     	srv *Server
     }
    
     func (sh serverHandler) ServeHTTP(rw ResponseWriter, req *Request) {
     	handler := sh.srv.Handler
     	if handler == nil {
     		handler = DefaultServeMux
     	}
     	...
     	handler.ServeHTTP(rw, req)
     }
    

    由于本例中调用http.ListenAndServe(":9090", nil)时,传入Handler的参数值为nil,因此设置handler = DefaultServeMux。上文提到,DefaultServeMux是一个指向ServeMux的指针,而*ServeMuxServeHTTP方法为:

    \\ handler.ServeHTTP(rw, req)
     func (mux *ServeMux) ServeHTTP(w ResponseWriter, r *Request) {
     	...
     	h, _ := mux.Handler(r)
     	h.ServeHTTP(w, r)
     }
    

    其中,h, _ := mux.Handler(r)内部还调用了ServeMux.handler方法,其作用是调用mux.match()根据path在DefaultServeMux中寻找对应的Handler函数。由于我们已经通过在主函数中的第一行代码中调用HandleFunc()DefaultServeMux中注册了请求"/"的路由规则(映射关系),因此当请求uri为"/"时,返回的变量h便是Handler函数sayhelloName

     func (mux *ServeMux) Handler(r *Request) (h Handler, pattern string) {
     	...
     	host := stripHostPort(r.Host)
     	...
     	return mux.handler(host, r.URL.Path)
     }
    
     // handler is the main implementation of Handler.
     func (mux *ServeMux) handler(host, path string) (h Handler, pattern string) {
     	...
     	if mux.hosts {
     		h, pattern = mux.match(host + path)
     	}
     	if h == nil {
     		h, pattern = mux.match(path)
     	}
     	if h == nil {
     		h, pattern = NotFoundHandler(), ""
     	}
     	return
     }
    
  9. 因此上述代码中的h.ServeHTTP(w, r)就是调用了Handler函数sayhelloNameServeHttp方法:

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

    ServeHTTP方法有2个参数,第二个参数是 Request ,该对象包含了该HTTP请求的所有的信息,比如请求地址、Header和Body等信息;第一个参数是 ResponseWriter ,利用 ResponseWriter 可以构造针对该请求的响应。

    这个方法内部其实就是调用sayhelloName本身。

实现流程图

3.3.illustrator

测试结果

一、无参数发起请求

客户端

20201225020323

服务端

20201225020402

二、带参数发起请求

客户端

20201225020554

服务端

20201226200222

posted @ 2020-12-23 01:19  koktlzz  阅读(214)  评论(0编辑  收藏  举报