Author:Wzy_CC

The full text consists of 10000 words

Read for 10 to 15 minutes

preface

Objectives of this paper:

The analysis of a series of actions from route registration to request route after listening on local port is basically limited to the interpretation of the route-related parts of the package file net/ HTTP server.go

Writing Purpose:

When using native libraries for Web development, Many beginners can easily be mux. Handle ()/mux HandleFunc ()/mux. The Handler ()/Handlerfunc/Handler/Handle ()/HandleFunc ()/Handler to buffalo, itself a few names are similar, Sometimes it’s uppercase, sometimes it’s lowercase, sometimes it’s handle, sometimes it’s handler, it looks similar but it has a completely different type and function. Because the names are similar and confusing, the true meaning is not easy to figure out, nor is it easy for developers to remember. Some names don’t even tell you what the function is for, and some are legacy issues from library design that make it more difficult to understand HTTP libraries.

A lot of online tutorials just talk about what something is and what it’s for, not why it’s the way it is, why it’s designed the way it is, or what the benefits of it are. More importantly, some of the tutorials are already old, it’s been two years since 2018, and many of the functions have been optimized and rewritten, at least in Server. go. But some things are constant, and many design ideas are common, and these are the ideas that beginners should master. In fact, memorizing the four writing methods of Handle by rote is of no help to development. If you do not have an in-depth understanding of it, you will often turn over the documents and feel confused.

Some of Go’s design philosophies are very interesting and can’t be explained in tens of thousands of words.

Read in order:

It would be helpful to read this article in order

directory

[TOC]

An overview of the

Website sample

In the example on the official website, building a stable, highly concurrent Web server using GO takes only a few lines:

http.Handle("/foo", fooHandler)

http.HandleFunc("/bar".func(w http.ResponseWriter, r *http.Request) {
	fmt.Fprintf(w, "Hello, %q", html.EscapeString(r.URL.Path))
})

log.Fatal(http.ListenAndServe(": 8080".nil))
Copy the code

Of course, these lines of code are sufficient for most development in production environments, but if you don’t understand some of the underlying principles, you need to dig deeper.

A go server runs normally through the following steps: registering functions, listening on ports, accepting requests, processing requests, providing services, and closing links

0. Registration function: Registers the routing rule in the routing table

1. Listening port: Create a Listen socket and listen in a loop

2. Accept the request: Accept the request, create a network link conn object, and turn on a coroutine to handle the link. For each new link served, serveHTTP is called in conn.connect() to handle the request

3. Request processing: Construct a Request object by reading Request parameters and search for the corresponding Handler in the Map routing table according to the Request path. The request is then assigned to the handler

4. Service provision: The processing function will process the requested parameters and other information and return different information

5. Close the link: The application layer closes the link after processing the request

Front knowledge

Go basic syntax, Web basics, * compressed dictionary tree

Source code analysis scope/outline

Go files in the NET/HTTP library are analyzed, but space is limited and emphasis is given (simplified use of mux.xx () instead of servemux.xx ()) :

1.ServeMux constructs and their methods: mux.newServemux (), mux.handle (), mux.handlefunc (), mux.handler() /mux.handler(), mux.servehttp ()

2.HandlerFunc structure and its implementation method: handlerfunc.servehttp ()

3.Handler Indicates the interface type

4. Handle() and HandleFunc()

That’s it for the routing part

ServeMux

A ServeMux is a structure

ServeMux definition

ServeMux is an HTTP request multiplexer. It matches the URL of each incoming request against a list of registration patterns (routing table) and invokes a handler for the pattern that best matches the URL.

type ServeMux struct {
    // contains filtered or unexported fields
}
Copy the code

The black box inside the structure contains filtered and unexported fields. It does not want you to know the structure inside. The actual structure is as follows:

type ServeMux struct {
    mu    sync.RWMutex          // Read/write mutex
    m     map[string]muxEntry   / / the routing table
    es    []muxEntry            // Ordered array, sorted from longest to shortest
    hosts bool                  // whether any patterns contain hostnames
}
Copy the code

The ServeMux structure is essentially made up of an MU read/write mutex, an M routing table, an ES array (many older tutorials don’t have this update field), and a hosts Boolean value

Among them:

1. Mu is a read/write mutex. For details, see the design concept

2. M is the routing table, which is essentially a map[String]muxEntry variable. The key is the path string (method and incoming parameter concatenation string), and the value is the corresponding processing structure muxEntry

