An overview of the

A typical Go Web application is structured as follows, from Go Web Programming:

  • The client sends the request.
  • The multiplexer in the server receives the request;
  • The multiplexer finds the registered processor based on the requested URL and sends the request to the processor for processing.
  • The processor executes the program logic, interacts with the database when necessary, and obtains the processing results;
  • The processor calls the template engine to render the specified template and the results of the previous step into a data format (usually HTML) that the client can recognize;
  • Finally, the data is returned to the client through the response;
  • The client takes the data and performs corresponding operations, such as rendering it to the user.

This article describes how to create a multiplexer, how to register a processor, and finally, a brief introduction to URL matching. Let’s build on the “Hello World” program from the previous article.

package main

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

func hello(w http.ResponseWriter, r *http.Request) {
    fmt.Fprintf(w, "Hello World")}func main(a) {
    http.HandleFunc("/", hello)
    if err := http.ListenAndServe(": 8080".nil); err ! =nil {
        log.Fatal(err)
    }
}
Copy the code

Multiplexer

Default multiplexer

The NET/HTTP package has a default multiplexer, DefaultServeMux, built in for our convenience. The definition is as follows:

// src/net/http/server.go

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

var defaultServeMux ServeMux
Copy the code

Here’s a look at the Go standard library code for comparison.

  • On Windows, the default installation directory for Go isC:\Go, i.e.,GOROOT;
  • GOROOTThere is a SRC directory under which the library code is stored.
  • Each package has a separate directory, such as the FMT package insrc/fmtDirectory;
  • Subpackages are in subdirectories of their parent packages, such as net/ HTTP packagessrc/net/httpDirectory.

Many of the methods in the NET/HTTP package internally call their counterparts in DefaultServeMux, such as HandleFunc. As we know, HandleFunc registers a handler for the specified URL (to be precise, Hello is a handler function, see below). Its internal implementation is as follows:

// src/net/http/server.go
func HandleFunc(pattern string, handler func(ResponseWriter, *Request)) {
	DefaultServeMux.HandleFunc(pattern, handler)
}
Copy the code

In fact, the http.handlefunc method registers the processor with DefaultServeMux.

Also, we call HTTP using “:8080” and nil as arguments.ListenAndServe creates a default server:

// src/net/http/server.go
func ListenAndServe(addr string, handler Handler) {
    server := &Server{Addr: addr, Handler: handler}
    return server.ListenAndServe()
}
Copy the code

This server uses DefaultServeMux by default to handle requests:

type serverHandler struct {
	srv *Server
}

func (sh serverHandler) ServeHTTP(rw ResponseWriter, req *Request) {
	handler := sh.srv.Handler
	if handler == nil {
		handler = DefaultServeMux
	}
	handler.ServeHTTP(rw, req)
}
Copy the code

Each request received by the server invokes the ServeHTTP method corresponding to the multiplexer (that is, ServeMux). In ServeMux’s ServeHTTP method, we look up our registered processor based on the URL and hand it the request.

While the default multiplexer is convenient to use, it is not recommended in a production environment. Because DefaultServeMux is a global variable, it can be modified by all code, including third-party code. Some third-party code registers some processors in DefaultServeMux, which may conflict with our registered processors.

The preferred approach is to create your own multiplexer.

Create a multiplexer

Creating a multiplexer is also relatively simple, simply calling the http.newServemux method. Then, register the processor on the newly created multiplexer:

package main

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

func hello(w http.ResponseWriter, r *http.Request) {
	fmt.Fprintf(w, "Hello World")}func main(a) {
	mux := http.NewServeMux()
	mux.HandleFunc("/", hello)

	server := &http.Server{
		Addr:    ": 8080",
		Handler: mux,
	}

	iferr := server.ListenAndServe(); err ! =nil {
		log.Fatal(err)
	}
}
Copy the code

The code above does the same thing as the “Hello World” program. Here we also create our own server object. By specifying server parameters, we can create a custom server.

server := &http.Server{
	Addr:           ": 8080",
	Handler:        mux,
	ReadTimeout:    1 * time.Second,
	WriteTimeout:   1 * time.Second,
}
Copy the code

In the code above, we create a server with a read timeout and a write timeout of 1s.

Processor and processor functions

As mentioned above, the server receives a request and sends it to the appropriate handler based on its URL. The Handler is a structure that implements the Handler interface defined in the NET/HTTP package:

// src/net/http/server.go
type Handler interface {
    func ServeHTTP(w Response.Writer, r *Request)
}
Copy the code

We can define a structure that implements this interface and register objects of this structure type into the multiplexer:

package main

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

type GreetingHandler struct {
    Language string
}

func (h GreetingHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
    fmt.Fprintf(w, "%s", h.Language)
}

func main(a) {
    mux := http.NewServeMux()
    mux.Handle("/chinese", GreetingHandler{Language: "Hello"})
    mux.Handle("/english", GreetingHandler{Language: "Hello"})
    
    server := &http.Server {
        Addr:   ": 8080",
        Handler: mux,
    }
    
    iferr := server.ListenAndServe(); err ! =nil {
        log.Fatal(err)
    }
}
Copy the code

Unlike the previous code, we defined a structure called GreetingHandler that implements the Handler interface. Then, create two objects of that structure and register them on the multiplexer’s/Hello and /world paths, respectively. Note that the Handle method is registered here, in contrast to the HandleFunc method.

