A RESTful Micro-Framework in Go

Posted January 25th, 2014

Why Go?

After reading countless articles about the wonders of Go, I wanted to learn more. But, learning a new language— I mean really learning it, not just getting familiar with the general capabilities and syntax—is hard. I'm sure there are tons of books that would purport to teach me all about it, but there is no substitute for hands-on learning.

I'm a maintainer on the Flask-RESTful framework (which is written in Python) but I've never built a RESTful framework from the ground up. This should be a great opportunity to learn Go.

Let's do it.

Oh, and I've already thought of a super clever name: sleepy.

Meet net/http

Before we start on the RESTful part of our framework, let's get familiar with how to use the http package to build a simple webserver in Go.

The http package lets us map request paths to functions. It's pretty simple, but provides plenty for us to work with.

package main

import (
    "net/http"
)

func response(rw http.ResponseWriter, request *http.Request) {
    rw.Write([]byte("Hello world."))
}

func main() {
    http.HandleFunc("/", response)
    http.ListenAndServe(":3000", nil)
}

When we go run this program, you'll notice it doesn't terminate. This is because it's listening for connections on port 3000! Let's make a request.

$ curl localhost:3000
Hello world.

Great. Now that we know how to build a simple server in Go. Let's make it RESTful.

Resource

Defining types is always a great place to start.

REST is all about resouces, so let's start with a Resource type. In REST, we interact with a resource using HTTP methods. An HTTP method will change or query the resource's state, and the resource will respond with a status code and a potential body. The type of the body could be anything. I think we've defined enough about a resource to create the following type.

type Resource interface {
    Get(values url.Values) (int, interface{})
    Post(values url.Values) (int, interface{})
    Put(values url.Values) (int, interface{})
    Delete(values url.Values) (int, interface{})
}

We've defined a Resource interface that defines the four most common HTTP methods. The methods each take a url.Values argument which is just defined as a simple map in url:

type url.Values map[string][]string

These functions return multiple arguments, an int, and an interface{}. The int will be the status code of the response, while the interface{} will be the data (in any format) the method returns.

405 Not Allowed

But, not every resource will want to implement all of these methods. How can we provide a default implementation of all methods that return a 405 Method Not Allowed when called? Go doesn't allow interfaces to provide default implementations, so I thought I was blocked. Then I found out about embedding.

Basically, if you declare a struct A in the definition of another struct B, B gets all of the methods of A, with the caveat that the receiver of all of the embedded methods will be A. Using this, I came up with the following solution:

type GetNotSupported struct {}
func (GetNotSupported) Get(values url.Values) (int, interface{}) {
    return 405, ""
}

The definition of a Resource that only supports Get looks like this.

type MyResource struct {
    PostNotSupported
    PutNotSupported
    DeleteNotSupported
}

It's not the sexiest solution, but the only one I could think of while still using idiomatic Go (i.e. not using the reflect package).

API

The next type we'll construct is our API type. An API could contain many internal fields, but let's keep it simple and have our API just be a receiver for methods that manage our resources.

type API struct {}

So, we make it an empty struct.

Putting It All Together

Revisiting our simple Go webserver, we quickly encounter a problem. Our Resource methods are of type:

func(url.Values) (int, interface{})

but http.HandleFunc requires a function of type:

func(http.ResponseWriter, *http.Request)

We need a way to rectify this discrepancy. Initially, this didn't quite feel possible. I couldn't see how to convert a method like Get(values url.Values) (int, interface{}) to the type I needed. It just didn't match up.

Then I remembered Go has support for first class functions! We can pass http.HandleFunc a function of the correct type that turns around and calls one of the Resource functions. Here's what that looks like.

func (api *API) requestHandler(resource Resource) http.HandlerFunc {
    return func(rw http.ResponseWriter, request *http.Request) {

        method := request.Method // Get HTTP Method (string)
        request.ParseForm()      // Populates request.Form
        values := request.Form

        switch method {
        case "GET":
            code, data = resource.Get(values)
        case "POST":
            code, data = resource.Post(values)
        case "PUT":
            code, data = resource.Put(values)
        case "DELETE":
            code, data = resource.Delete(values)
        }

        // TODO: write response
    }
}

So requestHandler returns a function of the correct type for HandleFunc. That function dispatches to the correct Resource method!

Solving this problem made me pretty happy. At first, I was stuck because I was trying to solve this using object-oriented design. It was a lot of fun to be initially frustrated by a perceived hole in Go's design and then realize I was approaching the problem from the wrong perspective. Very cool.

Anyways, back to our API.

Meet encoding/json

After we've received the data (of type interface{}) from the Resource method, we need to turn it into JSON. Conveniently, Go contains an encoding/json package that does just this.

In json, there is a function json.NewEncoder that looks like this.

func NewEncoder(w io.Writer) *Encoder

The Encoder provides an Encode method that serializes an interface{} (remember, this is any type in Go) to JSON. (Thanks twitter!). You'll notice it takes an io.Writer—which is an interface satsified by our http.ResponseWriter. Perfect. Here's what this looks like.

