I am fortunate enugh that my day to day job gives me a lot of freedom to pick a technology that I like or that I want to learn when approaching a new project. This is how my adventure with Go started.

The difficulty in wrtiting projects in Go stems from the fairly young age of the language and while it's easy to just google answers to problems in any of the well-established languages or frameworks, with go you might end up just trying things out or using small snippets of code and expand them on your own.

In the series of posts on Go, we're going to write a Go webserver for file uploads with authentication and a user backend acessible through ORM library. The idea comes from a panoply of file upload sites like pomf clones.

Our setup will be a Docker microservice for handling multipart file uploads through http with an admin interface + authentication and a database for handling actual uploads.

In part I, we're going to focus on net/http internals and how does it work with custom multiplexers.

What are we going to use?

Our project obviously requires an http server and for that purpose we're just going to use a vanilla net/http from std lib. While a robust router is not mandatory for a simple project like this, I grew fond of gorrilla/mux. Last but not least, we're going to use gorm, which is the most mature ORM library for GO.

What should I know to follow this?

I am working here under assumption that you at least finished A Tour of Go or went through most of the examples at gobyexample.com.

Creating a base webserver

Creating a server in Go is easy with a standard library. In this very basic example, we're not introducing anything other than net/http

package main

import (  
    "net/http"
)

func main() {  
    http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
        w.Write([]byte("OK!"))
    })
    http.ListenAndServe(":8080", nil)
}

The result is as expected

So what really happened here? We used 4 functions altogether to: spin up a server on port 8080, define a handler for requests coming to /, define a function that actually handles request and response and finally to write bytes. Seems easy enough?

Here comes a first gotcha. You might think that matching / means that we're going to see OK on the homepage, but not under localhost:8080/api/v1/. Let's verify:

To avoid that we would have to verify if / is really just /. An example from documentation suggests the following:

func(w http.ResponseWriter, req *http.Request) {  
        // The "/" pattern matches everything, so we need to check
        // that we're at the root here.
        if req.URL.Path != "/" {
                http.NotFound(w, req)
                return
        }
        fmt.Fprintf(w, "Welcome to the home page!")
})

After rewriting our handler it should look like this:

r.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {  
        if r.URL.Path != "/" {
            http.NotFound(w, r)
            return
        }
        fmt.Fprintf(w, "Welcome to the home page!")
    })

And the result of going to /api/v1 is 404 now. Yay!

Let's be honest, it's a lot of boilerplate for just saying:

/ is homepage and everything after that is just something else

Adding a custom router

The example of a boilerplate code to handle / path is trivial, but there are more reasons to use a custom request multiplexer aka router. What it we would like to have parameters in our url, or handle GET, POST< PUT requests differently without generating 50 lines long functions to inspect methods? This is where a custom request multiplexer comes in handy.

We could write our own, but there are at least 3 well established libraries that do it extremely well. There is a great blogpost by Alex Edwards that compares the libraries and I highly encourage you to check this post and subscribe to his blog.

In our example, we're going to use gorilla/mux. Defining a handler is extremely easy and that's also thanks to how well written the standard lib is.

If you look at the signature of ListenAndServe method it's defined in a following way:

func ListenAndServe(addr string, handler Handler) error  

and in our example we used nil in place of a Handler:

http.ListenAndServe(":8080", nil)  

According to godoc, that means we defaulted to DefaultServeMux. To use a custom router, we just have to pass it as parameter to ListenAndServe.

Additionally, we're going to separate our handler for clarity and follow that approach. Our main program should look like this at the moment:

package main

import (  
    "net/http"
    // we import gorilla/mux
    "github.com/gorilla/mux"
)

func main() {  
    // create a new Router instance
    r := mux.NewRouter()
    // we define a HomepageHandler in a separate function
    r.HandleFunc("/", HomepageHandler)
    // plug the new Router
    http.ListenAndServe(":8080", r)
}

func HomepageHandler(w http.ResponseWriter, r *http.Request) {  
    w.Write([]byte("OK!"))
}

We can use the *Router provided by gorilla/mux in our ListenAndServe method as it implements the http.Handler interface. Looking at net/http code, the interface is pretty straighforward.

type Handler interface {  
        ServeHTTP(ResponseWriter, *Request)
}

So the only thing you need to do to write a custom multiplexer is to write a Handler that has a ServeHTTP function that accepts a ResponseWriter and a pointer to Request. Does it solve a problem we discovered in our previous example?

Adding Negroni middleware

Without a middleware, each handler for a route defined in gorilla/mux would need to have logic to handle the same activity, like logging or authentication. Since we want to use authentication for our API, we have to resort to some sort of middleware. In Part 1 we're just going to use a middleware for basic auth, but in future posts, we're going to transition to JWT tokens.

For middleware handling, we're goint to use negroni and negroni-auth middleware for Basic Authentication.

Plugging negroni to our current application is straightforward. Firstly though, to check if our middleware works for all routes, let's add one more route just to be sure.

...
r.HandleFunc("/", HomepageHandler)  
// new route
r.HandleFunc("/api/v1", APIHandler)  
...
func APIHandler (w http.ResponseWriter, r *http.Request) {  
    w.Header().Set("Content-type", "application/json")
    w.Write([]byte(`{"api": "ok"}`))
}

So now we respond with plaintext "OK" when GET request is issued on / and with a json response on /api/v1. Let's test, just to be sure:

Well that looks great. Time to plug our middleware to that. Negroni is a robust library, but it doesn't care what kind of router you use and works well with default one, gorrila/mux or any other router you might want to use. As a general rule, you should plug all your middlewares prior to providing the router and that's what we're going to do with negroni-auth.

❗️WARNING - this is just a demonstration of how middleware plugs into our application. Using basic authentication like that is pure instanity, don't do it in production.

import (  
    ...
    // we import our new libs
    "github.com/urfave/negroni"
    auth "github.com/nabeken/negroni-auth"
)
...
// after defining all our gorilla/mux routes, we create a blank *Negroni instance
n := negroni.New()  
// we then instruct negroni to use one of our middlewares. Not the best practice,
// but as I said, this is just a temporary solution for securing our API
n.Use(auth.Basic("apiuser", "password"))  
// we then tell negroni that we're using our gorilla/mux router (r)
n.UseHandler(r)

// ListenAndServe should now get Negroni instance as parameter, not gorilla/mux Handler instance
http.ListenAndServe(":8080", n)  

What happens now:

No matter were we go in our application, we are going to be asked for authentication:

Wrapping up

In this blog post we already laid the foundation for our application by putting almost all the necessary parts in place for a full-fledged application.

We now have a server, a multiplexer and a way to manage middlewares, plus a sample middleware that expects basic authentication.

In part 1, we're going to define more elaborate routes and expand our application to connect to database.

The full code for this example is following:

package main

import (  
    "net/http"
    "github.com/gorilla/mux"
    auth "github.com/nabeken/negroni-auth"
    "github.com/urfave/negroni"
)

func main() {  
    r := mux.NewRouter()
    r.HandleFunc("/", HomepageHandler)
    r.HandleFunc("/api/v1", APIHandler)
    n := negroni.New()
    n.Use(auth.Basic("apiuser", "password"))
    n.UseHandler(r)
    http.ListenAndServe(":8080", n)
}

func HomepageHandler(w http.ResponseWriter, r *http.Request) {  
    w.Write([]byte("OK!"))
}

func APIHandler(w http.ResponseWriter, r *http.Request) {  
    w.Header().Set("Content-type", "application/json")
    w.Write([]byte(`{"api": "ok"}`))
}
Tags: go