We learned to use the net/http
package to build a simple web server in the previous section, and all those working principles are the same as those we will talk about in the 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: Request handling logic and response generation.
The following picture shows the work flow of a Go web server.
Figure 3.9 http work flow
- Create a listening socket, listen to a port and wait for clients.
- Accept requests from clients.
- Handle requests, read HTTP header. If the request uses POST method, read data in the message body and pass them to handlers. Finally, socket returns response data to clients.
Once we know the answers to the three following questions, it's easy to know how the web works in Go.
- How do we listen to a port?
- How do we accept client requests?
- How do we allocate handlers?
In the previous section we saw that Go uses ListenAndServe
to handle these steps: initialize a server object, call net.Listen("tcp", addr)
to setup a TCP listener and listen to a specific address and port.
Let's take a look at the 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 do we accept client requests after we begin listening to a port? In the source code, we can see that srv.Serve(net.Listener)
is called to handle client requests. In the body of the function there is a for{}
. It accepts a request, creates a new connection then starts a new goroutine, passing the request data to the go c.serve()
goroutine. This is how Go supports high concurrency, and every goroutine is independent.
How do we use specific functions to handle requests? conn
parses request c.ReadRequest()
at first, then gets the corresponding handler: handler := sh.srv.Handler
which is the second argument we passed when we called ListenAndServe
. Because we passed nil
, Go uses its default handler handler = DefaultServeMux
. So what is DefaultServeMux
doing here? Well, its the router variable which can call handler functions for specific URLs. Did we set this? Yes, we did. We did this in the first line where we used http.HandleFunc("/", sayhelloName)
. We're using this function to register the router rule for the "/" path. When the URL is /
, the router calls the function sayhelloName
. DefaultServeMux calls ServerHTTP to get handler functions for different paths, calling sayhelloName
in this specific case. Finally, the server writes data and responds to clients.
Detailed work flow:
Figure 3.10 Work flow of handling an 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