Writing a service in Go with multiple HTTP REST servers

A Go service that incorporates multiple HTTP REST servers using just the built in standard library.

Motivation

Most software engineers are likely to need to build HTTP REST services in their day job, or use them to explore ideas in their personal projects. We are likely to pick off the shelf libraries that take care of most of the boiler plate for us — for example where I work I only need to implement the endpoints and business logic, everything else is prepackaged, tested and maintained by another team. In most cases this is what you want — coding at the end of the day is a means to an end, and just like a hammer, you don’t need to know how it’s built, you just need it to work when it’s used.

The RESTful Go Service

To follow along it’s best you have a basic understanding of Go. If you’re new to Go why not check out the following to get a better understanding of it:

Hello World

Yep, let’s start from the basics where we all start from when we’re learning something new:

Prints “Hello World” to the console

Listen and Serve

Ok, so now let’s start by creating the basic server which will return 404 when we cURL the address:

Just start a server and nothing more
$ curl 127.0.0.1:8080 -v
* Trying ::1...
* TCP_NODELAY set
* Connection failed
* connect to ::1 port 8080 failed: Connection refused
* Trying 127.0.0.1...
* TCP_NODELAY set
* Connected to localhost (127.0.0.1) port 8080 (#0)
> GET / HTTP/1.1
> Host: localhost:8080
> User-Agent: curl/7.64.1
> Accept: */*
>
< HTTP/1.1 404 Not Found
< Content-Type: text/plain; charset=utf-8
< X-Content-Type-Options: nosniff
< Date: Sat, 24 Apr 2021 16:05:38 GMT
< Content-Length: 19
<
404 page not found
* Connection #0 to host localhost left intact
* Closing connection 0

Hello World Server with a http.Handler

It’s not very friendly at the moment, so let’s make it return a familiar message on a new endpoint:

Returns “Hello World” on GET to /hello
  • add trace and span ids to the request context (req.WithContext);
  • record duration of requests.

Hello World with http.Handle

We’re only interested in handling the /hello endpoint and nothing else, so let’s make this simpler and use http.Handle:

Using http.Handle

Hello World with http.HandleFunc

We can make it even simpler still, and not have the requirement of having to create a type that implements ServeHTTP:

Using http.HandleFunc

Different RESTful Methods

Ok, so we can get a nice message back from our server, but sometimes we want to be able to POST or use other RESTful method types. We can do this using a switch statement in our hello function:

Handling POST and GET requests to hello endpoint
$ curl --request POST localhost:8080/hello                             
Thanks for posting to me

The Middleware Pattern

Let’s just quickly explore the middleware pattern that we came across earlier. As I mentioned before, it’s a useful pattern where by we can extend the functionality of something without changing the existing implementation — you may recognise this as one of the SOLID principles, the open closed principle.

Adding a middleware that will add a value to the context and time the request

Custom HTTP Server

The default http server that is created when we use http.ListenAndServe is probably good enough for our side projects, but probably not for things we want to productionise. We can create a custom http server by creating a new http.Server in the main function:

Custom http server

Multiple RESTful Servers

Ok so now we have our hello service up and running, but what if we wanted to grab diagnostics, metrics and expose an admin endpoint? We could easily add those to endpoints to the existing server, but generally it’s safer to add a new server that listens on a different port and firewall that port to only be accessible locally and not over the public internet — this is a bit far fetched for our simple server, but this is a fun exercise. Let’s do this one step at a time:

Create Our Own ServeMux

Let’s first step away from using the DefaultServeMux and use our own so that we don’t accidentally mix the hello endpoint code with the admin endpoint code:

Creating a custom ServeMux for the hello endpoint

Create an Admin ServeMux and Server

Almost a C&P of the above. Let’s change the port to 8081, and create a new endpoint /ping that will respond with pong, so it’s a simple liveness check:

Creating a custom ServeMux and endpoint to access admin endpoints
$ curl localhost:8081/ping                                               
curl: (7) Failed to connect to localhost port 8081: Connection refused

Goroutines

Ok, time to bring out the big guns, or rather the tiny goroutines. Let’s place the two servers in their own goroutine, that’ll allow both the servers to run:

Running servers in their own goroutines
$ curl localhost:8080/hello                                              
Hello unknown
$ curl localhost:8081/ping
pong

WaitGroups

There’s a bit of a smell here — it feels wrong to block on the last ListenAndServe, although it works, it’s just not explicit or clear enough as to what’s going on. We should place the servers in their own goroutines and have the main goroutine wait until they shutdown before shutting the whole service down, we can then also add logs and other cleanup code just before the service shuts down:

Using WaitGourp to orchestrate the running of the servers in the own goroutines
$ go run main.go                                                                                    
Starting server
$ $?
zsh: command not found: 1

Signals

We want more control over the shutdown process so that we can log a message before the service shuts down. So we will need to use the signal package to help us with this:

Using signal to control the shutdown process

The Complete File

The completed service implementation:

Two HTTP REST services in Go
  • Query parameters;
  • Reading from the request body;
  • Writing to the response body;
  • JSON/XML parsing;
  • TLS;
  • Go HTTP client;

On an adventure

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store