3. Es is an ordered array. It maintains all the route addresses with suffix/from long to short

4. The Hosts attribute of the Boolean type marks whether the route contains the host name. If the hosts value is true, the route cannot start with a slash (/)

The structure of muxEntry in m routing table is as follows:

type muxEntry struct {
    h        Handler // Handler
    pattern  string  // Routing path
}
Copy the code

MuxEntry is essentially a string of Handler classes and routing paths, where:

1. H is the Handler type, which requires the implementation of ServeHTTP methods in interfaces

2. The pattern is the same as the key in the routing table

Default multiplexer

The default multiplexer is specified in the NET/HTTP package, and the default MUX is used if you do not specify it by hand:

// DefaultServeMux is the default ServeMux used by Serve.
var DefaultServeMux = &defaultServeMux

var defaultServeMux ServeMux
Copy the code

Why is it ok to use variables before declaration? Dave Cheney told me that pack-level variables are independent of the declaration order, and told me to go to Slack later and ask yourself: Sweat_smile: the compiler initializes pack-level variables first, so the declaration can be used wherever it is.

ServeMux method

Public methods

mux.NewServeMux()

// NewServeMux allocates and returns a new ServeMux.
func NewServeMux(a) *ServeMux { return new(ServeMux) }
Copy the code

Create and return a ServeMux structure to allocate space for fields in the structure. Note that the structure field hosts that is initialized and returned defaults to false

mux.Handler()

For a given request, mux.handler () always returns a non-empty Handler to use. If the method is CONNECT, see the private method mux.redirecttopathslash ().

Mux.handler () calls the private method mux.handler(), and inside mux.handler() calls the mux.match() method to return a Handler that matches the pattern

func (mux *ServeMux) Handler(r *Request) (h Handler, pattern string) {

	// CONNECT requests are not canonicalized.
	if r.Method == "CONNECT" {
		// If r.URL.Path is /tree and its handler is not registered,
		// the /tree -> /tree/ redirect applies to CONNECT requests
		// but the path canonicalization does not.
		if u, ok := mux.redirectToPathSlash(r.URL.Host, r.URL.Path, r.URL); ok {
			return RedirectHandler(u.String(), StatusMovedPermanently), u.Path
		}

		return mux.handler(r.Host, r.URL.Path)
	}

	// All other requests have any port stripped and path cleaned
	// before passing to mux.handler.
	host := stripHostPort(r.Host)
	path := cleanPath(r.URL.Path)

	// If the given path is /tree and its handler is not registered,
	// redirect for /tree/.
	if u, ok := mux.redirectToPathSlash(host, path, r.URL); ok {
		return RedirectHandler(u.String(), StatusMovedPermanently), u.Path
	}

	ifpath ! = r.URL.Path { _, pattern = mux.handler(host, path) url := *r.URL url.Path = pathreturn RedirectHandler(url.String(), StatusMovedPermanently), pattern
	}

	return mux.handler(host, r.URL.Path)
}
Copy the code

For r.host and r.ul.path, a brief description of what the two functions cleanPath() and stripHostPort() do:

cleanPath()

1. Handle invalid routes

2. For slash processing, replace invalid multiple slashes

3. Remove all. Replace it with the equivalent path

In simple terms, the path is treated as the equivalent shortest path, so that the corresponding key-value pair can be found in the subsequent process of searching the routing table.

// cleanPath returns the canonical path for p, eliminating . and .. elements.
func cleanPath(p string) string {
	if p == "" {
		return "/"
	}
	if p[0] != '/' {
		p = "/" + p
	}
	np := path.Clean(p)
	// path.Clean removes trailing slash except for root;
	// put the trailing slash back if necessary.
	if p[len(p)- 1] = ='/'&& np ! ="/" {
		// Fast path for common case of p being the string we want:
		if len(p) == len(np)+1 && strings.HasPrefix(p, np) {
			np = p
		} else {
			np += "/"}}return np
}
Copy the code

stripHostPort()

That’s the specification for the host format

// stripHostPort returns h without any trailing ":<port>".
func stripHostPort(h string) string {
	// If no port on host, return unchanged
	if strings.IndexByte(h, ':') = =- 1 {
		return h
	}
	host, _, err := net.SplitHostPort(h)
	iferr ! =nil {
		return h // on error, return unchanged
	}
	return host
}
Copy the code

mux.ServeHTTP()

