Not Another Go/Golang net/http Tutorial

Before we get into it, I’d like to provide a little bit of a preface for the motivation behind writing this tutorial. A few months back, Brent Anderson created the meetup.com group for Minnesota’s own Go User Meetup; and, a couple months later, I set about organizing the group and events. One of our own members, Craig Erdmann, and his trusty sidekick Jen Rajkumar offered to host and cater the meetup.

So I set about writing the presentation I wanted to see – not what the group needed. We were going to walk through a pretty obfuscated example from Hoare’s extremely influential ‘Communicating Sequential Processes’, which someone had implemented in Go. After asking for feedback though, it quickly became apparent that we had many new entrants; and, as a result of quickly rewriting the presentation, it was a pretty poor experience for the newcomers.

The idea with this post is to create interesting content for those who have used go net/http in the past, but keep it easy-to-follow for beginners.

Side note: For those of you who have contacted me asking to write about specific topics (i.e.: Setting up a hadoop cluster, using Mahout’s machine learning algorithms, using Groupcache as a replacement for Redis, etc.), I really appreciate your requests and still plan on writing them - they’re somewhere in the heap.

The Ubiquitous Example

We’ve all seen this example, either from the Golang wiki, or from blog posts regurgitating the same (wonder who does that?):

For those of us that haven’t, what’s going on here is:

First, we import the “net/http” package.

Then, we see a function definition for a function called handler . As arguments, handler takes an http.ResponseWriter and a pointer to an http.Request .

. Next, in our main() function, we use http.HandleFunc(<path>, <function>) to direct all requests to "/" to our handler function.

function, we use to direct all requests to to our function. Finally, we start a server listening on localhost:8080 , with no specified handler (this results in using the built-in DefaultServeMux as our multiplexer).

The wiki goes on to talk about how this works at a high level, but let’s take a deeper look.

http.ResponseWriter && http.Request

Our handler function takes two arguments: One of type http.ResponseWriter, and the other a pointer to a http.Request type.

The source for the http.ResponseWriter shows that it’s an interface type defined as:

So, we know that w (our http.ResponseWriter variable in our handler function arguments) is pretty simple - it really only needs to conform to a role of doing three things:

Being able to send a response header with status code back to the connection

The ability to write data (received by Write() as a []byte ) back to the connection held

as a ) back to the connection held The ability to return the header map to be used by WriteHeader

That last set of bullets may seem unnecessary, as it’s sort of in the comments, but it helps us distill the source down to the idea that instead of:

we could potentially do this in our handler function:

Pretty neat - we’re setting an HTTP Header, marshalling a string slice to JSON, and writing that out to our http connection.

If http.ResponseWriter is for handling our response on the connection, the http.Request is for the other half of the equation - the initial request. It’s type is actually struct , and it has a definition a bit lengthier than that of http.ResponseWriter , so I’ve reproduced it without most comments here:

Suffice it to say that if any request parameters, data, etc. had come through, we could retrieve and parse them from that http.Request pointer.

ServeMux

In our example, we’re not making use of the http.Request pointer argument at all – why then, do we need to include it as an argument to our handler function? To simplify the answer, we’re conforming to the requirements defined by the DefaultServeMux multiplexer. You might be wondering where the hell the code for that came from - we haven’t seen any declaration of this DefaultServeMux , or anything; but, when we passed in nil to our

call, we implicitly asked ListenAndServe to multiplex incoming connections over the Default Serve Multiplexer, which will take care of routing requests to the correct handler functions, as long as we’ve “registered” them appropriately.

We’ve only registered a handler function for the "/" path with the DefaultServeMux . In order to do that, though, we needed to call http.HandleFunc , which is defined as:

As an aside: I’d like to point out how easy it is to reason about the code in the built-in packages. Comparing that to the actual call to http.HandleFunc in our example code:

We can see that we satisfy the requirements, as we pass http.HandleFunc a string, and a function which takes both a ResponseWriter and a *Request as arguments.

So, what is ServeMux? From the docs:

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. Patterns name fixed, rooted paths, like “/favicon.ico”, or rooted subtrees, like “/images/” (note the trailing slash). Longer patterns take precedence over shorter ones, so that if there are handlers registered for both “/images/” and “/images/thumbnails/”, the latter handler will be called for paths beginning “/images/thumbnails/” and the former will receive requests for any other paths in the “/images/” subtree. Note that since a pattern ending in a slash names a rooted subtree, the pattern “/” matches all paths not matched by other registered patterns, not just the URL with Path == “/”. Patterns may optionally begin with a host name, restricting matches to URLs on that host only. Host-specific patterns take precedence over general patterns, so that a handler might register for the two patterns “/codesearch” and “codesearch.google.com/” without also taking over requests for “http://www.google.com/”. ServeMux also takes care of sanitizing the URL request path, redirecting any request containing . or .. elements to an equivalent .- and ..-free URL

ListenAndServe

Hopefully some of the ‘magic’ behind the ubiquitous Go/Golang net/http example has been demystified. Or you’re thoroughly confused, and should send me feedback.

Assuming the former, let’s look under the curtain of our final function call, http.ListenAndServe(":8080", nil) . Once again, from the source:

We get a new Server struct as server , and then call the http.ListenAndServe method on that server, which if we follow the source will lead us to calling the Serve method on our Server struct, passing it a tcpKeepAliveListener , which

sets TCP keep-alive timeouts on accepted connections. It’s used by ListenAndServe and ListenAndServeTLS so dead TCP connections (e.g. closing laptop mid-download) eventually go away.

For continuity, the source of the Server.ListenAndServe method, where we can see us calling Server.Serve is:

Returning from Server.ListenAndServe is the result of the Server.Serve call. The final call to our server ’s Serve method is defined as:

We can see that Server.Serve is busy waiting on calls to l.Accept (the tcpKeepAliveListener that was passed to Serve), and if we ignore the net.Error handling made up of nested if statements, it’s clear that we create a newConn (new connection) with what we received from l.Accept , and then set some state on that connection.

For our discussion, we’ll hand-wave that stuff, and go straight to the final piece of magic:

Now, it is plain that no matter what happened with the hand-wavy stuff, we’ve called the Serve method on our new accepted connection with its own Goroutine. This, along with other Goroutines running (like our current main() Goroutine), will be multiplexed in the Go Runtime.

That’s it

In under 300 lines of markdown, we’ve uncovered the magic behind the fantastically simple net/http server. Granted, I skipped some source, comments, and tons of options (TLS anyone?); but, you now have the tools and links to source to learn more.

Also, this isn’t a stab at Rails, since I’m an active and avid user of it still, but you just can’t delve down into its innards as you can with Go. Sure, you might argue that Webrick, Puma, Unicorn, etc. aren’t part of Rails, that’s beside the point.

If you’re interested in further demystifying what’s going on, I’d recommend trying to understand the concept of the Go Runtime. Check out the Analysis of the Go runtime scheduler by Neil Deshpande, Erica Sponsler, and Nathaniel Weiss @ Columbia University - which, although somewhat dated, is still very relevant. The only note I would make while you read that, though, is that the changes discussed (proposed by Dmitry Vyukov) are partially in the current Go Runtime Scheduler.

Last note: If you liked this, you should check out my Common Mistakes Made with Golang, and Resources to Learn From post.