Creating a Go HTTP Server with Gorilla

Welcome back to "Who wrote this Go service anyway?", where the code is made up but the implementation does matter. In tonight's episode we will find out what it takes to build a concurrent HTTP server to serve up static HTML pages for an application. If your interest is piqued, read on.

Making an HTTP Server with Gorilla

What is Gorilla?

The Go standard library provides the essential components for building an HTTP web server from scratch. For a test tube in vitro experiment or project, this is fine, however, in the wilderness of the Go programming, in vivo, you are likely to come across a framework for building HTTP services in Go called Gorilla.

Despite the name, the Gorilla framework has disappointingly little to do with our evolutionary cousins the Silverback associates. Rather, Gorilla is positioned as a "web toolkit" for the Go programming language that provides a variety of "table stakes" in web development including Sessions, Secure Cookies, Web Sockets, HTTP handlers, and more. This makes Gorilla a great choice for getting up and running with a new Go server or application Though they may no longer recognize us as formal members of the tribe, our charismatic ancestors have much to teach us still in the art of Go Programming.

Let's Make A Server

Enough musing, let's get down to brass tax! We will need to create a set of structures for our HTTP server and expose two essential methods to clients: Create and Kill. Let's begin with our structs by adding a new file to a server package called server.go

 // server/server.go

 package server
 import (
	 "context"
	 "fmt"
	 "log"
	 "net/http"
	 "sync"
	 "time"

	 "github.com/gorilla/mux"
 )

 type HttpServerConfiguration struct { ❶
   Host string
   Port string
 }

 type HttpServer struct { ❷
	 server *http.Server
	 wg     *sync.WaitGroup
 }

 
 func (conf *HttpServerConfiguration) GetAddress() string {
	 return fmt.Sprintf("%s:%s", conf.Host, conf.Port)
 }

We create two structs in the above code snippet: a configuration HttpServerConfiguration ❶ and the server HttpServer ❷. The configuration struct contains vital information for hosting used to construct our HttpServer. The HttpServer strut on the other hand wraps the Go library's http.Server combining it with a WaitGroup. This WaitGroup will be used to manage the lifecycle of our HTTP server mainly by listening for an interrupt signal from the main.go process. We will wire this up in the last section. Finally, we define a convenience function GetAddress ❸ to format the server address.

Now let's move to build the server:

 // server/server.go

 func Create(conf *HttpServerConfiguration) *HttpServer {
	 _, cancel := context.WithCancel(context.Background()) ❶
	 defer cancel()

	 r := mux.NewRouter() ❷
	 // r.HandleFunc("/", RootHandler(conf.BotInfo)) ❸
	 // r.HandleFunc("/ok", OkHandler) ❹

	 s := HttpServer{ ❺
		 server: &http.Server{
			 Addr:    conf.GetAddress(),
			 Handler: r,
		 },
	 }

	 // Start server async
	 go func() {
		 log.Printf("Server started on at %s", conf.GetAddress())
		 s.server.ListenAndServe()
	 }() ❽

	 return &s
 }

The Create function is responsible for, you guessed it, creating an HttpServer. The function takes in an HttpServerConfiguration argument and sets the server's Context using the context package's WithCancel method.

Recall that Go is a systems programming language with concurrency included as a first-class citizen. Because of this design choice, a concurrent Go program, such as our HTTP server, must be capable of quickly stopping goroutines associated with requests that have been canceled or timeout. Here we set the server's Context to a new derived value coming from the root Background() context. The purpose of all this context business is to ensure we can wind down our server's operations in a coordinated way in the event we receive an upstream cancellation signal.

Once we have established the HTTP server's context, using gorilla we create a new Router to set up handler functions for two routes / and /ok. The root path / will be handled by the RootHandler while the /ok path performs a health check and is handled by the OkHandler (Note that we leave these commented out for now and will create these handler functions in the next section). Finally, once the HttpServer is constructed Create starts a goroutine to block and listen for TCP connections at the provided address.

With the server creation out of the way let's move onto the server shutdown which goes hand in hand with the birth of our new server.

 // server/server.go

 func (s *HttpServer) Kill() error {
	 timeout := time.Second * 3 ❶
	 ctx, cancel := context.WithTimeout(context.Background(), timeout) 

	 defer cancel()

	 if err := s.server.Shutdown(ctx); err != nil { ❷
		 if err := s.server.Close(); err != nil { ❸
			 log.Printf("Server failed graceful shutdown. Error: %v", err) ❹
			 return err
		 }
	 }

	 return nil
 }

