In previous sections, we learned about the work flow of the web and talked a little bit about Go's http
package. In this section, we are going to learn about two core functions in the http
package: Conn and ServeMux.
Unlike normal HTTP servers, Go uses goroutines for every job initiated by Conn in order to achieve high concurrency and performance, so every job is independent.
Go uses the following code to wait for new connections from clients.
c, err := srv.newConn(rw)
if err != nil {
continue
}
go c.serve()
As you can see, it creates a new goroutine for every connection, and passes the handler that is able to read data from the request to the goroutine.
We used Go's default router in previous sections when discussing conn.server, with the router passing request data to a back-end handler.
The struct of the default router:
type ServeMux struct {
mu sync.RWMutex // because of concurrency, we have to use a mutex here
m map[string]muxEntry // router rules, every string mapping to a handler
}
The struct of muxEntry:
type muxEntry struct {
explicit bool // exact match or not
h Handler
}
The interface of Handler:
type Handler interface {
ServeHTTP(ResponseWriter, *Request) // routing implementer
}
Handler
is an interface, but if the function sayhelloName
didn't implement this interface, then how did we add it as handler? The answer lies in another type called HandlerFunc
in the http
package. We called HandlerFunc
to define our sayhelloName
method, so sayhelloName
implemented Handler
at the same time. It's like we're calling HandlerFunc(f)
, and the function f
is force converted to type HandlerFunc
.
type HandlerFunc func(ResponseWriter, *Request)
// ServeHTTP calls f(w, r).
func (f HandlerFunc) ServeHTTP(w ResponseWriter, r *Request) {
f(w, r)
}
How does the router call handlers after we set the router rules?
The router calls mux.handler.ServeHTTP(w, r)
when it receives requests. In other words, it calls the ServeHTTP
interface of the handlers which have implemented it.
Now, let's see how mux.handler
works.
func (mux *ServeMux) handler(r *Request) Handler {
mux.mu.RLock()
defer mux.mu.RUnlock()
// Host-specific pattern takes precedence over generic ones
h := mux.match(r.Host + r.URL.Path)
if h == nil {
h = mux.match(r.URL.Path)
}
if h == nil {
h = NotFoundHandler()
}
return h
}
The router uses the request's URL as a key to find the corresponding handler saved in the map, then calls handler.ServeHTTP to execute functions to handle the data.
You should understand the default router's work flow by now, and Go actually supports customized routers. The second argument of ListenAndServe
is for configuring customized routers. It's an interface of Handler
. Therefore, any router that implements the Handler
interface can be used.
The following example shows how to implement a simple router.
package main
import (
"fmt"
"net/http"
)
type MyMux struct {
}
func (p *MyMux) ServeHTTP(w http.ResponseWriter, r *http.Request) {
if r.URL.Path == "/" {
sayhelloName(w, r)
return
}
http.NotFound(w, r)
return
}
func sayhelloName(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "Hello myroute!")
}
func main() {
mux := &MyMux{}
http.ListenAndServe(":9090", mux)
}
Let's take a look at the whole execution flow.
- Call
http.HandleFunc
- Call HandleFunc of DefaultServeMux
- Call Handle of DefaultServeMux
- Add router rules to map[string]muxEntry of DefaultServeMux
- Call
http.ListenAndServe(":9090", nil)
- Instantiate Server
- Call ListenAndServe method of Server
- Call net.Listen("tcp", addr) to listen to port
- Start a loop and accept requests in the loop body
- Instantiate a Conn and start a goroutine for every request:
go c.serve()
- Read request data:
w, err := c.readRequest()
- Check whether handler is empty or not, if it's empty then use DefaultServeMux
- Call ServeHTTP of handler
- Execute code in DefaultServeMux in this case
- Choose handler by URL and execute code in that handler function:
mux.handler.ServeHTTP(w, r)
- How to choose handler: A. Check router rules for this URL B. Call ServeHTTP in that handler if there is one C. Call ServeHTTP of NotFoundHandler otherwise
- Directory
- Previous section: How Go works with web
- Next section: Summary