Mux.servehttp () makes a request to the handler that best matches the requested URL, and finally calls handler.servehttp () of the handler type that implements ServeHTTP() to process the request

A bit convoluted, but HTTP requests are first handled by mux.servehttp (), Inside this function, mux.handler() (see the private method mux.handler()) is called to select the Handler Handler(in the process, mux.handler()/ mux.redirecthandler () is called to find the routing table, Finally, mux.match() is called inside mux.handler() to finally match the route. The lookup returns h of type handler.)

// 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)
}
Copy the code

mux.Handle()

During the route registration/route addition phase, the registration function mux.handle () is responsible for registering handlers and paths into the routing table, essentially a table writing 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")}if handler == nil {
		panic("http: nil handler")}if _, exist := mux.m[pattern]; exist {
		panic("http: multiple registrations for " + pattern)
	}

	if mux.m == nil {
		mux.m = make(map[string]muxEntry)
	}
	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}}Copy the code

For routes that repeat in the table, panic occurs, and for paths with slash/, write to mux.es by length, which was mentioned earlier when analyzing the structure mux.

Take a quick look at the sort function it implements:

func appendSorted(es []muxEntry, e muxEntry) []muxEntry {
	n := len(es)
	i := sort.Search(n, func(i int) bool {
		return len(es[i].pattern) < len(e.pattern)
	})
	if i == n {
		return append(es, e)
	}
	// we now know that i points at where we want to insert
	es = append(es, muxEntry{}) // try to grow the slice in place, any entry works.
	copy(es[i+1:], es[i:])      // Move shorter entries down
	es[i] = e
	return es
}
Copy the code

mux.HandleFunc()

Mux.handlefunc () is the most important method in my opinion, and also registers the handler to the routing table. We should compare mux.handlefunc () with mux.handle (). Mux.handlefunc () is a rewrap of the mux.handle () function. It calls HandlerFunc(), an adapter function that essentially uses an ordinary function as a syntax sugar for HTTP request handlers. We no longer need to implement ServeHTTP(). Instead, we can route the ResponseWriter (*Request) function as long as it is of func(*Request) type, basically in one line of code. This is why it is so easy to build a simple Web application in the official website example. In the website of the example HandleFunc () function calls the default multiplexer DefaultServeMux. HandleFunc () method, developers only need to define common function:

// HandleFunc registers the handler function for the given pattern.
func (mux *ServeMux) HandleFunc(pattern string, handler func(ResponseWriter, *Request)) {
	if handler == nil {
		panic("http: nil handler")
	}
	mux.Handle(pattern, HandlerFunc(handler))
}
Copy the code

Private methods

mux.match()

When a call to mux.handler() returns the Handler class, the mux.match() function is called inside mux.handler(), which is essentially a route lookup process (Handle() is a route registration process).

// 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. mux.es contains all patterns
	// that end in / sorted from longest to shortest.
	for _, e := range mux.es {
		if strings.HasPrefix(path, e.pattern) {
			return e.h, e.pattern
		}
	}
	return nil.""
}
Copy the code

In the process of matching:

1. Perform an exact match in the routing table and return a muxEntry match

2. If it is not found in the routing table, it is matched in the ordered array ES. As can be seen from strings.hasprefix (), this is a fuzzy match in nature, and only the corresponding prefix is matched, the match is considered successful

3. If the corresponding prefix cannot be queried, the match fails and the nil handler is returned

The one-sentence description of the matching rules is: Longer patterns take precedence over shorter ones, and Longer strings have a higher priority than shorter ones

mux.shouldRedirectRLocked()

Mux. ShouldRedirectRLocked () method is relatively simple, the effect of judgment about whether to need to “/ tree/” this routing redirection (in ServeMux for”/tree “will be automatically redirected to the”/tree /, “unless”/tree “is already available in the routing table, This process is accomplished by calling mux.redirecttopathslash () in mux.handler ())

1. Check whether host+ Path or a combination of path exists in the routing table. If yes, no redirection is required

2. If path is an empty string, no redirection is required

3. If path+ / exists in the routing table, you need to redirect the route. For example, if /tree/ is registered in the routing table during registration, routes from /tree are redirected to /tree/.

func (mux *ServeMux) shouldRedirectRLocked(host, path string) bool {
	p := []string{path, host + path}

	for _, c := range p {
		if _, exist := mux.m[c]; exist {
			return false
		}
	}

	n := len(path)
	if n == 0 {
		return false
	}
	for _, c := range p {
		if _, exist := mux.m[c+"/"]; exist {
			return path[n- 1] != '/'}}return false
}
Copy the code