Similar to the Create our Kill function acquires a context to attempt a shutdown of the HttpServer. However, in this case, instead of a context derived WithCancel, we derive a new context using WithTimeout, providing a 3-second timeout value to cancel the operation. If the server throws an error in the middle of the process, we forcefully close it. Both Kill and Create get their exercise through use in the main.go which we will update now.

To avoid code overload, we will split the code listing into two parts: setting up environment variables with Viper and creating our HTTP Server.

Setting Up Viper and an HTTP Server

Configuration management is a touchy topic. While few in number, there certainly do exist software programmers who are Prima Donnas when it comes to design choices and style (read: the author). Because of this fatal flaw in our profession, one should be careful to suggest a particular means of managing application configuration to avoid things coming to blows in a spontaneous water cooler brawl (For the consultants in the room, I have been told that Agile is somewhat less effective when combined with brawl-driven-development).

We will use a well-known Go package called viper to manage our configurations and avoid the headaches of rolling our own solution. Viper is flexible and capable of handling various configuration file types (JSON, .env, YAML) as well as environment variables injected into the application processes' environment. Alongside our configuration dependency, now would be a good time to mention we will be following a few common industry principles in software development concerning building "12-factor applications". In specific, we will use the technique of environment variables as configuration flags.

According to the 12-factor philosophy's third principle about configurations, by using environment variables to configure the app we achieve "strict separation of configuration from code" because "config varies substantially across deploys, code does not." This allows us to tinker with the dependencies of an application (e.g. database connections, ports, caches) without directly tinkering with the application code. As mentioned, this practice is common in the industry so understanding how to design applications in this way is an asset.

 // main.go

 package main

 import (
	 "bot/server"
	 "log"
	 "os"
	 "os/signal"
	 "syscall"

	 "github.com/spf13/viper"
 )

 func main() {
	 log.Println("Hello World.")
	 viper.SetConfigFile("local.env") ❶

	 if err := viper.ReadInConfig(); err != nil { ❷
		 if _, ok := err.(viper.ConfigFileNotFoundError); ok {
			 log.Print("Config file not found\n")
		 } else {
			 log.Printf("Config file was found but another error was produced: %v\n", err)
		 }
	 }

	 viper.AutomaticEnv() ❸
	 viper.SetDefault("PORT", "3000") ❹
	 viper.SetDefault("HOST", "localhost") ❺

	 # -- snipped --
 }

Returning to our application's loveable main function, we add in some new functionality to get the ball rolling. Specifically, we tell viper to look for ❶ and read ❷ a configuration file called local.env which will contain defined environment variables for our application during local development. Once we have confirmed that viper has read our local configuration, we use the AutomaticEnv function ❸ to tell viper to load existing environment variables into it's consciousness as well. Finally, we set our default PORT ❹ and HOST ❺ values for local development.

With environment setup aside, we can set up our server below in the second snippet of code.

 // main.go

 # -- snipped imports --

 func main() {
   # -- snipped Viper setup --

 port := viper.GetString("PORT") ❶
	 host := viper.GetString("HOST") ❷

	 s := server.Create(&server.HttpServerConfiguration{ ❸
		 Host: host, Port: port, BotInfo: "No configuration.",
	 })

	 defer s.Kill() ❹

	 interrupt := make(chan os.Signal, 1) 
	 signal.Notify(interrupt, os.Interrupt, syscall.SIGINT, syscall.SIGTERM)
	 <-interrupt ❹
 }

Picking up where we left off, we grab our port ❶ and host ❷ values and combine them with the magical Create function we built in the server package to build a shiny new HTTP server ❸. With this freshly minted HTTP server, we dutifully promise to shut it down by deferring our Kill function to the main end, and finally set up a channel to wait for an interrupt or termination signal to be sent to the program to end.

Our HTTP server setup is in place lets attempt to run it!

 $  go build -o bin/main main.go

 2020/12/29 10:15:59 Hello World.
 2020/12/29 10:15:59 Server started on at localhost:3000

Our program starts and does absolutely nothing at the moment - terrific. However, that is what we expect and we will change that in the next section.

Constructing Handlers and HTML pages

