preface

Golang HTTP server implementation principle, this article will be described through the following two points, I hope to help you!

  • 1. How to create an HTTP server
  • 2. Implementation principle of HTTP server

How do I create an HTTP server

Steps to create an HTTP server:

  • 1. Create a processor (actual business logic)
  • 2. Create a router and bind/register it with the processor (so that the URL matches the corresponding function)
  • 3. Start the HTTP server and listen to the specified port

There are really only two ways to implement a processor, either by using processor functions or by creating a structure and implementing ServeHTTP methods.

As for the binding method between processor and router, one is to directly bind processor through HandleFunc method (as shown in notation 1). One is to change direction binding through the Handle method (as written in method 2).

Implemented using processor functions

// Use the handler function to implement the HTTP server.
func index(w http.ResponseWriter, r *http.Request) {
	_, err := w.Write([]byte("Hello World"))
	iferr ! =nil {
		fmt.Println("err", err)
	}
}

func httpServerByFunc(a) {
	http.HandleFunc("/", index)
	err := http.ListenAndServe(": 5000".nil)
	iferr ! =nil {
		panic(err)
	}
}


// Use the handler function to implement the HTTP server.
func httpServerByFunc2(a)  {
	http.Handle("/", http.HandlerFunc(index))
	err := http.ListenAndServe(": 5000".nil)
	iferr ! =nil {
		panic(err)
	}
}

// Use the handler function to implement the HTTP server.
func httpServerByFunc3(a) {
	// Anonymous function
	http.HandleFunc("/".func(w http.ResponseWriter, r *http.Request) {
		_, err := w.Write([]byte("Hello World"))
		iferr ! =nil {
			fmt.Println("err", err)
		}
	})
	err := http.ListenAndServe(": 5000".nil)
	iferr ! =nil {
		panic(err)
	}
}

Copy the code

Implemented using processor constructs

type MyHandler struct{}// Implement ServeHTTP methods
func (my *MyHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	_, err := w.Write([]byte("Hello World"))
	iferr ! =nil {
		fmt.Println("err", err)
	}
}

func httpServerByStruct(a)  {
	mux := http.NewServeMux()
	myHandler := MyHandler{}
	mux.Handle("/", &myHandler)

	// Note that mux is also a Handler.
	err := http.ListenAndServe(": 5000", mux)
	iferr ! =nil {
		panic(err)
	}
}

Copy the code

Note: the above example does not matter if you do not understand it at first, you can first look at the following principle, and then come back to see, you will understand.

Implementation principle of HTTP server

The HTTP server implementation is essentially a process of creating a router and binding the router to the processor. (An issue that I personally feel is core)

The implementation of the Golang HTTP server is not very complicated, but one of the tricky things is that the function names (or function signatures) are very close, so it can be confusing at first. Typical examples are Handler, Handle, HandlerFunc, and HandleFunc. The difference between these four will be described in the following article, pay attention to the source code when do not read the wrong.

How to Create routes

There are two ways to create a route. The first is to use the default route, DefaultServeMux, and the second is to use the NewServeMux method to create a route. There is no essential difference between the two methods, except that Golang always writes a route structure and provides the default route. And the method of customizing routes.

HTTP routers are defined as follows:

// ServeMux is an HTTP request multiplexer.
// It matches the URL of each incoming request against a list of registered
// patterns and calls the handler for the pattern that
// most closely matches the URL.
type ServeMux struct {
	mu    sync.RWMutex
	m     map[string]muxEntry
	es    []muxEntry // slice of entries sorted from longest to shortest.
	hosts bool       // whether any patterns contain hostnames
}

type muxEntry struct {
	h       Handler
	pattern string
}

Copy the code

A ServeMux is an HTTP request multiplexer, also known as a router, that matches the requested URL with the registered URL and sends the match to a Handler.

Binding of a router to a processor

Handler and Handle are used to bind a router to a processor.

First, semantically, Handler is a noun that represents a processor. “Handle” is a verb that stands for processing. Corresponding HandlerFunc and HandleFunc.

Handler and Handle are different from each other.

// A Handler responds to an HTTP request.
// Handler, the Handler, receives the request and responds
type Handler interface {
	ServeHTTP(ResponseWriter, *Request)
}

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

