An in depth understanding of Dispatcher in the Go language

  • 2020-06-07 04:37:01
  • OfStack

introduce

Go USES goroutines to handle connection read and write events without blocking:


c, err := srv.newConn(rw)
  if err != nil {
    continue
  }
  go c.serve()

c is the connection created. It saves the information of the request and then passes it to the corresponding handler. handler can read the header information of the request, ensuring the independence between the requests.

The ServeMux Go

The c (this c is connection).serve () method is mentioned in the code above. Internally, the default router of the http package is called, and the router passes the information of the request to the backend handler.

The default router, ServeMux, has the following structure:


type ServeMux struct {
 mu sync.RWMutex  // Locks, because the request involves concurrent processing, are required here 1 A locking mechanism 
 m map[string]muxEntry //  Routing rules, 1 a string The corresponding 1 a mux Entities, here string Is the registered routing expression 
 hosts bool //  Whether in an arbitrary rule with host information 
}

Let's look at 1 muxEntry:


type muxEntry struct {
 explicit bool  //  Whether it matches exactly 
 h    Handler //  Which routing expression corresponds to handler
 pattern string // Matching string 
}

Then take a look at the definition of Handler:


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

Handler is an interface, but the sayhelloName function in the previous section does not implement the INTERFACE ServeHTTP and can still be added to the routing table. The reason is that there is another HandlerFunc in the http package. The function sayhelloName we define is the result of the call to HandlerFunc HandlerFunc(f) , casts f to the HandlerFunc type, so f has the ServeHTTP method.


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

Let's take a look at the official notes of HandlerFunc:

The HandlerFunc type is an adapter that allows ordinary functions to be used as HTTP handlers. If f is a function with an appropriate signature, HandlerFunc(f) It's Handler calling f.

Appropriate signature, one is because the author deep (after all, is I this life language java), guess is under 1 refers to the function parameters and return values, that is to say: if a function parameter is two, respectively is a pointer to the Request ResponseWriter and, and the return value is the function of void type, can be strong to HandlerFunc, and in the final call f method namely ServeHttp Handler interface.

After the routing rules are stored in the router, how are the specific requests distributed? See the following code. The default router implements ServeHTTP:


func (mux *ServeMux) ServeHTTP(w ResponseWriter, r *Request) {
 if r.RequestURI == "*" {
 w.Header().Set("Connection", "close")
 w.WriteHeader(StatusBadRequest)
 return
 }
 h, _ := mux.Handler(r)
 h.ServeHTTP(w, r)
}

After the router receives the request, as shown above, close the link if it is *, or call mux.Handler(r) Returns the processing Handler corresponding to the set route, and executes h.ServeHTTP(w, r) . See one ServeMUX.Handler(*request) Official documents:

Handler returns the handler for the given request, consult r.Method . r.Host and r.URL.Path . It always returns a non-ES90en handler. If the path is not in its canonical form, the handler is a generated handler that is redirected to the canonical path.

Handler also returns the registration pattern that matches the request, or, in the case of an internally generated redirect, the pattern that matches after the redirect.

If there is no registration handler for the request, Handler returns the "Not found Page" handler and empty mode.

To be clear, request returns a handler based on the path of method, host, and the requested URL. This handler is what we said Handler. If we look at the method of the Handler interface, we know that it will end up in sayhelloName. Let's take a look at ServeMux.Handler(*request) The implementation of the:


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
}

In order not to confuse the reader, let's look at the first match method, which is a private method that iterates over map in mux:


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
}

Return the stored handler and call the ServeHTTP interface of handler to execute the function.

Go actually supports the external implementation of the router ListenAndServe the second parameter is used to configure the external router, it is an Handler interface, that is, the external router as long as the implementation of Handler interface can be, we can implement custom routing function in the implementation of the router ServeHTTP.

We realize a simple router:


package main
import (
 "fmt"
 "net/http"
)
type MyMux struct {}
func (p *MyMux) ServeHTTP(w http.ResponseWriter, r *http.Request) {
 if r.URL.Path == "/" {
 sayhelloName(w, r)
 return
 }
 http.NotFound(w, r)
 return
}
func sayhelloName(w http.ResponseWriter, r *http.Request) {
 fmt.Fprintf(w, "Hello myroute!")
}
func main() {
 mux := &MyMux{}
 http.ListenAndServe(":9090", mux)
}

With an analysis of the http package, let's now tease out the entire code execution process:

1. Call first Http.HandleFunc , in order to do a few things:

HandleFunc is called for DefaultServeMux Handle for DefaultServeMux is called To DefaultServeMux HandlerFunc(f)0 To add the corresponding handler and routing rules

2. Call next http.ListenAndServe(“:9090”, nil) , did a few things in order:

Instantiation Server Call ListenAndServe() for Server Call net.Listen (" tcp ", addr) to listen on the port Start an for loop with the Accept request in the body of the loop Instantiate 1 Conn for each request, and open 1 goroutine to service the request go c.serve() Read the contents of each request w, err := c.readRequest() Determine if handler is empty. If handler is not set (handler is not set in this example), handler is set to DefaultServeMux ServeHttp calling handler In this case, let's go to ES168en.ServeHttp Select handler according to request, and enter the ServeHTTP of this handler, mux.handler(r).ServeHTTP(w, r) Choose handler: Determine if any routes satisfy this request (muxEntry for looping through ServerMux) If any route satisfies, call ServeHttp of this route handler If no route is satisfied, call ServeHttp of NotFoundHandler

conclusion


Related articles: