1. Introduction
Let's learn this http server creation step by step. We're not very familiar with it.
2. http creation
1. Introduction to http.handler interface
type Handler interface { ServeHTTP(w ResponseWriter, r *Request) } func ListenAndServe(address string, h Handler) error
The ListenAndServe function requires a server address such as "localhost:8000" and an instance of the Handler interface where all requests can be dispatched.
We only need to create a type, and then let this type implement the Handler interface, that is, create a ServeHTTP function, so that the function written by ourselves can be registered in the server.
2. Step by step
1. The simplest can only perform a single function
func main() { db := database{"shoes": 50, "socks": 5} log.Fatal(http.ListenAndServe("localhost:8000", db)) } type dollars float32 func (d dollars) String() string { return fmt.Sprintf("$%.2f", d) } type database map[string]dollars func (db database) ServeHTTP(w http.ResponseWriter, req *http.Request) { for item, price := range db { fmt.Fprintf(w, "%s: %s\n", item, price) } }
Output:
- The Handler interface is implemented with database
- Use http.ListenAndServe, listen on port 8000, and register db with the HTTP service
- With direct access to the browser, the server can execute our functions
2. Add the use of URL to realize multi-path access
The URL is simply the path to access the server and the path to access the server
For example: https://www.baidu.com/s?ie=UTF -8&wd=%E7%99%BE%E5%BA%A6
From / s? The beginning is the path, and we should also implement this method
func (db database) ServeHTTP(w http.ResponseWriter, req *http.Request) { switch req.URL.Path { case "/list": for item, price := range db { fmt.Fprintf(w, "%s: %s\n", item, price) } case "/price": item := req.URL.Query().Get("item") price, ok := db[item] if !ok { w.WriteHeader(http.StatusNotFound) // 404 fmt.Fprintf(w, "no such item: %q\n", item) return } fmt.Fprintf(w, "%s\n", price) default: w.WriteHeader(http.StatusNotFound) // 404 fmt.Fprintf(w, "no such page: %s\n", req.URL) } }
Here, the simplest way is to modify ServeHTTP
Perform corresponding functions according to the contents of req.URL.Path
If we visit http://localhost:8000/list , execute the contents of the corresponding case
If we visit http://localhost:8000/price?item=socks
/price will execute the corresponding case
? For Query()
Get("item") returns the socks string
3. Create servermux to realize functional routing
By adding the URL, we can see that different functions can be realized through different accesses
However, if our functions are very complex, we'd better not write different functions to the same Handler
In other words, it will be divided into several different functions, written separately, and then registered in the http service
However, if we only use the current method, we can't realize this function. We need to introduce ServeMux
func main() { db := database{"shoes": 50, "socks": 5} mux := http.NewServeMux() mux.Handle("/list", http.HandlerFunc(db.list)) mux.Handle("/price", http.HandlerFunc(db.price)) log.Fatal(http.ListenAndServe("localhost:8000", mux)) } type database map[string]dollars func (db database) list(w http.ResponseWriter, req *http.Request) { for item, price := range db { fmt.Fprintf(w, "%s: %s\n", item, price) } } func (db database) price(w http.ResponseWriter, req *http.Request) { item := req.URL.Query().Get("item") price, ok := db[item] if !ok { w.WriteHeader(http.StatusNotFound) // 404 fmt.Fprintf(w, "no such item: %q\n", item) return } fmt.Fprintf(w, "%s\n", price) }
As can be seen from the above implementation, we have implemented the list and price functions respectively
Then a ServeMux is created
And register our list and price functions in the servermux according to their corresponding access paths
Then, we register the ServeMux with the http service
Why don't we register the list and price functions directly in http, but through a function route such as ServeMux?
We can see that the list and price function names are different from the function registration names, so they cannot be registered directly, but only through an adapter.
For example, database is a mobile phone manufacturer that produces mobile phones with two different interfaces, but the charger with only one interface must have a conversion head (adapter) to charge?
The statement http.HandlerFunc(db.list) is a conversion rather than a function call because http.HandlerFunc is a type. It has the following definitions:
type HandlerFunc func(w ResponseWriter, r *Request) func (f HandlerFunc) ServeHTTP(w ResponseWriter, r *Request) { f(w, r) }
HandlerFunc shows some unusual features in the Go language interface mechanism.
This is a function type that implements the method of the interface http.Handler.
The behavior of the ServeHTTP method is the function itself that calls it.
Therefore, HandlerFunc is an adapter that allows the function value to meet an interface, where the function and the only method of the interface have the same function signature.
In fact, this technique makes a single type, such as database, satisfy the http.Handler interface in many ways: one through its list method, one through its price method, and so on.
4. The default is defaultservermux, which reduces code writing
net/http package provides a global servermux instance, DefaultServerMux and package level http.Handle and http.HandleFunc functions.
In this way, the http service uses defaultservermux as the main handler of the server. We don't need to pass it to the ListenAndServe function to write the nil value.
func main() { db := database{"shoes": 50, "socks": 5} http.HandleFunc("/list", db.list) http.HandleFunc("/price", db.price) log.Fatal(http.ListenAndServe("localhost:8000", nil)) }
http.Handle() : func Handle(pattern string, handler Handler)
This is a variable that registers the Handler interface
The second parameter is to directly write the variables that implement the Handler interface
For example, we have two different types and both implement the Handler interface
Then, register with this function
http.HandleFunc() : func HandleFunc(pattern string, handler func(ResponseWriter, *Request))
This is for registering functions with the same parameters as Handler
Any function that satisfies handler func(ResponseWriter, *Request) can be registered