Exploring go net/http: How Server and the Handler structs works.
Add to bookmarksMon Nov 16 2020
Introduction
The net/http
is easily one of the most used libraries in go. This article is the first in a multipart series that will be looking at how certain functionalities work in the HTTP package. In this article, we'll be taking a look at Servers in go, the parts that make them work and examining a few code snippets on how it works.
A quick Http Lesson.
From the moment you make a curl request to a server a few things happen in the background (after DNS resolving has taken place). To simplify it: - A TCP (or whatever protocol is being used) connection is open by the client to the server. - An HTTP request is sent by the client to the server. - The server processes the request and sends a response to the client - More requests are sent by the client (e.g CSS resources) on the same connection as long as the keep-alive parameters still allow it.
Now that we've gotten that out of the way, the go net/http.Server
is responsible for accepting and responding to HTTP connections and we will be taking a brief look at how part of it works, as we can't cover the full workings of the server in this article.
The Server Struct
Taking a look at net/http/server.go
file in the go source code, we can take a look at the server struct item (with the comments removed for brevity):
type Server struct {
Addr string
Handler Handler
TLSConfig *tls.Config
ReadTimeout time.Duration
ReadHeaderTimeout time.Duration
WriteTimeout time.Duration
IdleTimeout time.Duration
MaxHeaderBytes int
TLSNextProto map[string]func(*Server, *tls.Conn, Handler)
ConnState func(net.Conn, ConnState)
ErrorLog *log.Logger
BaseContext func(net.Listener) context.Context
ConnContext func(ctx context.Context, c net.Conn) context.Context
inShutdown atomicBool
disableKeepAlives int32
nextProtoOnce sync.Once
nextProtoErr error
mu sync.Mutex
listeners map[*net.Listener]struct{}
activeConn map[*conn]struct{}
doneChan chan struct{}
onShutdown []func()
}
There are quite a lot of fields in the struct (some self-explanatory and some are unneeded for this article) so I will be explaining a few briefly before going further on some later in the article:
- Addr: This is the address that the server listens on if there is no listener passed to any of the Serve methods (To be touched later in the article)
- Handler: The handler is basically responsible for responding to each request. It'll be explained further in the article.
- TLSConfig: It holds the TLS configuration used by the server when responding to client requests.
- ReadTimeout: The read timeout is the allowed amount of time allowed to read the whole http request, including the body.
- WriteTimeout: The write timeout is the maximum amount of time the server waits before a response is written, starting from the moment the request is read. Think of it as the amount of time your handler has to respond to the request.
- IdleTimeout: It is the maximum amount of time the server waits for the next request before closing the connection. Note that this doesn't matter if keep-alive isn't enabled in the connection.
- BaseContext: It specifies the function that returns the context used by this server.
- ConnContext: It modifies the base context for each new connection accepted by the server.
Setting up a simple server
Below is the code to set up a basic HTTP server that listens on the port 9091
and responds with Hello World
:
package main
import (
"fmt"
"net/http"
)
func main() {
http.HandleFunc("/", func(writer http.ResponseWriter, request *http.Request) {
fmt.Fprint(writer, "Hello world")
})
server := http.Server{
Addr: ":9091",
}
fmt.Println("Starting server")
if err := server.ListenAndServe(); err != nil {
panic(err)
}
}
The server code above is split up into three parts:
- The
HandleFunc
, which is responsible for setting up the handler. - The server config which sets up the address for the listener.
- The actual running of the server with the
ListenAndServe
function.
Now, we'd be going through each of these and how they help get a go server up and running.
Handlers
What is the handler? The handler is the struct responsible for responding to individual requests, for a struct to be considered a handler, it has to satisfy the interface:
type Handler interface {
ServeHTTP(ResponseWriter, *Request)
}
E.g if we were to pass a handler directly to the server we created above, the code above could be rewritten to:
...
type customHandler struct {
response string
}
func (c *customHandler) ServeHTTP(writer http.ResponseWriter, request *http.Request) {
fmt.Fprint(writer, c.response)
}
func main() {
server := http.Server{
Addr: ":9091",
Handler: &customHandler{"Hello world from the custom handler"},
}
fmt.Println("Starting server")
if err := server.ListenAndServe(); err != nil {
panic(err)
}
}
The resulting server would look like:
So here we create a customHandler
Handler and pass it on to the server to be used to handle responses. Which begs the question, how did our earlier code get the server to handle our request initially without explicitly passing a handler?
DefaultServerMux
When there is no handler explicitly passed to the handler, the DefaultServerMux
(which is a ServeMux
) declared in the net/http
is used. If we take a look at the /net/http/server.go
at the following lines:
1810 // Serve a new connection.
1811 func (c *conn) serve(ctx context.Context) {
...
1946 serverHandler{c.server}.ServeHTTP(w, w.req)
...
1977 }
...
2873 func (sh serverHandler) ServeHTTP(rw ResponseWriter, req *Request) {
2874 handler := sh.srv.Handler
2875 if handler == nil {
2876 handler = DefaultServeMux
2877 }
...
2881 handler.ServeHTTP(rw, req)
2882 }
We can see where the DefaultServeMux
is used (lines 2875 - 2877) once the serveHandler.ServeHttp
is called in the connection serving method on line 1811.
So when the http.HandleFunc
function is called. Here is what happens:
2491 func (mux *ServeMux) HandleFunc(pattern string, handler func(ResponseWriter, *Request)) {
2492 if handler == nil {
2493 panic("http: nil handler")
2494 }
2495 mux.Handle(pattern, HandlerFunc(handler))
2496 }
...
2506 func HandleFunc(pattern string, handler func(ResponseWriter, *Request)) {
2507 DefaultServeMux.HandleFunc(pattern, handler)
2508 }
*Note the existense of HandlerFunc for now
ServeMux
ServeMux is an HTTP request multiplexer. It matches the URL of each incoming request against a list of registered patterns and calls the handler for the pattern that most closely matches the URL. Source: godoc
type ServeMux struct {
mu sync.RWMutex
m map[string]muxEntry
es []muxEntry // slice of entries sorted from longest to shortest.
hosts bool // whether any patterns contain hostnames
}
type muxEntry struct {
h Handler
pattern string
}
The attributes of the ServeMux
are quite straight forward:
- mu is a mutex lock and prevents race conditions.
- m holds a map of patterns to a muxEntry. A muxEntry is essentially a handler and pattern.
- es is the sorted lists of muxEntries from longest to shortest (this makes resolving URLs a lot more accurate)
Taking a look at DefaultServerMux's explanation above, we see that every call to HandleFunc
of the ServeMux calls .Handle
on the mux while passing the pattern and a HandlerFunc:
2445 // Handle registers the handler for the given pattern.
2445 // If a handler already exists for pattern, Handle panics.
2446 func (mux *ServeMux) Handle(pattern string, handler Handler) {
2447 mux.mu.Lock()
2448 defer mux.mu.Unlock()
2449
2450 if pattern == "" {
2451 panic("http: invalid pattern")
2452 }
2453 if handler == nil {
2454 panic("http: nil handler")
2455 }
2456 if _, exist := mux.m[pattern]; exist {
2457 panic("http: multiple registrations for " + pattern)
2458 }
2459 if mux.m == nil {
2460 mux.m = make(map[string]muxEntry)
2461 }
2462 e := muxEntry{h: handler, pattern: pattern}
2463 mux.m[pattern] = e
2464 if pattern[len(pattern)-1] == '/' {
2465 mux.es = appendSorted(mux.es, e)
2466 }
2467
2468 if pattern[0] != '/' {
2469 mux.hosts = true
2470 }
2471 }
Pretty straightforward code to follow. The mux checks if the m
map has been initialized (it initializes it if it hasn't). Then adds a muxEntry
to the map for that pattern.
Remember the ServerMux is still a Handler
, so it has to have a ServeHTTP(ResponseWriter, *Request)
function. What does that look like? (comments added by me for explanation)
func (mux *ServeMux) ServeHTTP(w ResponseWriter, r *Request) {
if r.RequestURI == "*" {
if r.ProtoAtLeast(1, 1) {
// if this is at least http 1.1 send a close connection header since keep-alive is enabled by default in http > 1.1
w.Header().Set("Connection", "close")
}
w.WriteHeader(StatusBadRequest)
return
}
// .Handler picks the handler that matches the request the most, the next line runs the
// ServeHTTP method of the selected handler
h, _ := mux.Handler(r)
h.ServeHTTP(w, r)
}
How the ServerMux
decides which handler to use is a bit beyond the scope of this article (for brevity), all you need to know is the multiplexer picks the handler based on the closest path match. Handlers like gorilla mux take this to a whole new level, so you can always check that out.
HandlerFunc
Remember the HandlerFunc
from the ServerMux.HandleFunc
method?
2491 func (mux *ServeMux) HandleFunc(pattern string, handler func(ResponseWriter, *Request)) {
...
2495 mux.Handle(pattern, HandlerFunc(handler))
2496 }
The HandlerFunc
is an adapter is an adapter that converts a func(ResponseWriter, *Request)
into a Handler
. How?
// The HandlerFunc type is an adapter to allow the use of
// ordinary functions as HTTP handlers. If f is a function
// with the appropriate signature, HandlerFunc(f) is a
// Handler that calls f.
type HandlerFunc func(ResponseWriter, *Request)
// ServeHTTP calls f(w, r).
func (f HandlerFunc) ServeHTTP(w ResponseWriter, r *Request) {
f(w, r)
}
So when it is used on line 2495 (mux.Handle(pattern, HandlerFunc(handler))
) above, it covers the second parameter of ServerMux.HandleFunc
, which is a function that accepts a ResponseWriter and Request (func(ResponseWriter, *Request)
) into a Handler
The Actual Server
The important part of the server that we will be looking at is the Server.Serve(net.Listener)
. This is the method responsible for accepting connections via the listener and responding to requests via the handler.
In our initial code sample. The example called the server.ListenAndServe
function. A quick look at that function and we see that it calls the .Serve
function (comments added by me for explanatory purposes):
func (srv *Server) ListenAndServe() error {
if srv.shuttingDown() {
return ErrServerClosed
}
// check if the address is set
addr := srv.Addr
if addr == "" {
addr = ":http"
}
// create a listener for tcp connections on the specified address
ln, err := net.Listen("tcp", addr)
if err != nil {
return err
}
// serve the "server"
return srv.Serve(ln)
}
Now, as you can see, if you were calling the Serve
function directly you would need to pass a listener, but the ListenAndServe
already creates one for us based on the address passed to the server configuration.
Next, we're taking a look at the actual Serve
function:
2945 func (srv *Server) Serve(l net.Listener) error {
...
2963 baseCtx := context.Background()
2964 if srv.BaseContext != nil {
2965 baseCtx = srv.BaseContext(origListener)
2966 if baseCtx == nil {
2967 panic("BaseContext returned a nil context")
2968 }
2969 }
2970
2971 var tempDelay time.Duration // how long to sleep on accept failure
2972
2973 ctx := context.WithValue(baseCtx, ServerContextKey, srv)
2974 for {
2975 rw, err := l.Accept()
2976 if err != nil {
2977 select {
2978 case <-srv.getDoneChan():
2979 return ErrServerClosed
2980 default:
2981 }
2982 if ne, ok := err.(net.Error); ok && ne.Temporary() {
2983 if tempDelay == 0 {
2984 tempDelay = 5 * time.Millisecond
2985 } else {
2986 tempDelay *= 2
2987 }
2988 if max := 1 * time.Second; tempDelay > max {
2989 tempDelay = max
2990 }
2991 srv.logf("http: Accept error: %v; retrying in %v", err, tempDelay)
2992 time.Sleep(tempDelay)
2993 continue
2994 }
2995 return err
2996 }
2997 connCtx := ctx
2998 if cc := srv.ConnContext; cc != nil {
2999 connCtx = cc(connCtx, rw)
3000 if connCtx == nil {
3001 panic("ConnContext returned nil")
3002 }
3003 }
3004 tempDelay = 0
3005 c := srv.newConn(rw)
3006 c.setState(c.rwc, StateNew, runHooks) // before Serve can return
3007 go c.serve(connCtx)
3008 }
3009 }
Line 2963 - 2969 set up the base context that will be passed around through the duration of the connection. It checks if the BaseContext
Attribute of the Server
(in the server struct section) and uses that to whip up the base server context. If nil, a background (default) contexts is used. Afterwards, the server is added to the context as a context value for later.
The next interesting part is the infinite loop on line 2974
. The first step of the infinite loop is to wait for a net.Conn
( a connection ) via the listener's Accept()
function. If there is an error, it continues the loop, after waiting for the duration of tempDelay
till the max time is reached before the error is returned. After that, from line 2997 the context is cloned and modified with the ConnContext
attribute of the Server from earlier. Then, on line 3005 a new http.conn
struct is generated from the net.Conn
( a network connection) received from the listener earlier (line 2975).
What is the http.conn
? It is more or less the HTTP package's representation of the server-side of an HTTP connection. It holds an immutable Server struct, and the net.Conn
struct it is derived from (Code below):
type conn struct {
// server is the server on which the connection arrived.
// Immutable; never nil.
server *Server
// cancelCtx cancels the connection-level context.
cancelCtx context.CancelFunc
// rwc is the underlying network connection.
// This is never wrapped by other types and is the value given out
// to CloseNotifier callers. It is usually of type *net.TCPConn or
// *tls.Conn.
rwc net.Conn
// remoteAddr is rwc.RemoteAddr().String(). It is not populated synchronously
// inside the Listener's Accept goroutine, as some implementations block.
// It is populated immediately inside the (*conn).serve goroutine.
// This is the value of a Handler's (*Request).RemoteAddr.
remoteAddr string
// tlsState is the TLS connection state when using TLS.
// nil means not TLS.
tlsState *tls.ConnectionState
// werr is set to the first write error to rwc.
// It is set via checkConnErrorWriter{w}, where bufw writes.
werr error
// r is bufr's read source. It's a wrapper around rwc that provides
// io.LimitedReader-style limiting (while reading request headers)
// and functionality to support CloseNotifier. See *connReader docs.
r *connReader
// bufr reads from r.
bufr *bufio.Reader
// bufw writes to checkConnErrorWriter{c}, which populates werr on error.
bufw *bufio.Writer
// lastMethod is the method of the most recent request
// on this connection, if any.
lastMethod string
curReq atomic.Value // of *response (which has a Request in it)
curState struct{ atomic uint64 } // packed (unixtime<<8|uint8(ConnState))
// mu guards hijackedv
mu sync.Mutex
// hijackedv is whether this connection has been hijacked
// by a Handler with the Hijacker interface.
// It is guarded by mu.
hijackedv bool
}
Lastly, the generated http.conn
's serve
function is called on a new goroutine to listen for requests on that connection:
1810 // Serve a new connection.
1811 func (c *conn) serve(ctx context.Context) {
...
1872 for {
1873 w, err := c.readRequest(ctx)
1874 if c.r.remain != c.server.initialReadLimitSize() {
1875 // If we read any bytes off the wire, we're active.
1876 c.setState(c.rwc, StateActive, runHooks)
1877 }
1878 if err != nil {
1879 const errorHeaders = "\r\nContent-Type: text/plain; charset=utf-8\r\nConnection: close\r\n\r\n"
1880
1881 switch {
1882 case err == errTooLarge:
1883 // Their HTTP client may or may not be
1884 // able to read this if we're
1885 // responding to them and hanging up
1886 // while they're still writing their
1887 // request. Undefined behavior.
1888 const publicErr = "431 Request Header Fields Too Large"
1889 fmt.Fprintf(c.rwc, "HTTP/1.1 "+publicErr+errorHeaders+publicErr)
1890 c.closeWriteAndWait()
1891 return
1892
1893 case isUnsupportedTEError(err):
1894 // Respond as per RFC 7230 Section 3.3.1 which says,
1895 // A server that receives a request message with a
1896 // transfer coding it does not understand SHOULD
1897 // respond with 501 (Unimplemented).
1898 code := StatusNotImplemented
1899
1890 // We purposefully aren't echoing back the transfer-encoding's value,
1891 // so as to mitigate the risk of cross side scripting by an attacker.
1892 fmt.Fprintf(c.rwc, "HTTP/1.1 %d %s%sUnsupported transfer encoding", code, StatusText(code), errorHeaders)
1893 return
1894
1895 case isCommonNetReadError(err):
1896 return // don't reply
1897
1898 default:
1899 if v, ok := err.(statusError); ok {
1900 fmt.Fprintf(c.rwc, "HTTP/1.1 %d %s: %s%s%d %s: %s", v.code, StatusText(v.code), v.text, errorHeaders, v.code, StatusText(v.code), v.text)
1901 return
1902 }
1903 publicErr := "400 Bad Request"
1904 fmt.Fprintf(c.rwc, "HTTP/1.1 "+publicErr+errorHeaders+publicErr)
1905 return
1906 }
1907 }
1908
...
1938
1939 // HTTP cannot have multiple simultaneous active requests.[*]
1940 // Until the server replies to this request, it can't read another,
1941 // so we might as well run the handler in this goroutine.
1942 // [*] Not strictly true: HTTP pipelining. We could let them all process
1943 // in parallel even if their responses need to be serialized.
1944 // But we're not going to implement HTTP pipelining because it
1945 // was never deployed in the wild and the answer is HTTP/2.
1946 serverHandler{c.server}.ServeHTTP(w, w.req)
1947 w.cancelCtx()
1948 if c.hijacked() {
1949 return
1950 }
1951 w.finishRequest()
1952 if !w.shouldReuseConnection() {
1953 if w.requestBodyLimitHit || w.closedRequestBodyEarly() {
1954 c.closeWriteAndWait()
1955 }
1956 return
1957 }
1958 c.setState(c.rwc, StateIdle, runHooks)
1959 c.curReq.Store((*response)(nil))
1960
1961 if !w.conn.server.doKeepAlives() {
1962 // We're in shutdown mode. We might've replied
1963 // to the user without "Connection: close" and
1964 // they might think they can send another
1965 // request, but such is life with HTTP/1.1.
1967 return
1968 }
1969
1970 if d := c.server.idleTimeout(); d != 0 {
1971 c.rwc.SetReadDeadline(time.Now().Add(d))
1972 if _, err := c.bufr.Peek(4); err != nil {
1973 return
1974 }
1975 }
1976 c.rwc.SetReadDeadline(time.Time{})
1977 }
1978 }
There are quite a few things going on in this function (remember it happens in a separate goroutine), also, most of theTLS parts of this function have been left due to scope.
The first thing is the infinite for loop on line 1827
which blocks till it reads the next request from the connection on line 18273
. Then error checks happen from line 1878
- 1907
, it is important to notice how during the error checks, HTTP information is written back on the net.Conn
(since the connection implements an io.Writer
interface). For example, the errorHeaders
const on line 1879
contains a header that sends a close connection signal to the client.
After that, on line 1846
. A serverHandler
struct is created with the Server struct, and the ServeHTTP method is called. What does that look like? (comments added for explanatory purposes):
func (sh serverHandler) ServeHTTP(rw ResponseWriter, req *Request) {
// get the servers handler or use DefaultServeMux if nil
handler := sh.srv.Handler
if handler == nil {
handler = DefaultServeMux
}
if req.RequestURI == "*" && req.Method == "OPTIONS" {
handler = globalOptionsHandler{}
}
// call the ServeHTTP on the handler
handler.ServeHTTP(rw, req)
}
Now, the remaining part of the conn.serve
function is honestly for tidying up things so we won't be going into those.
Conclusion
Quite a lot was touched in this article, but we have been able to walk through the process of accepting and responding to connections and requests, and we have been able to see how the sample server we set up, worked under a few layers of the hood.
Hope this article helped, if it did, don't forget to share.
Enjoy!