mux.redirectToPathSlash()

The mux.redirecttopathslash () function determines whether a “/” is required after its path and returns the new URL and the redirected handler once it has determined that a “/” is required:

func (mux *ServeMux) redirectToPathSlash(host, path string, u *url.URL) (*url.URL, bool) {
	mux.mu.RLock()
	shouldRedirect := mux.shouldRedirectRLocked(host, path)
	mux.mu.RUnlock()
	if! shouldRedirect {return u, false
	}
	path = path + "/"
	u = &url.URL{Path: path, RawQuery: u.RawQuery}
	return u, true
}
Copy the code

After deciding that it should be redirected, return to the path ending with a slash

In mux.handler (), if http. Method is CONNECT, a RedirectHandler (also a type of Handler) is returned and written to StatusMovedPermanently (as defined in status.go), Call RedirectHandler. ServeHTTP () to handle the HTTP request

	if u, ok := mux.redirectToPathSlash(r.URL.Host, r.URL.Path, r.URL); ok {
			return RedirectHandler(u.String(), StatusMovedPermanently), u.Path
		}

		return mux.handler(r.Host, r.URL.Path)
Copy the code

mux.handler()

The mux.handler() function is an implementation of mux.handler()

// handler is the main implementation of Handler.
// The path is known to be in canonical form, 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
}
Copy the code

Returns NotFoundHandler() after neither match is found

conclusion

The second parameter of ListenAndServe is used to configure the external router. It is a Handler interface, that is, the external router can implement the Handler interface. We can implement custom routing in ServeHTTP of our own router

HandleFunc()

HandleFunc()Is the function

HandleFunc definition

For a given pattern string, HandleFunc registers the handler function to the appropriate route. In other words, HandleFunc can bind the processing logic to the URL when different URL paths are requested.

Function definition:

func HandleFunc(pattern string, handler func(ResponseWriter, *Request)) {
	DefaultServeMux.HandleFunc(pattern, handler)
}
Copy the code

The first argument is a string, and the second argument is handler. HandleFunc handles the matching URL path request.

HandleFunc() essentially calls the default multiplexer’s mux.handlefunc ()

Example:

package main

import (
	"io"
	"log"
	"net/http"
)

func main(a) {
	h1 := func(w http.ResponseWriter, _ *http.Request) {
		io.WriteString(w, "Hello from a HandleFunc #1! \n")
	}
	h2 := func(w http.ResponseWriter, _ *http.Request) {
		io.WriteString(w, "Hello from a HandleFunc #2! \n")
	}

	http.HandleFunc("/", h1)
	http.HandleFunc("/endpoint", h2)

	log.Fatal(http.ListenAndServe(": 8080".nil))}Copy the code

HandleFunc advantage

The HandleFunc function allows us to use a ResponseWriter (*Request) function directly as a handler, without the need to implement the handler interface and a custom ServeHTTP function type. HandleFunc makes it very easy to register a path for a URL.

Handle()

Handle()Is the function

The Handle () definition

Like HandleFunc, it essentially calls the mux.handle () method of the default MUx

Function definition:

// Handle registers the handler for the given pattern
// in the DefaultServeMux.
// The documentation for ServeMux explains how patterns are matched.
func Handle(pattern string, handler Handler) { DefaultServeMux.Handle(pattern, handler) }
Copy the code

The first argument is a string to be matched, and the second argument is a Handler of type Handler. Unlike the Handler in the example above, you need to implement a new class and implement the ServeHTTP method in that class

Example:

package main

import (
	"fmt"
	"log"
	"net/http"
	"sync"
)

type countHandler struct {
	mu sync.Mutex // guards n
	n  int
}

func (h *countHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	h.mu.Lock()
	defer h.mu.Unlock()
	h.n++
	fmt.Fprintf(w, "count is %d\n", h.n)
}

func main(a) {
	http.Handle("/count".new(countHandler))
	log.Fatal(http.ListenAndServe(": 8080".nil))}Copy the code

Handler

Handler is the interface

Handler definition

In the official website document of Go Package, the Handler is defined as “Handler responds to HTTP requests”, which means “Handler” in Chinese.

Handler is an interface defined in the NET/HTTP native package:

type Handler interface {
    ServeHTTP(ResponseWriter, *Request)
}
Copy the code

Anything that implements a ServeHTTP method belongs to Handler.

