This is a creation in Article, where the information may have evolved or changed.
The content of this article is very basic and very easy to understand. The original address, the feeling is the most clear of the net/http
use of the package to tell a piece, so the translation of sharing.
Fundamentals of Everything: Servemux and Handler
The Go language handles HTTP requests mainly related to two things: ServeMux
and Handler
.
ServrMux
is essentially an HTTP request router (or called a multiplexer, multiplexor). It compares the received request to a list of pre-defined URL paths and then invokes the associated processor (Handler) when matching to the path.
The processor (Handler) is responsible for outputting the header and body of the HTTP response. Any http.Handler
object that satisfies the interface can be used as a processor. In layman's words, an object can only be signed with the following ServeHTTP
method:
ServeHTTP(http.ResponseWriter, *http.Request)
The Go language's HTTP package comes with several functions that are used as common processors, such as FileServer
, NotFoundHandler
and RedirectHandler
. Let's start with a simple, concrete example:
$ mkdir handler-example$ cd handler-example$ touch main.go
//File: main.gopackage mainimport ( "log" "net/http")func main() { mux := http.NewServeMux() rh := http.RedirectHandler("http://example.org", 307) mux.Handle("/foo", rh) log.Println("Listening...") http.ListenAndServe(":3000", mux)}
Quickly over the code:
In the main
function we only use the http.NewServeMux
function to create an empty one ServeMux
.
Then we use the http.RedirectHandler
function to create a new processor that will perform 307 redirect operations to all requests received http://example.org
.
Next we use the function to register the processor with the ServeMux.Handle
newly created ServeMux
one, so it /foo
receives all requests on the URL path to the processor.
Finally, we create a new server and http.ListenAndServe
listen to all incoming requests through the function, passing the created to match the ServeMux
corresponding processor for the request.
Go ahead and run this program:
$ go run main.goListening...
Then access in the browser http://localhost:3000/foo
, you should be able to find that the request has been successfully redirected.
Perspicacious you should be able to notice something interesting: ListenAndServer
the function signature is ListenAndServe(addr string, handler Handler)
, but the second argument we pass is a ServeMux
.
We can do this because ServeMux
there is a ServeHTTP
way, so it is also legal Handler
.
For me, to be ServerMux
used as a special handler is a simplification. Instead of outputting the response itself, it passes the request to the other Handler registered to it. This does not sound a significant leap at first-but it Handler
is a very common use to link together in Go.
Custom Processor (handlers)
Let's create a custom processor that will output the current local time in a specific format:
type timeHandler struct { format string}func (th *timeHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { tm := time.Now().Format(th.format) w.Write([]byte("The time is: " + tm))}
The code itself is not the focus in this example.
The real point is that we have an object (in this case timerHandler
, a struct, but it can also be a string, a function, or anything), and we implement a signature method on this object ServeHTTP(http.ResponseWriter, *http.Request)
, which is all we need to create a processor.
Let's integrate this into a specific example:
//File: main.gopackage mainimport ( "log" "net/http" "time")type timeHandler struct { format string}func (th *timeHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { tm := time.Now().Format(th.format) w.Write([]byte("The time is: " + tm))}func main() { mux := http.NewServeMux() th := &timeHandler{format: time.RFC1123} mux.Handle("/time", th) log.Println("Listening...") http.ListenAndServe(":3000", mux)}
main
function, we initialize it like a regular struct, and timeHandler
&
get its address with a symbol. Then, as in the previous example, we use a mux.Handle
function to register it with ServerMux
.
Now when we run this application, we will ServerMux
/time
give any request directly to the timeHandler.ServeHTTP
method processing.
Visit this address to see the effect: Http://localhost:3000/time.
Note that we can easily reuse multiple routes timeHandler
:
func main() { mux := http.NewServeMux() th1123 := &timeHandler{format: time.RFC1123} mux.Handle("/time/rfc1123", th1123) th3339 := &timeHandler{format: time.RFC3339} mux.Handle("/time/rfc3339", th3339) log.Println("Listening...") http.ListenAndServe(":3000", mux)}
To use a function as a processor
For simple cases (such as the example above), it is a little cumbersome to define a new ServerHTTP
custom type with a method. Let's take a look at another way, we use the http.HandlerFunc
type to let a regular function satisfy Handler
the condition as an interface.
Any func(http.ResponseWriter, *http.Request)
function that has a signature can be converted to a HandlerFunc
type. This is useful because the HandlerFunc
object has built-in ServeHTTP
methods that can intelligently and conveniently invoke the content of the function we originally provided.
If you're still a little confused, try looking [related source code]http://golang.org/src/pkg/net .... You will see that Handler
it is very simple and elegant to have a function object satisfy the interface.
Let's use this technique to re-implement the timeHandler
application:
//File: main.gopackage mainimport ( "log" "net/http" "time")func timeHandler(w http.ResponseWriter, r *http.Request) { tm := time.Now().Format(time.RFC1123) w.Write([]byte("The time is: " + tm))}func main() { mux := http.NewServeMux() // Convert the timeHandler function to a HandlerFunc type th := http.HandlerFunc(timeHandler) // And add it to the ServeMux mux.Handle("/time", th) log.Println("Listening...") http.ListenAndServe(":3000", mux)}
In fact, converting a function into HandlerFunc
post-registration ServeMux
is a very common use, so the Go language provides a convenient way to do this: ServerMux.HandlerFunc
method.
We use the convenient way main()
to rewrite the function to look like this:
func main() { mux := http.NewServeMux() mux.HandleFunc("/time", timeHandler) log.Println("Listening...") http.ListenAndServe(":3000", mux)}
In most cases, this function works well in the same way as the processor. But when things start to get more complicated, there are some limitations.
You may have noticed that, unlike the previous approach, we had to hardcode the time format into timeHandler
the method. What if we want to main()
pass some information or variables to the processor from the function?
An elegant way is to put our processor in a closure and bring the variables we want to use:
//File: main.gopackage mainimport ( "log" "net/http" "time")func timeHandler(format string) http.Handler { fn := func(w http.ResponseWriter, r *http.Request) { tm := time.Now().Format(format) w.Write([]byte("The time is: " + tm)) } return http.HandlerFunc(fn)}func main() { mux := http.NewServeMux() th := timeHandler(time.RFC1123) mux.Handle("/time", th) log.Println("Listening...") http.ListenAndServe(":3000", mux)}
timeHandler
The function now has a more ingenious identity. In addition to encapsulating a function as Handler (as we did before), we now use it to return a processor. This mechanism has two key points:
The first is to create one fn
, which is an anonymous function that format
encapsulates variables into a closure. The nature of closures allows the processor to access variables at the local scope in any case format
.
Next our closure function satisfies the func(http.ResponseWriter, *http.Request)
signature. If you remember what we said before, it means that we can convert it to a HandlerFunc
type (which satisfies the http.Handler
interface). Our timeHandler
function then returns after the conversion HandlerFunc
.
In the example above we can already pass a simple string to the processor. But in real-world applications, you can use this method to pass database connections, template groups, or other application-level contexts. Using global variables is also a good choice, and the additional benefit is to write more elegant self-contained processors for testing.
You may have seen the same wording, like this:
func timeHandler(format string) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { tm := time.Now().Format(format) w.Write([]byte("The time is: " + tm)) })}
Or, when returning, use an HandlerFunc
implicit conversion to a type:
func timeHandler(format string) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { tm := time.Now().Format(format) w.Write([]byte("The time is: " + tm)) }}
More Convenient Defaultservemux
You may have seen it in many places DefaultServeMux
, from the simplest Hello World
example to the source of the go language.
It took me a long time to realize that DefaultServerMux
there was nothing special in the place. That's DefaultServerMux
what we used before ServerMux
, but it net/httpp
was automatically initialized as the package was initialized. The related lines in the Go source code are as follows:
var DefaultServeMux = NewServeMux()
net/http
The package provides a set of shortcuts to match DefaultServeMux
: http.Handle
and http.HandleFunc
. The only difference between these functions and the functions of similar names that we've seen before is that they register the processor with the DefaultServerMux
one we created before ServeMux
.
In addition, ListenAndServe
if no other processor is provided (that is, the second parameter is set nil
), it is used internally DefaultServeMux
.
So, as a last step, we used DefaultServeMux
to rewrite our timeHandler
app:
//File: main.gopackage mainimport ( "log" "net/http" "time")func timeHandler(format string) http.Handler { fn := func(w http.ResponseWriter, r *http.Request) { tm := time.Now().Format(format) w.Write([]byte("The time is: " + tm)) } return http.HandlerFunc(fn)}func main() { // Note that we skip creating the ServeMux... var format string = time.RFC1123 th := timeHandler(format) // We use http.Handle instead of mux.Handle... http.Handle("/time", th) log.Println("Listening...") // And pass nil as the handler to ListenAndServe. http.ListenAndServe(":3000", nil)}