GoWeb – Receive requests (ServeMux)

Request and response process

/*
Create a simple Go web server
*/
func main() {<!-- -->
http.HandleFunc("/hello", helloWorld)
if err := http.ListenAndServe(":8080", nil); err != nil {<!-- -->
log. Fatal(err)
}
}

func helloWorld(w http.ResponseWriter, r *http.Request) {<!-- -->
fmt. Fprintf(w, "hello Go Web")
}

The request and response flow of a Go web server is as follows:

  1. The client sends a request;
  2. The multiplexer on the server side receives the request:
  3. The multiplexer finds the registered processor according to the requested URL, and hands the request to the processor for processing;
  4. The processor executes the program logic, and if necessary, interacts with the database to obtain the processing results;
  5. The processor invokes the template engine to render the specified template and the result obtained in the previous step into a data format recognizable by the client (usually HTML format);
  6. The server returns the data to the client through HTTP response;
  7. The client gets the data and performs corresponding operations (such as rendering it out and presenting it to the user).

Receive request

ServeMux and DefaultServeMux

ServeMux is a structure that contains a map that maps URLs to corresponding handlers. It will find the URL that best matches the requested URL in the mapping, and then call the ServeHTTP() method of the corresponding processor to process the request.

DefaultServeMux is a multiplexer provided by default in the net/htp package, and its essence is an instance of ServeMux. The task of the multiplexer is to redirect the request to different handlers according to the requested URL. If the user does not specify a processor for the Server object, the server uses DefaultServeMux as an instance of the ServeMux structure by default.

ServeMux is also a processor that can implement processor cascading for its instances when needed. The default multiplexer DefaultServeMux is located in the library file src/net/http/server.go, and its declaration statement is as follows:

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

The HandleFunc() function is used to register a handler for the specified URL. The HandleFunc() processor function will call the corresponding method of the DefaultServeMux object internally, and its internal implementation is as follows:

func HandleFunc(pattern string, handler func(ResponseWriter, *Request)) {<!-- -->
DefaultServeMux. HandleFunc(pattern, handler)
}

As can be seen from the above method body, the http.HandleFunc() function registers the handler to the multiplexer. Multiple processors can also be specified with the default multiplexer, which is used as follows.

/*
specify multiple processors with the default multiprocessor
*/
func main() {<!-- -->
handle1 := handle1{<!-- -->}
handle2 := handle2{<!-- -->}
//nil indicates that the server uses the default multiplexer DefaultServeMux
server := http. Server{<!-- -->
Addr: "0.0.0.0:8080",
Handler: nil,
}
//The handle() function calls the DefaultServeMux.Handler() method of the multiplexer
http.Handle("/handle1", handle1)
http.Handle("/handle2", handle2)
server. ListenAndServe()
}

// define multiple handlers
type handle1 struct{<!-- -->}

func (h1 handle1) ServeHTTP(w http.ResponseWriter, r *http.Request) {<!-- -->
fmt. Fprintf(w, "hi, handle1")
}

type handle2 struct{<!-- -->}

func (h2 handle2) ServeHTTP(w http.ResponseWriter, r *http.Request) {<!-- -->
fmt. Fprintf(w, "hi, handle2")
}