Nature of the Handler

1.Handler can only be used to read the body of a request, but cannot modify the request

2. Read the body first and then write the RESp

3. In fact, we don’t use Handler very often in real development because NET/HTTP gives us a more convenient HandleFunc() function that lets us directly use a function as a Handler, Handler is a function type instead of the Handle interface, which is more convenient than implementing ServeHTTP().

Handler is used to process the request and give the response, or, more specifically, to read the body of the request, write the corresponding response field (respones header) to ResponseWriter, and then return

HandlerFunc

HTTP also provides the HandlerFunc class, which is only one word different from the HandleFunc() function

HandlerFunc is essentially an adapter function that internally implements the ServeHTTP() function, so this class is essentially a Handler type

HandlerFunc () definition

HandlerFunc(f) is a handler that calls the f function

Function prototype:

type HandlerFunc func(ResponseWriter, *Request)
Copy the code

The source code

// Call the HandleFunc method of the default ServerMux
func HandleFunc(pattern string, handler func(ResponseWriter, *Request)) {                                          
        DefaultServeMux.HandleFunc(pattern, handler)
}
// Convert the method handler to HandlerFunc, that is, implement the handler interface; Then execute the Handle method
func (mux *ServeMux) HandleFunc(pattern string, handler func(ResponseWriter, *Request)) {
        mux.Handle(pattern, HandlerFunc(handler))
}

// The router registers a handler to the specified parttern
func (mux *ServeMux) Handle(pattern string, handler Handler){... }Copy the code

Executing HandleFunc() simply registers the handler for a rule’s request.

Handler/HandlerFunc difference

Handler is an interface

HandlerFunc is of type Handler and is an adapter function

The Handle ()/HandleFunc () difference

Both HandleFunc() and Handle() call the corresponding methods of DefaultServeMux, which is an instance of ServeMux that implements the Handler interface. In fact, HandleFunc() will eventually call Handle, as mentioned earlier:

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

Route Registration Process

The route registration process is implemented by two methods within the MUX structure: mux.handle () and mux.handlefunc ()

mux.Handle()

The route registration process is mainly implemented by the Handle() method in the ServeMux structure. If the route already exists in the routing table, Panic will occur

The Handle() method in the ServeMux structure does roughly this:

1. Check the validity of the route. If the route does not comply with the rule, the multiple registrations for/XXX message is displayed

2. If no routing table exists in the multiplexer, create a routing table

3. If the route is valid, create muxEntry and add it to the routing table

4. If the last character of the route has a “/”, the custom sort function appendSorted() locates the index, inserts it, and sorts it (although I am confused about the efficiency of this function) and returns the sorted array.

5. If the route does not start with a slash (/), it contains the host name

mux.HandleFunc()

func (mux *ServeMux) HandleFunc(pattern string, handler func(ResponseWriter, *Request)) {
	if handler == nil {
		panic("http: nil handler")
	}
	mux.Handle(pattern, HandlerFunc(handler))
}
Copy the code

Helpful behavior

In previous versions of server.go, the registration function mux.Handle had some helper behavior. When you set the routing path to /tree/, the Helpful Behavior implicitly and permanently registered /tree to the registry. On /tree/, the /tree handler automatically redirects the request to /tree/ :

Status code: 301 / Moved PermanentlyCopy the code

In today’s server.go, the ServeMux structure maintains an es array, which records the string from long to short and ends with a ‘/’

When using mux.match() to match the route path (see “Route Lookup Procedure” for details), the routing table is searched first. If the route does not exist in the routing table, the es array is iterated to match its maximum length:

// 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. mux.es contains all patterns
	// that end in / sorted from longest to shortest.
	for _, e := range mux.es {
		if strings.HasPrefix(path, e.pattern) {
			return e.h, e.pattern
		}
	}
	return nil.""
}
Copy the code

Service request process

We can use golang’s native net/ HTTP library to implement a simple example of web routing:

// serve.go
package main

import (
    "fmt"
    "net/http"
)

func main(a) {
    http.HandleFunc("/", HelloWorld)
    http.ListenAndServe(": 8080".nil)}func HelloWorld(w http.ResponseWriter, r *http.Request) {
    fmt.Fprint(w, "Hello World")}Copy the code

