We learned to use net/http
package to build a simple web server in previous section, but all the working principles are the same as we talked in first section of this chapter.
Request: request data from users, including POST, GET, Cookie and URL.
Response: response data from server to clients.
Conn: connections between clients and servers.
Handler: logic of handle request and produce response.
The following picture shows that work flow of Go's web server.
Figure 3.9 http work flow
- Create listening socket, listen to a port and wait for clients.
- Accept requests from clients.
- Handle requests, read HTTP header, if it uses POST method, also need to read data in message body and give them to handlers. Finally, socket returns response data to clients.
Once we know the answers of three following questions, it's easy to know how web works in Go.
- How to listen to a port?
- How to accept client requests?
- How to allocate handlers?
In the previous section we saw that Go uses ListenAndServe
to handle these problems: initialize a server object, call net.Listen("tcp", addr)
to setup a TCP listener and listen to specific address and port.
Let's take a look at http
package's source code.
//Build version go1.1.2.
func (srv *Server) Serve(l net.Listener) error {
defer l.Close()
var tempDelay time.Duration // how long to sleep on accept failure
for {
rw, e := l.Accept()
if e != nil {
if ne, ok := e.(net.Error); ok && ne.Temporary() {
if tempDelay == 0 {
tempDelay = 5 * time.Millisecond
} else {
tempDelay *= 2
}
if max := 1 * time.Second; tempDelay > max {
tempDelay = max
}
log.Printf("http: Accept error: %v; retrying in %v", e, tempDelay)
time.Sleep(tempDelay)
continue
}
return e
}
tempDelay = 0
c, err := srv.newConn(rw)
if err != nil {
continue
}
go c.serve()
}
}
How to accept client requests after listened to the port? In the source code, we can see that it calls srv.Serve(net.Listener)
to handle client requests. In body of function there is a for{}
, it accepts request, creates a new connection, and then starts a new goroutine, and passes request data to this goroutine: go c.serve()
. This is how Go supports high concurrency, and every goroutine is independent.
Now, how to use specific functions to handle requests? conn
parses request c.ReadRequest()
at first, and get corresponding handler: handler := c.server.Handler
which is the second argument we passed when we called ListenAndServe
. Because we passed nil
, so Go uses it's default handler handler = DefaultServeMux
. So what is DefaultServeMux
doing here? Well, this is the variable of router at this time, it calls handler functions for specific URLs. Did we setting this? Yes, we did. Remember in the first line we used http.HandleFunc("/", sayhelloName)
. It's like you use this function to register the router rule for "/" path. When the URL is /
, router calls function sayhelloName
. DefaultServeMux calls ServerHTTP to get handler function for different path, and it calls sayhelloName
in this case. Finally, server writes data and response to clients.
Detailed work flow:
Figure 3.10 Work flow of handling a HTTP request
I think you should know how Go runs web servers now.
- Directory
- Previous section: Build a simple web server
- Next section: Get into http package