This is a creation in Article, where the information may have evolved or changed.
Objective
In this article and in the next article, we will study Golang's source code to explore how Golang implements HTTP URL matching, and compares the implementation of the MUX.
I have limited level, if there are omissions and incorrect places, but also please enlighten me, thank you!
Golang source based on 1.9.2
Body
We have such an HTTP server program:
func main() { http.HandleFunc("/bar", func(w http.ResponseWriter, r *http.Request) { fmt.Fprintln(w, "Hello") }) http.HandleFunc("/foo", func(w http.ResponseWriter, r *http.Request) { fmt.Fprintln(w, "World") }) http.ListenAndServe(":8080", nil)}
We launch such a program, and in the browser input http://localhost:8080/bar
, will see the page print out Hello, when we replace the URL http://localhost:8080/foo
, the page will print the world. It is HTTP server that is based on /bar
and /foo
found the appropriate handler to server this request. We follow the Golang source to explore the process of matching.
Registered
Following a few steps into the code, you will find that Golang defines such a structure
type ServeMux struct { mu sync.RWMutex m map[string]muxEntry hosts bool // whether any patterns contain hostnames}
And that muxEntry
's what defines it.
type muxEntry struct { explicit bool h Handler pattern string}
See here, we can roughly guess m
that this structure is the key to URL matching. It takes the URL path as key, and contains the corresponding handler muxEntry
as value. Thus, when an HTTP request is received, the URL Path is parsed out, as long as the m
corresponding handler can be found in the server this request. Let's take a look at the handler registration process
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 re Direct 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 are 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}}}
Helpful behavior
The preceding code is obvious, and if this pattern is not registered, handler will be registered to this pattern. and the Helpful behavior
following code will do something like this: if I register for /bar/
such a pattern,mux will default to help me register /bar
this pattern, and /bar
the handler will be /bar
redirect to the request /bar/
. Let's modify our main function:
func main() { http.HandleFunc("/bar/", func(w http.ResponseWriter, r *http.Request) { fmt.Fprintln(w, "Hello") }) http.HandleFunc("/foo", func(w http.ResponseWriter, r *http.Request) { fmt.Fprintln(w, "World") }) http.ListenAndServe(":8080", nil)}
When we enter in the browser http://localhost:8080/bar
, we will see that the URL of the browser becomes http://localhost:8080/bar/
and the page prints out Hello
. In fact, this is a two HTTP request:
Request URL: http://127.0.0.1:8080/barRequest Method: GETStatus Code: 301 Moved PermanentlyRemote Address: 127.0.0.1:8080
Request URL: http://localhost:8080/bar/Request Method: GETStatus Code: 200 OK (from disk cache)Remote Address: [::1]:8080
This is exactly what the server /bar
did to redirect the request.
Registering a handler to a pattern looks relatively simple, so how does Golang HTTP Server serve an HTTP request?
The
We all know that the HTTP protocol is based on TCP, so let's start by looking at a TCP echo server
func main() { fmt.Println("Launching server...") // listen on all interfaces ln, _ := net.Listen("tcp", ":8081") for { // accept connection on port conn, _ := ln.Accept() // will listen for message to process ending in newline (\n) message, _ := bufio.NewReader(conn).ReadString('\n') // output message received fmt.Print("Message Received:", string(message)) // sample process for string received newmessage := strings.ToUpper(message) // send new string back to client conn.Write([]byte(newmessage + "\n")) }}
Golang inside the net.Listen
package socket()
and bind()
the process, get one listener
after, by calling Accept()
function blocking wait for a new connection, each time Accept()
the function returns, you will get a TCP connection.
The HTTP service in Golang also does this:
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: = Conte Xt. 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.s Econd; 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.ne Wconn (rw) c.setstate (C.RWC, statenew)//Before Serve can return go C.serve (CTX)}}
From this you can see that for each HTTP request, the server will have a goroutine to serve.
followed by the source of the trace all the way down, found to call a function:
// parseRequestLine parses "GET /foo HTTP/1.1" into its three parts.func parseRequestLine(line string) (method, requestURI, proto string, ok bool) { s1 := strings.Index(line, " ") s2 := strings.Index(line[s1+1:], " ") if s1 < 0 || s2 < 0 { return } s2 += s1 + 1 return line[:s1], line[s1+1 : s2], line[s2+1:], true}
HTTP protocol parsing of the content sent by the connection, and the HTTP method and URI are obtained. We skip the other parts of protocol parsing and validation and look directly at the function of serve request:
serverHandler{c.server}.ServeHTTP(w, w.req)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)}
We see that when handler
it is nil
time, the package's default handler will be used DefaultServeMux
. And back to our Main.go:
http.ListenAndServe(":8080", nil)
When we listen to the service, the incoming handler is really nil
, so used DefaultServeMux
, and when we call http.HandleFunc
it, it is to DefaultServeMux
register the pattern and the corresponding handler. DefaultServeMux
the ServeHTTP
method is as follows:
// 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)}
mux.Handler(r)
method to find the corresponding handler by request:
Handler is the main implementation of handler.//, the path was known to being in canonical form, and 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}//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}//Does path match pattern?func pathmatch (pattern, path string) bool { If Len (pattern) = = 0 {//should not happen return false} N: = Len (pattern) if pattern[n-1]! = '/' {return pattern = = path} return len (path) >= n && path[0:n] = = Pattern}
The match
exact match is checked first in the function, and if it matches, the corresponding handler is returned directly. If there is no match, traverse all registered paths to check for the pathMatch
pathMatch
longest path that satisfies the wins. For example, the main
function is as follows:
func main() { http.HandleFunc("/bar/", func(w http.ResponseWriter, r *http.Request) { fmt.Fprintln(w, "Hello") }) http.HandleFunc("/bar/bbb/", func(w http.ResponseWriter, r *http.Request) { fmt.Fprintln(w, "bbb") }) http.HandleFunc("/foo", func(w http.ResponseWriter, r *http.Request) { fmt.Fprintln(w, "World") }) http.ListenAndServe(":8080", nil)}
At this point in the browser input, http://localhost:8080/foo/aaa
will return 404 page not found
, and input http://localhost:8080/bar/aaa
, will be returned Hello
. Input http://localhost:8080/bar/bbb/ccc
, /bar/
and /bar/bbb/
will be matched to, but /bar/bbb/
this pattern longer, the browser will print outbbb
Summarize
At this point, we analyze the route matching process of Golang, the registration process to register the pattern and the corresponding handler to a map
, matching the first check whether the pattern and path exactly match, if not, then check the longest match.
The entire process looks simple, straightforward, but does not support regular route matching.
in the next article, we will analyze the source code of the MUX and learn its route matching method.