After the server is started, enter localhost:8080/ Chinese in the address box of the browser. Hello is displayed in the browser. Enter localhost:8080/ English, and Hello is displayed.

While the custom processor approach is flexible and powerful, the need to define a new structure to implement the ServeHTTP method is cumbersome. For ease of use, the NET/HTTP package provides a function to register the processor using HandleFunc. The function must satisfy the signature: func (w http.responsewriter, r * http.request). We call this function the processor function. This is what we use in our “Hello World” program. Inside the HandleFunc method, the handler function passed in is converted to the HandlerFunc type.

// src/net/http/server.go
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

HandlerFunc is a new type with an underlying type of func (w ResponseWriter, r *Request), which can customize its methods. Because the HandlerFunc type implements the Handler interface, it is also a processor type, eventually registered with Handle.

// src/net/http/server.go
type HandlerFunc func(w *ResponseWriter, r *Request)

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

Note that these interfaces and method names can easily be confused, so here’s a reminder:

  • Handler: Processor interface, defined in the NET/HTTP package Implements the type of interface whose objects can be registered with a multiplexer;
  • Handle: method of registering the processor;
  • HandleFunc: method of registering processor functions;
  • HandlerFunc: Indicates the underlying typefunc (w ResponseWriter, r *Request)The new type is implementedHandlerInterface. It connects theProcessor functionwithThe processor.

The URL to match

The average Web server has a lot of URL bindings, and different urls correspond to different processors. But how does the server decide which processor to use? For example, we now bind three urls, / and /hello and /hello/world.

Obviously, if the requested URL is /, the handler corresponding to/is called. If the requested URL is /hello, the handler corresponding to /hello is called. If the requested URL is /hello/world, the handler corresponding to /hello/world is called. However, if /hello/ Others is requested, which processor is used? Matches follow the following rules:

  • First, exact matching. To find out if there are any/hello/othersCorresponding processor. If yes, the search is complete. If no, go to the next step.
  • Remove the last part of the path and look again. Looking for/hello/Corresponding processor. If yes, the search is complete. If not, proceed with this step. Looking for/Corresponding processor.

One caveat here is that if the registered URL does not end in /, it will only match exactly the requested URL. Conversely, ServeMux considers the requested URL to be a match even if only the prefix is the same as the bound URL.

This is why the previous step failed to match /hello when it reached /hello/. Because /hello does not end with a /, it must match exactly. If the URL we bind is /hello/, then when the server can’t find a processor that exactly matches /hello/others, it will take the next step and start looking for a processor that matches /hello/.

Look at the following code:

package main

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

func indexHandler(w http.ResponseWriter, r *http.Request) {
	fmt.Fprintf(w, "This is the index page")}func helloHandler(w http.ResponseWriter, r *http.Request) {
	fmt.Fprintf(w, "This is the hello page")}func worldHandler(w http.ResponseWriter, r *http.Request) {
	fmt.Fprintf(w, "This is the world page")}func main(a) {
	mux := http.NewServeMux()
	mux.HandleFunc("/", indexHandler)
	mux.HandleFunc("/hello", helloHandler)
	mux.HandleFunc("/hello/world", worldHandler)

	server := &http.Server{
		Addr:    ": 8080",
		Handler: mux,
	}

	iferr := server.ListenAndServe(); err ! =nil {
		log.Fatal(err)
	}
}
Copy the code
  • Browser requestlocalhost:8080/Will return"This is the index page"Because the/Accurate matching;

  • Browser requestlocalhost:8080/helloWill return"This is the hello page"Because the/helloAccurate matching;

  • Browser requestlocalhost:8080/hello/Will return"This is the index page".Notice it’s nothelloBecause of binding/helloRequires an exact match while requesting/hello/Can’t match it exactly. So I look up/;

  • Browser requestlocalhost:8080/hello/worldWill return"This is the world page"Because the/hello/worldAccurate matching;

  • Browser requestlocalhost:8080/hello/world/Will return"This is the index page".The search steps are/hello/world/(can’t with/hello/worldExact match) ->/hello/(can’t with/hello/Exact match) ->/;

  • Browser requestlocalhost:8080/hello/otherWill return"This is the index page".The search steps are/hello/others -> /hello/(can’t with/helloExact match) ->/;

If the time of registration, the/hello/hello/instead, then request localhost: 8080 / hello/and localhost: 8080 / hello/world/all will return to the “This is the hello page”. Try it yourself!

Consider: What does localhost:8080/hello/ return when using /hello/ to register a processor?

conclusion

This article introduced the basic structure of the Go Web application. The basic form of Go Web is as follows:

package main

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

func helloHandler(w http.ResponseWriter, r *http.Request) {
    fmt.Fprintf(w, "Hello, World")}type greetingHandler struct {
    Name string
}

func (h greetingHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
    fmt.Fprintf(w, "Hello, %s", h.Name)
}

func main(a) {
    mux := http.NewServeMux()
    // Register handler functions
    mux.HandleFunc("/hello", helloHandler)
    
    // Register the handler
    mux.Handle("/greeting/golang", greetingHandler{Name: "Golang"})
    
    server := &http.Server {
        Addr:       ": 8080",
        Handler:    mux,
    }
    iferr := server.ListenAndServe(); err ! =nil {
        log.Fatal(err)
    }
}
Copy the code

Most programs in subsequent articles simply add handlers or processor functions and register them with the appropriate URL. The processor and processor functions can be used with only one or both. Notice that I’ve put Handler in the name for convenience.

The resources

  1. Go Web programming