Recall that we commented out handlers in the previous section's Create function. In this section, we will create these handlers as well as some HTML pages to support them. To get us started let's uncomment those lines and rip off the metaphorical bandage of our wounded HTTP server routing.

 // server/server.go

 func Create(conf *HttpServerConfiguration) *HttpServer {
   # -- snipped --
	 r := mux.NewRouter() ❷
	 r.HandleFunc("/", RootHandler(conf.BotInfo)) ❸
	 r.HandleFunc("/ok", OkHandler) ❹
   # -- snipped --
 }

Adding an OK Handler

With my daily dramatics out of the way, we now need to implement two handler functions RootHandler and OkHandler. The OkHandler is simply a health check which is intentionally simple. Inside the server package create a new Go file called ok_handler.go.

Below is the code listing describing our health check endpoint:

 // server/ok_handler.go

 package server

 import (
	 "encoding/json" ❶
	 "net/http"
 )

 type Ok struct { 
	 Msg string
 }

 func OkHandler(w http.ResponseWriter, req *http.Request) { ❷
	 ok := Ok{Msg: "Ok"} ❸
	 j, _ := json.Marshal(ok)

	 w.Header().Add("Content-Type", "application/json") 

	 w.Write(j) ❹
 }

In the words of a memorable childhood character Monsieur Pepe Le Pew:

"This is good, no?".

We begin by importing some familiar facourites: encoding/json and net/http ❶, then proceed to construct our handler's function body ❷. The handler simply marshals an "Ok" message using a conveniently named struct called Ok ❸. Given how simplicity of the Ok struct, we marshal it into JSON with the json package without writing a custom function. After marshaling the message, the hander writes the result to the http.ResponseWriter, responding to the caller.

Be sure to add application/json as Content-Type to the header lest you anger the WWW gods.

Adding The Root Handler

With the OkHandler out of the way, we move onto our RootHandler. The RootHandler is responsible for displaying information about your application. Since I don't know what your application is going to do, we will keep it simple and just add an HTML page. Go's standard library is well equipped to help us out here using the html/template package. To make use of this let's set up a root.html page that takes advantage of Go templating inside our templates package.

 <h1> Bot Configuration </h1>
 <code style="white-space:pre-line;"> 
		{{.Message}} ❶
 </code>

Go's standard templates are executed by applying them to a data structure. Within the template file, markup (such as HTML) lives alongside various annotations that refer to elements inside the data structure and are evaluated through the double brace ({{ }}) syntax. In the above root.html file we use a single element from the data structure called Message ❶ and insert the value into a <code> block. With our root.html template complete, let's look into the implementation of RootHandler. To begin, create a new file root_handler.go under the server package.

 // server/root_handler.go

 package server

 import (
	 "html/template"
	 "net/http"
 )

 type RootPageData struct { ❶
	 Message string
 }

 func RootHandler(msg string ❸) func(http.ResponseWriter, *http.Request) { ❷

	 data := RootPageData{msg} ❹

	 return func(w http.ResponseWriter, r *http.Request) { ❺
		 tmpl := template.Must(template.ParseFiles("templates/root.html")) ❻
		 tmpl.Execute(w, data) ❼
	 }
 }

Inside root_handler.go we define the underlying strct for our page's data RootPageData containing the Message value for our root.html template ❶. Next, we define the actual RootHandler which is a bit unconventional from what we have seen in typical handlers ❷. Given the handlers are defined in a separate server package and do not have direct access to the models we create, we want to supply the the message as a string argument ❸ to the RootHandler to display. However, to do this would mean RootHandler no longer conforms to the required interface for gorilla's HandleFunc Router function (gasp).

In particular, the gorilla HandleFunc signature requires a function that receives only an http.ResponseWriter and http.Request as arguments meaning our additional parameter is a no go.

 func (r *Router) HandleFunc(path string, f func(http.ResponseWriter, *http.Request)) *Route

In order to have our cake and eat it too, we turn the RootHandler into a "wrapper function" of sorts, that defines our desired function signature receiving a string parameter and returning a compatible type for gorilla's HandleFunc interface, namely a standard handler function ❺. Inside RootHandler we focus on binding our info parameter value to our template structure ❹ and move the parsing ❻ and template execution ❼ inside the returned func that is called by gorilla. Who knew HTML could be so fun?

With our handlers in place and HttpServer set up to use them, we can now start our program and hit some endpoints.

 $ go build -o bin/main main.go

 # In a seperate terminal window
 $ curl http://localhost:3000/ok
 {"Msg":"Ok"}

If you navigate to localhost:3000 in your browser, you should see your lovely new root HTML page as well.