Default Route Matching Rules in Go s http Packets

Posted by gurjit on Wed, 22 Jan 2020 05:27:07 +0100

1. Execution process

First, let's build a simple http server:

package main

import (
	"log"
	"net/http"
)

func main() {
	http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
		w.Write([]byte("hello world"))
	})
	log.Fatal(http.ListenAndServe(":8080", nil))
}

You can see the output using http://127.0.0.1:8080/

By tracing the http.go package code, you can see that the basic execution process is as follows:

  • 1. Create a Listener to listen on port 8080

  • 2. Enter for loop and Accept request, blocking without request

  • 3. Receive the request, create a conn object, and place it in the goroutine (key to high concurrency)

  • 4. Resolve the request source information to get important information such as request path

  • 5. Request ServerHTTP method, ResponseWriter and Request objects have been obtained from the previous step

func (sh serverHandler) ServeHTTP(rw ResponseWriter, req *Request) {
	//This handler is the second parameter in http.ListenAndServe
	handler := sh.srv.Handler 
	if handler == nil {
		//If handler is empty, use internal DefaultServeMux for processing
		handler = DefaultServeMux
	}
	if req.RequestURI == "*" && req.Method == "OPTIONS" {
		handler = globalOptionsHandler{}
	}
	//Start processing http requests here
	//If you need to use a custom mux, you need to implement the ServeHTTP method, which implements the Handler interface.
	handler.ServeHTTP(rw, req)
}
  • 6. The logic to enter DefaultServeMux is to match the path s of requests to find handlers in the map and leave them to the handler to process

More information about the http request processing process can be referenced Go Web Programming 3.3 How Go Makes the Web Work

2. DefaultServeMux Route Matching Rules

First look at several routing rules:

package main

import (
	"log"
	"net/http"
)

func main() {
	//Rule 1
	http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
		w.Write([]byte("hello world"))
	})
	
	//Rule 2
	http.HandleFunc("/path/", func(w http.ResponseWriter, r *http.Request) {
		w.Write([]byte("pattern path: /path/ "))
	})

	//Rule 3
	http.HandleFunc("/path/subpath", func(w http.ResponseWriter, r *http.Request) {
		w.Write([]byte("pattern path: /path/subpath"))
	})

	log.Fatal(http.ListenAndServe(":8080", nil))
}

Scenario one:

Access: http://127.0.0.1:8080/

Return: hello world

Scenario 2:

Access: http://127.0.0.1:8080/path

Return: pattern path: /path/

Scenario three:

Access: http://127.0.0.1:8080/path/subpath/

Return: pattern path: /path/

Scenario four:

Access: http://127.0.0.1:8080/hahaha/

Return: hello world

Let's start with some rules and see how the code works:

1. If a matching path is followed by a /, a matching rule without/suffix is automatically added and jumps to path/, explaining scenario 2 and why the matched/path/

2. Why do I set so many rules? Why can a rule universally match unset routing information without affecting the existing routing? How does it work internally?

2.1 Add Routing Rules

Look at the two struct s first, which store the default routing rules:

type ServeMux struct {
	mu    sync.RWMutex  //Handle concurrency, increase read-write locks
	m     map[string]muxEntry  //Store the rule map, where key is the path set
	hosts bool // Whether any patterns contain host names
}

type muxEntry struct {
	explicit bool //Is it a perfect match
	h        Handler//handler for the corresponding matching rule
	pattern  string//Match Path
}

By tracing http.HandleFunc to the following code, it is adding rules to the two struct s above:

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")
	}
	//panic if already matched
	if mux.m[pattern].explicit {
		panic("http: multiple registrations for " + pattern)
	}
    
    //Add a new match rule
	mux.m[pattern] = muxEntry{explicit: true, h: handler, pattern: pattern}
	
	//Determine if there is a host based on the first letter of path
	if pattern[0] != '/' {
		mux.hosts = true
	}

	//!!Here we can see clearly that scenario two has been achieved and see the criteria for judgment
	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}
	}
}

The annotated behavior of Helpful behavior above, which is the case for scenario 2, is to determine that if the last path matched contains /, and there was no previous rule to add backslash removal, automatically add a 301 jump point to / path/

2.2 Find Routing Rules

The routing rule lookup is to match the lookup from the map in ServeMux, to this handler and execute, but there are some processing mechanisms, such as how to ensure that the access to/path/subpath matches/path/subpath first instead of matching/path/

When a request comes in, the mux.match method is tracked:

Procedure mux.ServerHTTP->mux.Handler->mux.handler->mux.match

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 a rule is matched, the handler is not returned immediately, and it is critical to continue matching and determine if the path length is the longest!!!
		if h == nil || len(k) > n {
			n = len(k)
			h = v.h
			pattern = v.pattern
		}
	}
	return
}

1. This explains why the exact path you set is the best match because it is determined by the length of the path. Of course that explains why / can match all (see the pathMatch function, / is matching all, but it is only in the end that the match succeeds)

2. Get the handler that handles the request, then call h.ServeHTTP(w, r) to execute the corresponding handler method.

Wait, where is the ServeHTTP method in handler?

Because a custom handler handler handler handler function has been forced into a HandlerFunc type when http.HandleFunc is called, you have a ServeHTTP method:

type HandlerFunc func(ResponseWriter, *Request)

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

f(w,r) implements handler execution.

Focus on the Public Number of "School Point Procedure" to learn more about dry goods

Topics: Programming