[vernacular] http creation of Go

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:

  1. The Handler interface is implemented with database
  2. Use http.ListenAndServe, listen on port 8000, and register db with the HTTP service
  3. 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

Tags: Go http

Posted on Sun, 31 Oct 2021 09:47:38 -0400 by kenrbnsn