// The HandlerFunc type is an adapter to allow the use of
// ordinary functions as HTTP handlers. If f is a function
// with the appropriate signature, HandlerFunc(f) is a
// Handler that calls f.
type HandlerFunc func(ResponseWriter, *Request)

// HandleFunc registers the handler function for the given pattern
// in the DefaultServeMux.
// The documentation for ServeMux explains how patterns are matched.
func HandleFunc(pattern string, handler func(ResponseWriter, *Request)) {
	DefaultServeMux.HandleFunc(pattern, handler)
}
Copy the code

Handler interface: Technically, it is an interface and contains ServeHTTP methods. Logically, however, it is a processor that receives HTTP requests and responds to them, and it can be considered a processor as long as any structure implements the ServeHTTP method.

2.HandlerFunc type: As you can see from the source code comments, this is an adapter that converts an ordinary function into a processor. Why is it designed this way? Because the previous way of creating a processor was cumbersome and not concise enough, you needed to create a structure and implement the ServeHTTP method.

This is why our common processors look like this:

func index(w http.ResponseWriter, r *http.Request){}Copy the code

So how does HandlerFunc turn a normal function into a handler?

The answer is in the source code below, HandlerFunc is a type, but it feels a bit structural in this case, and it implements ServeHTTP methods, which means HandlerFunc is also a Handler, which is why a normal function can be used as a processor.

type HandlerFunc func(ResponseWriter, *Request)

// ServeHTTP calls f(w, r).
// Call our actual handler function
func (f HandlerFunc) ServeHTTP(w ResponseWriter, r *Request) {
	f(w, r)
}

Copy the code

3.Handle and HandleFunc: The functions of these two methods are the same, they both register the processor with the router. The main difference between them is that HandleFunc encapsulates another layer on top of Handle, and that’s all (check the source code).

A quick summary of the above three points is that ServeHTTP and HandlerFunc generate a Handler, while Handle and HandleFunc bind the generated Handler to the router, or register it.

The final conclusions are as follows: 1. The implementation of HTTP server involves router, processor, and the mapping relationship between them 2. A router uses the default route (DefaultServeMux) and creates a route using the NewServeMux method. There is no significant difference between the two. In the example above, index and login are processor 4. From the source code, Handler is an interface that contains ServeHTTP methods, which means that as long as a structure implements ServeHTTP, it can act as a processor. 5. Since implementing ServeHTTP to become a processor is redundant and not concise, there is the HandlerFunc type, which converts an ordinary function into a processor 6. It is worth noting that ServeMux itself implements the ServeHTTP method, which is also a processor, but only to forward requests to other processors.

Finally, about the HTTP server implementation principle, the actual knowledge involved far more than the above much, this article only focuses on the creation of the router, the processor and the binding of the two, I hope to help you!

supplement

The ServeMux ServeHTTP

This is just to prove that ServeMux also implements the ServeHTTP method

// 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

HTTP route closure problem

The following is an example:

func routerClosure(a)  {
	// When the method name /URL is obtained dynamically
	methods := []string{"index"."login"."logout"}

	for _, m := range methods {
		http.HandleFunc("/" + m, func(w http.ResponseWriter, r *http.Request) {
			fmt.Println("method", m)
			w.Write([]byte("method name is : " + m))
		})
	}

	err := http.ListenAndServe(": 5000".nil)
	iferr ! =nil {
		panic(err)
	}
}

Copy the code

If you test it, you can see that the index/login handler is eventually routed to logout because of the closure.

$The curl http://127.0.0.1:5000/index
method name is : logout% 

$The curl http://127.0.0.1:5000/login
method name is : logout% 
Copy the code

The solutions are as follows:

func routerClosure2(a)  {
	// When the method name /URL is obtained dynamically
	methods := []string{"index"."login"."logout"}

	for _, m := range methods {
		newM := m
		http.HandleFunc("/" + newM, func(w http.ResponseWriter, r *http.Request) {
			fmt.Println("method", newM)
			w.Write([]byte("method name is : " + newM))
		})
	}

	err := http.ListenAndServe(": 5000".nil)
	iferr ! =nil {
		panic(err)
	}
}
Copy the code

reference

Net/HTTP package usage mode for Go language