【GO】28.golang http package source code

The Go language standard library net/httppackage provides a very easy-to-use interface, as shown below, we can use the functions provided by the standard library to quickly build new HTTP services:

func handler(w http.ResponseWriter, r *http.Request) {
    fmt.Fprintf(w, "Hi there, I love %s!", r.URL.Path[1:])
}

func main() {
    http.HandleFunc("/", handler)
    log.Fatal(http.ListenAndServe(":8080", nil))
}

Go

The above mainfunction only calls two functions provided by the standard library, which are net/http.HandleFuncthe function for registering the processor and the function for monitoring and processing requests net/http.ListenAndServe. Most server frameworks will contain these two types of interfaces, which are responsible for registering the processor respectively. And processing external requests, this is a very common pattern, we will also introduce how the standard library supports the implementation of the HTTP server according to these two dimensions.

Register Handler #

An HTTP service is composed of a set of processors that implement net/http.Handlerthe interface . When processing an HTTP request, an appropriate processor will be selected according to the route of the request:

http-server-and-handlers

Figure 9-11 HTTP service and handler

When we call the net/http.HandleFuncregistered , the standard library will use the default HTTP server net/http.DefaultServeMuxto process the request, and this method will be called directly net/http.ServeMux.HandleFunc:

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

Go

The above method will convert the handler into net/http.Handleran interface type call net/http.ServeMux.Handleregistered handler:

func (mux *ServeMux) Handle(pattern string, handler Handler) {
	if _, exist := mux.m[pattern]; exist {
		panic("http: multiple registrations for " + pattern)
	}

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

Go

Routes and corresponding handlers will be composed net/http.DefaultServeMux, and this structure will hold a net/http.muxEntryhash , which stores the mapping relationship from URL to handler, and the HTTP server will use this hash to find the handler when processing the request.

Customization of ServeMux

When we described conn.server in the previous section, in fact, the default router of the http packet was called internally, and the information of this request was passed to the back-end processing function through the router. So how does this router work?

Its structure is as follows:

type ServeMux struct {
    mu sync.RWMutex   // 锁,由于请求涉及到并发处理,因此这里需要一个锁机制
    m  map[string]muxEntry  // 路由规则,一个 string 对应一个 mux 实体,这里的 string 就是注册的路由表达式
    hosts bool // 是否在任意的规则中带有 host 信息
}

Let's take a look at muxEntry

type muxEntry struct {
    explicit bool   // 是否精确匹配
    h        Handler // 这个路由表达式对应哪个 handler
    pattern  string  // 匹配字符串
}

Then look at the definition of Handler

type Handler interface {
    ServeHTTP(ResponseWriter, *Request)  // 路由实现器
}

Handler is an interface, but the sayhelloName function in the previous section does not implement the ServeHTTP interface, why can it be added? It turns out that a type HandlerFunc is defined in the http package. The function we defined sayhelloName is the result of this HandlerFunc call. This type implements the ServeHTTP interface by default, that is, we call HandlerFunc (f), and force type conversion f to become HandlerFunc type, so that f has a ServeHTTP method.
 

type HandlerFunc func(ResponseWriter, *Request)

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

Handling Requests #

The provided by the standard library net/http.ListenAndServecan be used to monitor TCP connections and process requests. This function will use the incoming listening address and handler to initialize an HTTP server net/http.Server, and call the server's net/http.Server.ListenAndServemethod :

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

Go

net/http.Server.ListenAndServeIt will use the provided by the network library net.Listento monitor the TCP connection on the corresponding address and net/http.Server.Serveprocess the client's request through:

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

Go

net/http.Server.ServeIt will listen for external TCP connections in a loop and net/http.Server.newConncreate net/http.conn, which is the server-side representation of HTTP connections:

func (srv *Server) Serve(l net.Listener) error {
	l = &onceCloseListener{Listener: l}
	defer l.Close()

	baseCtx := context.Background()
	ctx := context.WithValue(baseCtx, ServerContextKey, srv)
	for {
		rw, err := l.Accept()
		if err != nil {
			select {
			case <-srv.getDoneChan():
				return ErrServerClosed
			default:
			}
			...
			return err
		}
		connCtx := ctx
		c := srv.newConn(rw)
		c.setState(c.rwc, StateNew) // before Serve can return
		go c.serve(connCtx)
	}
}

Go

After the server connection is created, the implementation in the standard library will create a separate Goroutine for each HTTP request and call the net/http.Conn.servemethod If the current HTTP service receives a large number of requests, a large number of Goroutines will be created internally, which may make the The overall quality of service is significantly reduced and requests cannot be processed.

func (c *conn) serve(ctx context.Context) {
	c.remoteAddr = c.rwc.RemoteAddr().String()

	ctx = context.WithValue(ctx, LocalAddrContextKey, c.rwc.LocalAddr())
	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, _ := c.readRequest(ctx)
		serverHandler{c.server}.ServeHTTP(w, w.req)
		w.finishRequest()
		...
	}
}