In the above code, the http.Handle(0 function is used directly to specify multiple processors. The code of the Handle() function is as follows:

func Handle(pattern string, handler Handler) {<!-- --> DefaultServeMux.Handle(pattern, handler) }

As you can see from the code, the DefaultServeMux.Handle() method is called in the http.Handle() function to process the request. Each request received by the server calls the ServeHTTP() method of the corresponding multiplexer. The code details of this method are as follows:

func (sh serverHandler) ServeHTTP(rw ResponseWriter, req *Request) {<!-- -->
handler := sh.srv.Handler
if handler == nil {<!-- -->
handler = DefaultServeMux
}
if req.RequestURI == "*" & amp; & amp; req.Method == "OPTIONS" {<!-- -->
handler = globalOptionsHandler{<!-- -->}
}
handler. ServeHTTP(rw, req)
}

In the ServeHTTP() method of the ServeMux object, we will find our registered processor according to the URL, and then hand over the request to it for processing.

While the default multiplexer is convenient to use, it is not recommended for use in a production environment. This is because: DefaultServeMux is a global variable, all code (including third-party code) can modify it. Some third-party code will register some processors in DefaultServeMux, which may conflict with our registered processors. The recommended way is to customize the multiplexer.

Custom multiplexer is also relatively simple, just call the http.NewServeMux() function directly. Then, register the handler on the newly created mux:

/*
custom multiplexer
*/
func main() {<!-- -->
mux := http. NewServeMux()
mux.HandleFunc("/", hi)

server := http. Server{<!-- -->
Addr: ":8080",
Handler: mux,
}

if err := server.ListenAndServe(); err != nil {<!-- -->
log. Fatal(err)
}
}

func hi(w http.ResponseWriter, r *http.Request) {<!-- -->
fmt. Fprintf(w, "Hi Web")
}

The function of the above code is the same as that of the default multiplexer program, which starts an HTTP server. The server object Server is also created here. .Customized servers can be created by specifying server parameters:

server := {<!-- -->
Addr: ":8080",
Handler: mux,
ReadTimeout: 5 * time. Second,
WriteTimeout: 5 * time. Second,
}

In the above code, a server with a read timeout and a write timeout of 5s is created.

To briefly summarize, ServerMux implements the ServeHTTP(ResponseWriter, *Request) method of the http.Handler interface. When creating a Server, if the Handler is set to be empty, DefaultServeMux will be used as the default processor, and DefaultServeMux is a global variable of ServerMux.

ServeMux’s URL route matching

In practical applications, a web server often has many URL bindings, and different URLs correspond to different processors.

Suppose we now bind 3 URLs, namely /, /hi and /hi/web.

  • If the requested URL is /, the handler corresponding to / is called.
  • If the requested URL is /hi, call the handler corresponding to /hi.
  • If the requested URL is /hi/web, call the handler corresponding to /hi/web.
If the registered URL does not end with /, it can only match the requested URL exactly. Conversely, even if the requested URL only has the same prefix as the bound URL, ServeMux considers them to be a match. For example, if the requested URL is /hi/, /hi cannot be matched. Because /hi does not end with /, it must be an exact match. If the URL we bind is /h/i, when the server cannot find a processor that exactly matches /hi/others, it will fall back to the next best thing and start looking for a processor that can match /hi/.
/*
route matching
*/
func main() {<!-- -->
mux := http. NewServeMux()
mux.HandleFunc("/", indexHandler)
mux.HandleFunc("/hi", hiHandler)
mux.HandleFunc("/hi/web", webHandler)

server := http. Server{<!-- -->
Addr: ":8080",
Handler: mux,
}

if err := server.ListenAndServe(); err != nil {<!-- -->
log. Fatal(err)
}
}

func indexHandler(w http.ResponseWriter, r *http.Request) {<!-- -->
fmt.Fprintf(w, "The processor is: indexHandler! The path is: /")
}

func hiHandler(w http.ResponseWriter, r *http.Request) {<!-- -->
fmt.Fprintf(w, "The processor is: hiHandler! The path is: /hi")
}

func webHandler(w http.ResponseWriter, r *http.Request) {<!-- -->
fmt.Fprintf(w, "The processor is: webHandler! The path is /hi/web")
}

Visit localhost:8080/

Visit localhost:8080/hi

Visit localhost:8080/hi/web

Visit localhost:8080/hi/

The processor here is indexHandler, because /hi requires an exact match, and the requested /hi/ cannot match it exactly, so look up /.

Both handlers and handler functions can do URL route matching. Typically, one or both of handlers and handler functions can be used. Sample code to use both is below.

/*
Using Handlers and Handler Functions Together
*/
func main() {<!-- -->
mux := http. NewServeMux()
//register handler function
mux.HandleFunc("/hi", hiHandler1)
//register handler
mux.Handle("/welcome/goweb", welcomeHandler{<!-- -->Name: "Hi, Go Handle"})

server := http. Server{<!-- -->
Addr: ":8080",
Handler: mux,
}
if err := server.ListenAndServe(); err != nil {<!-- -->
log. Fatal(err)
}

}

//Handler function handlerFunc
func hiHandler1(w http.ResponseWriter, r *http.Request) {<!-- -->
fmt. Fprintf(w, "Hi, Go HandleFunc")
}

//processor handler
type welcomeHandler struct {<!-- -->
name string
}

func (h welcomeHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {<!-- -->
fmt.Fprintf(w, "hi, %s", h.Name)
}

Handlers and Handler Functions

Processor

After the server receives the request, it will hand over the request to the corresponding multiplexer according to its URL; then, the multiplexer forwards the request to the processor for processing. A handler is a structure that implements the Handler interface. The Handler interface is defined in the net/http package:

type Handler interface {<!-- -->
ServeHTTP(ResponseWriter, *Request)
}

As you can see, there is only one ServeHTTP() processing method in the Handler interface. Any object that implements the Handler interface can be registered with the multiplexer.

You can define a structure to implement the method of this interface to register objects of this structure type into the multiplexer, see the sample code below.

/*
Example of using the Handler interface
*/

type WelComeHandler struct {<!-- -->
Language string
}

func (h WelComeHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {<!-- -->
fmt. Fprintf(w, "%s", h. Language)
}

func main() {<!-- -->
mux := http. NewServeMux()
mux.Handle("/cn", WelComeHandler{<!-- -->
Language: "1111",
})
mux.Handle("/en", WelComeHandler{<!-- -->
Language: "2222",
})

server := &http.Server{<!-- -->
Addr: ":8080",
Handler: mux,
}
if err := server.ListenAndServe(); err != nil {<!-- -->
log. Fatal(err)
}
}

Handler function

/*
handler function
*/

func hihihi(w http.ResponseWriter, r *http.Request) {<!-- -->
fmt. Fprintf(w, "hihihi")
}

func main() {<!-- -->
http.HandleFunc("/", hihihi)
if err := http.ListenAndServe(":8080", nil); err != nil {<!-- -->
log. Fatal(err)
}
}

Although the custom processor is more flexible and powerful, it needs to define a new structure to implement the ServeHTTP() method, which is quite cumbersome.

For ease of use, the net/http package provides functions to register handlers, that is, use the HandleFunc() function to register handlers. If a function implements the anonymous function func(w http.ResponseWriter,.r*http.Request), this function is called a “handler function”. The HandleFunc() function internally calls the HandleFunc() method of the ServeMux object.

The specific code of the HandleFunc() method of the ServeMux object is as follows:

// HandleFunc registers the handler function for the given pattern.
func (mux *ServeMux) HandleFunc(pattern string, handler func(ResponseWriter, *Request)) {<!-- -->
mux.Handle(pattern, HandlerFunc(handler))
}

Continue to look at the internal code to find that the HandlerFunc() function finally implements the ServeHTTP() method of the Handler interface. Its implementation code is as follows:

type HandlerFunc func(w *Responsewriter, r *Request)

func (f HandlerFunc) ServeHTTP (w ResponseWriter, r *Request) {<!-- -->
f(w,r)
}

The above function or method names are easy to confuse, and their calling relationship is shown in the figure.

  • Handler: Processor interface. Objects that implement the Handler interface defined in the net/http package can be registered in the multiplexer.
  • Handle(): The calling function in the process of registering the processor.
  • HandleFunc(): Handler function.
  • HandlerFunc: The bottom layer is the func(w ResponseWriter, r*Request) anonymous function, which implements the Handler processor interface. It is used to link handler functions with handlers.

In short, HandlerFunc() is a processor function, which calls a series of methods in ServeMux, and finally implements the ServeHTTP() method of the Handler processor interface at the bottom layer, thereby realizing the function of the processor.

Chaining multiple handlers and handler functions

A function can be passed as a parameter to another function, that is, multiple functions can be connected in series to reuse certain methods, thereby solving the problem of code duplication and strong dependence.

In fact, the processor is also a function. So when dealing with operations such as logging, security checks, and error handling, we often reuse these common methods, and then we need to call these functions in series. Concatenation techniques can be used to separate the code that needs to be reused in the code. The sample code for concatenating multiple functions is as follows.

/*
Concatenate multiple functions
*/

func main() {<!-- -->
http.HandleFunc("/", log(index))
http.ListenAndServe(":8080", nil)
}

func log(h http.HandlerFunc) http.HandlerFunc {<!-- -->
return func(w http.ResponseWriter, r *http.Request) {<!-- -->
fmt.Printf("time: %s| handlerfunc:%s\
", time.Now().String(),
runtime.FuncForPC(reflect.ValueOf(h).Pointer()).Name())
}
}

func log2(h http.Handler) http.Handler {<!-- -->
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {<!-- -->
fmt.Printf("*******")
h. ServeHTTP(w, r)
})
}

func index(w http.ResponseWriter, r *http.Request) {<!-- -->
fmt. Fprintf(w, "hello index!")
}

build model

A complete Wb project includes “processor processing request”, “operate database with model”, “use template engine (or processor) to combine the data returned by the model from the database and the template, and generate HTML or other format documents” , and the 4 steps of “transmitting to the client via HTTP message”.
The process of connecting the processor and the database on the server side through the model is shown in the figure.

  1. Create a new package model that holds the user model:
package model
  1. Create a struct named User
type User struct {<!-- -->
Uid int
name string
phone string
}
  1. Define three fields Uid, Name, and Phone in the model structure, which are int, string, and string types respectively. In order to access the database, you also need to import the “database/sql” package and the “github.com/go-sql-driver/mysql” package, and define a global variable for db:
import (
"database/sql"
"fmt"
_ "github.com/go-sql-driver/mysql"
)

var DB *sql.DB
  1. Initialize the database connection through the init() function
//Initialize the database connection
func init() {<!-- -->
DB, _ = sql. Open("mysql",
"root:123456@tcp(127.0.0.1:3306)/chapter05")
}
  1. Define the function used to get user information:
//Get user information
func GetUser(uid int) (u User) {<!-- -->
// Very important: Make sure to call the Scan method after QueryRow, otherwise the held database connection will not be released
err := DB.QueryRow("select uid,name,phone from `user` where uid=?", uid).Scan( & amp;u.Uid, & amp;u.Name, & amp;u.Phone)
if err != nil {<!-- -->
fmt.Printf("scan failed, err:%v\
", err)
return
}
return u
}
  1. Create HTML template file
<!DOCTYPE html>
<html>
  <head>
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
    <title>Welcome to my page</title>
  </head>
  <body>
    <ul>
    {<!-- -->{ range . }}
        <h1 style="text-align:center">{<!-- -->{ . }}</h1>
    {<!-- -->{ end}}
        <h1 style="text-align:center">Welcome to my page</h1>
        <p style="text-align:center">this is the user info page</p>
    </ul>
  </body>
</html>
  1. create controller
package controller

import (
"../model"
"fmt"
"html/template"
"net/http"
"strconv"
)

type UserController struct {<!-- -->
}

func (c UserController) GetUser(w http.ResponseWriter, r *http.Request) {<!-- -->
query := r.URL.Query()
uid, _ := strconv.Atoi(query["uid"][0])

//Call the model here to get data from the database
user := model. GetUser(uid)
fmt.Println(user)

t, _ := template.ParseFiles("./src/bookWebPro/chapter3/view/t3.html")
fmt. Println(t == nil)
userInfo := []string{<!-- -->user.Name, user.Phone}
t. Execute(w, userInfo)
}
  1. main function
import (
"./controller"
"log"
"net/http"
)

func main() {<!-- -->
http.HandleFunc("/getUser", controller.UserController{<!-- -->}.GetUser)
if err := http.ListenAndServe(":8088", nil); err != nil {<!-- -->
log. Fatal(err)
}
}
  1. test

Browser access 127.0.0.1:8088/getUser?uid=1