In previous sections, we learned the work flow of web, and talked about a little about http
package. In this section, we are going to learn two core functions in http
package: Conn, ServeMux.
Unlike normal HTTP servers, Go uses goroutine for every affair that created by Conn in order to achieve high concurrency and performance, so every affair is independent.
Go uses 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 goroutine for every connection, and passes handler that is able to read data from request to the goroutine.
We used default router in previous section when talked about conn.server, the router passed request data to back-end handler.
The struct of default router:
type ServeMux struct {
mu sync.RWMutex // because of concurrency, we have to use 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 a interface, but the function sayhelloName
didn't implement this interface, why could we add it as handler? Because there is another type HandlerFunc
in http
package. We called HandlerFunc
to define our sayhelloName
, so sayhelloName
implemented Handler
at the same time. it's like we call HandlerFunc(f)
, and function f
is forced 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 the router calls handlers after we set router rules?
The router calls mux.handler.ServeHTTP(w, r)
when it receives requests. In other words, it calls ServeHTTP
interface of handlers.
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 URL as a key to find corresponding handler that saved in map, and calls handler.ServeHTTP to execute functions to handle data.
You should understand the router work flow now, and Go actually supports customized routers. The second argument of ListenAndServe
is for configuring customized router, it's a interface of Handler
. Therefore, any router implements interface Handler
that 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 list of 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)
- Instantiated Server
- Call ListenAndServe of Server
- Call net.Listen("tcp", addr) to listen to port.
- Start a loop, and accept requests in loop body.
- Instantiated a Conn and start a goroutine for every request:
go c.serve()
. - Read request data:
w, err := c.readRequest()
. - Check 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