code, data = resource.Get(values)

encoder = json.NewEncoder(rw) // rw is http.ResponseWriter
encoder.Encode(data) // calls ResponseWriter.Write()

Pretty straightforward. Of course, in the final version, we'll need to make sure we check the error code returned from Encode, but you get the idea.

Construct The Response

Now that we have a status code and a body, we need to actually send the response to the client. Unsurprisingly, this is also pretty easy with the Go standard library. We just use the http.ResponseWriter interface. You'll notice it's already an input parameter to our anonymous function:

func(http.ResponseWriter, *http.Request)

It's a pretty simple interface.

rw.WriteHeader(code)
rw.Write(content)

That's it.

Finish The API

We can now take a Resource and convert it to a method we can give to http.HandleFunc. Let's make a convenience method on our API struct that makes this easy.

func (api *API) AddResource(resource Resource, path string) {
    http.HandleFunc(path, api.requestHandler(resource))
}

and a method that starts our API on a given port.

func (api *API) Start(port int) {
    portString := fmt.Sprintf(":%d", port)
    http.ListenAndServe(portString, nil)
}

and finally, the api.Abort method we referenced earlier.

func (api *API) Abort(rw http.ResponseWriter, statusCode int) {
    rw.WriteHeader(rw)
}

Usage

Whew. That was a lot of code snippets. Let's take a second to reflect on what we've built by constructing an actual example. Let's assume we've saved all of the above code into a sleepy module.

package main

import (
    "net/url"
    "sleepy"
)

type HelloResource struct {
    sleepy.PostNotSupported
    sleepy.PutNotSupported
    sleepy.DeleteNotSupported
}

func (HelloResource) Get(values url.Values) (int, interface{}) {
    data := map[string]string{"hello": "world"}
    return 200, data
}

func main() {

    helloResource := new(HelloResource)

    var api = new(sleepy.API)
    api.AddResource(helloResource, "/hello")
    api.Start(3000)

}

Now we run the program and hit our new endpoint!

curl localhost:3000/hello
{"hello": "world"}

So, we construct a struct that implements Resource, assign it to a path and start our API. Pretty simple! We've built a working RESTful framework in Go.

Improvements

UPDATE: sleepy is under active development and both the code and usage of the library have changed since the posting of the article. I'm leaving the content of this article alone, though, in hopes that it proves useful to others interested in Go or RESTful frameworks.

There are a few things I'd like to add to sleepy.

But not much more than that. After all, it's supposed to be a micro-framework.

Full Code

Here's the entire 92-line framework. If you're a Go expert, please let me know how you would have done this better! @dougblackio.

package sleepy

import (
    "encoding/json"
    "fmt"
    "net/http"
    "net/url"
)

const (
    GET    = "GET"
    POST   = "POST"
    PUT    = "PUT"
    DELETE = "DELETE"
)

type Resource interface {
    Get(values url.Values) (int, interface{})
    Post(values url.Values) (int, interface{})
    Put(values url.Values) (int, interface{})
    Delete(values url.Values) (int, interface{})
}

type (
    GetNotSupported    struct{}
    PostNotSupported   struct{}
    PutNotSupported    struct{}
    DeleteNotSupported struct{}
)

func (GetNotSupported) Get(values url.Values) (int, interface{}) {
    return 405, ""
}

func (PostNotSupported) Post(values url.Values) (int, interface{}) {
    return 405, ""
}

func (PutNotSupported) Put(values url.Values) (int, interface{}) {
    return 405, ""
}

func (DeleteNotSupported) Delete(values url.Values) (int, interface{}) {
    return 405, ""
}

type API struct{}

func (api *API) Abort(rw http.ResponseWriter, statusCode int) {
    rw.WriteHeader(statusCode)
}

func (api *API) requestHandler(resource Resource) http.HandlerFunc {
    return func(rw http.ResponseWriter, request *http.Request) {

        var data interface{}
        var code int

        request.ParseForm()
        method := request.Method
        values := request.Form

        switch method {
        case GET:
            code, data = resource.Get(values)
        case POST:
            code, data = resource.Post(values)
        case PUT:
            code, data = resource.Put(values)
        case DELETE:
            code, data = resource.Delete(values)
        default:
            api.Abort(rw, 405)
            return
        }

        content, err := json.Marshal(data)
        if err != nil {
            api.Abort(rw, 500)
        }
        rw.WriteHeader(code)
        rw.Write(content)
    }
}

func (api *API) AddResource(resource Resource, path string) {
    http.HandleFunc(path, api.requestHandler(resource))
}

func (api *API) Start(port int) {
    portString := fmt.Sprintf(":%d", port)
    http.ListenAndServe(portString, nil)
}

Conclusion

I hope this was informative. I definitely learned a lot about the net package in Go and got a chance to cut my teeth on Go's design. Again, if you see anything you would have done better, please let me know! @dougblackio.

sleepy is on github.