ListenAndServe(); ListenAndServe(); HandleFunc();

  1. Registration routing process: called firstHandleFunc(): The default multiplexer was calledDefaultServeMuxtheHandleFunc()And call theDefaultServeMuxtheHandleTo DefaultServeMuxmap[string]muxEntryTo add handlers and routing rules.
  2. Instantiate the server and callserver.ListenAndServe(), the callnet.Listen("tcp", addr)Listening port. Start a for loop and Accept the request in the body of the loop
  3. Instantiate a Conn for each request and turn on a goroutine to service the request go c.sever ()
  4. Read each request w, err := c.readRequest()
  5. Go to ServeHandler. ServeHTTP uses its own MUX if it has its own implementation. Determines if the handler is empty, and if no handler is set (nil handler in this example), the handler is set to DefaultServeMux
  6. Call handler ServeHttp, into the DefaultServeMux. ServeHttp
  7. Select a match handler based on the request and enter the ServeHTTP of this handler

For each HTTP request, the server writes a coroutine to service it, which is beyond the scope of this article. For the official website example:

http.ListenAndServe(": 8080".nil)
Copy the code

If handler is passed in nil, the default multiplexer DefaultServeMux is used, and when HandleFunc is called, the handler is registered with the default multiplexer

// 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)
}
Copy the code

Since this article is too short, it’s a good opportunity to re-examine the server.serve () function in server.go

Route Lookup procedure

How to ensure that ‘/path/subpath’ is accessed first match ‘/path/subpath’ and not match ‘/path/’ is because of the lookup rules in route lookup (also mentioned earlier) :

mux.ServerHTTP() -> mux.Handler() -> mux.handler() -> mux.match()

Call h.server HTTP(w, r) to execute the corresponding handler method:

type HandlerFunc func(ResponseWriter, *Request)

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

F (w,r) implements handler execution

Design idea

Mu read/write lock: Request design concurrency, exactly where concurrency is used?

No time to write

Does hasPrefix() match exactly?

Yes, but there’s no need. Because fuzzy routes need to be matched, if there is only routing table M on the implementation, it will be extremely unfriendly to the person accessing the URL and can not achieve effective auxiliary prompt

Why maintain a separate array es with the suffix /?

1. As above, for fuzzy matching

2. Most importantly, insertion sort is easy, although the time complexity is less optimistic

conclusion

This article is only a small part of the HTTP native library for understanding, for production is not particularly helpful, but master some of the native library design ideas and design patterns for understanding other frameworks is certainly very helpful, after all, the authors of the native library are real gods.

Many programmers only focus on the framework of learning and do not pay attention to this kind of foundation, and training classes and what is the difference? It’s important to really understand the native library, after all, third parties have borrowed these design philosophies.

future

one

Because native library comes with multiple requests by default router function is limited, which has given rise to a lot of routing framework, such as relatively famous httprouter, the framework of this article is detailed explained the httprouter framework, interested can look at and what’s the difference between a native library, in the future if you have time, will also be updated to the framework of learning.

Some of the existing Web routing frameworks have been compared, and this article is a good preview:

Performance comparison of hypercomplete Go Http routing frameworks

two

I would like to systematically explain the intelligent crawler and crawler framework for a long time, because crawler and HTTP this library is very big. Basically STAR more framework I have more or less used, part of the source code has also read, of course, because of the complexity of the framework itself, hand tear source code will be more difficult and time-consuming. Before also briefly introduced colly and other commonly used crawler framework, the future will update more source code interpretation of crawler framework. The technical content of crawler itself is not high, but it is much higher to understand why and where this framework is good. After all, it is necessary to compare with other crawler frameworks to know their advantages and disadvantages.

However, depending on the level of each developer, these frameworks have more or less problems, and they are not as “all-powerful” as the Github homepage makes them sound, so there is a big difference between mastering and using at this lower level, and most people just use them without mastering them.

Of course, if you’re familiar with the HTTP library, you’ll find that using native functions to write crawlers is pretty smooth.

I insist that the crawler’s use of frames is a final compromise.

three

Some of Go’s design philosophies are very interesting and can’t be explained in tens of thousands of words

I don’t want to label myself as a Go evangelist or get involved in a language crusade, Gopher is supposed to be fun Gopher:

Gopher is moe, but confusing

Refer to the link

Go Web: Handler

Package http

ServeMux parsing in Golang

HTTP. ServeMux parsing

Golang Web route implementation method collation summary

GOLANG HTTP packet default route matching rule Read Notes

Relationship between Handle and HandleFunc and handler and HandlerFunc

How does Golang ServeMux implement multiplexing

Analysis of Golang Route Matching [1]