Golang Route matching analysis [1]

Source: Internet
Author: User
Tags string back
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 behaviorThe 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. DefaultServeMuxthe 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.

Related Article

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.