Go

The above code snippet is our simplified connection processing process, which includes reading the HTTP request, calling the Handler to process the HTTP request, and calling to complete the request. The read HTTP request will be called net/http.Conn.readRequest, this method will get the HTTP request from the connection and build a variable that implements net/http.ResponseWriterthe interface net/http.response, and the data written to the structure will be forwarded to the buffer held by it:

func (w *response) write(lenData int, dataB []byte, dataS string) (n int, err error) {
	...
	w.written += int64(lenData)
	if w.contentLength != -1 && w.written > w.contentLength {
		return 0, ErrContentLength
	}
	if dataB != nil {
		return w.w.Write(dataB)
	} else {
		return w.w.WriteString(dataS)
	}
}

Go

net/http.ResponseWriterAfter parsing the HTTP request and initializing the , we can call net/http.serverHandler.ServeHTTPthe lookup handler to handle the HTTP request:

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

Go

If the current HTTP server does not contain any handlers, we will use the default net/http.DefaultServeMuxto handle external HTTP requests.

net/http.ServeMuxIt is an HTTP request multiplexer, which can receive external HTTP requests, match and call the most appropriate handler according to the requested URL:

func (mux *ServeMux) ServeHTTP(w ResponseWriter, r *Request) {
	h, _ := mux.Handler(r)
	h.ServeHTTP(w, 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)
            return RedirectHandler(p, 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
}

Go

After a series of function calls, the above process will eventually call the HTTP server net/http.ServerMux.match, which will traverse the previously registered routing table and match according to specific rules:

func (mux *ServeMux) match(path string) (h Handler, pattern string) {
	v, ok := mux.m[path]
	if ok {
		return v.h, v.pattern
	}

	for _, e := range mux.es {
		if strings.HasPrefix(path, e.pattern) {
			return e.h, e.pattern
		}
	}
	return nil, ""
}

Go

If the path of the request matches the entry in the route successfully, we will call the corresponding handler in the entry, and the business logic contained in the handler will net/http.ResponseWriterconstruct response corresponding to the HTTP request and send it back to the client through the TCP connection.

Execution flow of Go code

After analyzing the http package, now let's sort out the entire code execution process.

    First call Http.HandleFunc

    Do a few things in order:

    1 Called the HandleFunc of DefaultServeMux

    2 Called the Handle of DefaultServeMux

    3 Add the corresponding handler and routing rules to the map [string] muxEntry of DefaultServeMux

    Next call http.ListenAndServe (":9090", nil)

    Do a few things in order:

    1 Instantiate the Server

    2 Call Server's ListenAndServe ()

    3 Call net.Listen ("tcp", addr) to listen to the port

    4 Start a for loop, accept the request in the loop body

    5 Instantiate a Conn for each request, and open a goroutine to serve the request go c.serve ()

    6 Read the content of each request w, err := c.readRequest ()

    7 Determine whether the handler is empty, if no handler is set (in this example, no handler is set), the handler is set to DefaultServeMux

    8 Call the handler's ServeHttp

    9 In this example, the following enters the DefaultServeMux.ServeHttp

    10 Select a handler according to the request, and enter the ServeHTTP of this handler

    mux.handler(r).ServeHTTP(w, r)

    11 Select the handler:

    A Determine whether there is a route that can satisfy this request (loop through the muxEntry of ServeMux)

    B If a route is satisfied, call the ServeHTTP of this route handler

    C If no route is satisfied, call NotFoundHandler's ServeHTTP

reference:

https://draveness.me/golang/docs/part4-advanced/ch09-stdlib/golang-net-http/

https://learnku.com/docs/build-web-application-with-golang/034-gos-http-package-detailed-solution/3171

Guess you like

Origin blog.csdn.net/chen_peng7/article/details/118731624