Saturday, January 3, 2015

Go/Gorilla for MEAN Stack developers part 1: HTTP routing

Are you a MEAN Stack developer who knows some Go and is curious about trying out web development in the language? If so, you've come to the right place! In this tutorial, you'll learn the basics of Go's http package and Gorilla's mux package from Express.js.

As the title suggests, this tutorial assumes you know Express. This tutorial also assumes you know the basics of Go. If you don't I recommend going to tour.golang.org for an interactive tutorial. I found Go really easy to learn from C++, so if you know C++ or JavaScript, it'll be a quick read.

In this tutorial we're going to make a simple Go/Gorilla web server and you will be able to find the final product on this GitHub repository. If you're interested in a more flashy example, my last two tutorials, which you can find here and here will teach you how to make this app, Slothful Soda, with the GGAP Stack (Go, Gorilla, Angular.js, PostgreSQL). Those tutorials do throw more concepts at you at once, though, so if you're just starting Go web development I'd recommend reading this tutorial first.

To get started, create a directory in the src directory of your GOPATH called gorilla-tutorial. In this tutorial I will have Node.js examples alongside the Go examples, so if you want to follow along in Node as well as Go, add a file to the directory called server.js.

A hello world example

In Go, HTTP functionality is handled with the standard net/http package. Handling HTTP requests in Go looks very similar to handling HTTP requests on an Express server. To demonstrate that similarity, here's a hello world example in Express. If you're following along in Node, run npm install express and then put this code in server.js.
var express = require('express');

var app = express();
var serveHelloWorld = function(req, res){ //1
  res.send('Hello world!');
}

app.use('/', serveHelloWorld);            //2
app.listen(1123);
1. We have an Express middleware function serveHelloWorld that takes in a request object and a response object and sends the message “Hello world!”

2. We register serveHelloWorld with the catch-all '/' route, so serveHelloWorld handles all requests to our server.

Pretty simple, now let's see how we would make this in Go. In gorilla-tutorial make a server.go file with this code:
package main

import (
    "fmt"
    "net/http"
)

func serveHelloWorld(w http.ResponseWriter, r *http.Request){ //1
    fmt.Fprintf(w, "Hello world!")
}

func main(){
    http.HandleFunc("/", serveHelloWorld)                     //2
    http.ListenAndServe(":1123", nil)                         //3
}
Here's what's going on:

1. Functions handling HTTP requests in Go all take in a ResponseWriter and a Request pointer. Like with serveHelloWorld in the Express version, we are writing “Hello world!” as our response.

2. We register serveHelloWorld to the catch-all "/" route with HandleFunc.

3. We have our server listen on port 1123 with http.ListenAndServe.

Now run go install and then run gorilla-tutorial and on localhost:1123 you should get:


So far the Go code looks like the Express version. Before we move on I'd like to quickly point out how in Go routing we needed to explicitly say that serveHelloWorld takes in a ResponseWriter and a Request pointer. Because of that we know at compile time what types go into all calls to serveHelloWorld and any other functions handling HTTP requests. This type safety in Go really comes in handy for preventing a lot of annoying hard-to-detect type errors.

Let's add some more routes

Here's a couple more Express routes. If you're following along in Express, add these routes to server.js before the catch-all '/' route:
app.use(/\/sloths$/, function(req, res){      //1
  res.send('Sloths rule!');
});
app.use('/images', function(req, res){        //2
  res.send('You are in the images directory');
});
1. Requests to localhost:1123/sloths get the response “Sloths rule!” (the path uses a regular expression to only match the exact URL /sloths).

2. Requests to any path that starts with /images/ get the response “You are in the images directory”.

For the Go implementation add these routes in main:
http.HandleFunc("/sloths", func(w http.ResponseWriter, r *http.Request){ //1
    fmt.Fprintf(w, "Sloths rule!")
})
http.HandleFunc("/images/", func(w http.ResponseWriter, r *http.Request){//2
    fmt.Fprintf(w, "You are in the images directory")
})
1. In Go routing if a path doesn't end with a slash, it only matches that exact URL. So “/sloths” only matches requests to localhost:1123/sloths.

2. A path ending with a slash in Go matches all URLs with that path plus anything after the slash, so to match URLs that start with /images/, we use the path “/images/”.

The rule about slash and no-slash paths explains why “/” is Go's catch-all path; “/” matches any path starting with /. All paths start with / so “/” matches all requests to the server.

Run go install and gorilla-tutorial and on localhost:1123/sloths you will get:


On localhost:1123/images, you will get:


Since we have that images directory route, let's make it actually serve images.

Statically serving images

In Express, static files like images can be served with express.static. To make our image server, first make a directory called public and in it make a directory called images and download these pictures into the images directory as sloth.jpg and lemur.jpg.


If you're following along in Express, in server.js replace the images route with:

app.use('/images', express.static(__dirname+'/public/images'));

This puts express.static in charge of requests to /images, serving images in public/images.

Here's the equivalent in Go:
       //1                        //3
http.Handle("/images/", http.StripPrefix("/images/",
                          http.FileServer(http.Dir("public/images"))))
                                 //2
1. For our images route, notice we're using Handle instead of HandleFunc. While HandleFunc takes in a function to handle requests, Handle takes in an object called a Handler to handle the requests.

2. We're using the FileServer function to make our Handler, which serves files in public/images.

3. The Handler created by FileServer is almost what we want. FileServer's Handler serves images in the right directory but it looks for them using the request's whole URL. So for a request to /images/sloth.jpg, the Handler would look for the image in public/images/images/sloth.jpg, not public/images/sloth.jpg.
To fix this, StripPrefix takes our request URL, removes the /images/ prefix, and then lets the FileServer's Handler serve our images. So if we request /images/sloth.jpg, the path our Handler gets is now just sloth.jpg so the Handler looks in the right file path.

Here's a diagram of how that works:


Also note that FileServer isn't just for images. You can also use it for other files like HTML, CSS, and JavaScript files if you add calls to Handle with FileServers for the directories you keep those files in.

Introducing Gorilla

We've done some pretty straightforward routing so far. But there are more complex routes out there to serve. Go's standard HTTP package doesn't have rules for paths with routing parameters or regular expressions. For that, Gorilla's got us covered, so let's move our routing logic to a Gorilla router.

First, go get github.com/gorilla/mux.

Now make a new file called router.go and give it this code:
package main

import(
    "net/http"
    "fmt"

    "github.com/gorilla/mux"
)

func initRouter() *mux.Router{
    r := mux.NewRouter()                                                 //1

    r.HandleFunc("/sloths", func(w http.ResponseWriter, r *http.Request){//2
        fmt.Fprintf(w, "Sloths rule!")
    })
    r.PathPrefix("/images/").Handler(http.StripPrefix("/images/",        //3
                                       http.FileServer(
                                         http.Dir("public/images"))))
    r.PathPrefix("/").HandlerFunc(serveHelloWorld)                       //4

    return r
}
Here's what's going on:

1. We create a Gorilla Router with mux.NewRouter()

2. Like with the standard http package, we add routes to a Gorilla router with Handle and HandleFunc.

3. Unlike in Go http, in Gorilla a basic path (with no parameters) only matches that exact URL even if the path ends with a /. So to serve our images, instead of doing r.Handle(“/images/” … we pass “/images/” into r.PathPrefix to handle requests whose URLs start with /images/.

4. We use PathPrefix again with the “/” path to make a catch-all route for serveHelloWorld

To use our new router, replace the main function in server.go with
func main(){
    router := initRouter()
    http.Handle("/", router)
    http.ListenAndServe(":1123", nil)
}
Since "/" in Go http is a catch-all route, so http.Handle("/", router) tells our server to have the Gorilla router handle our requests.

Note that we are able to pass a Gorilla mux Router into http.Handle. Why is that? Remember how Handle takes in a Handler? Well Gorilla Routers are a type of Handler so they can be used in http.Handle.

URL parameters and regular expressions

Now that we've started making our Gorilla router, let's add some routes with parameters. But before we do that, if you're following along in Express, run npm install html-escape and in server.js add the line

var htmlEscape = require('html-escape');

We need to do this because we are working with user input that we're putting into our HTML, so we need a way to sanitize our user input to prevent cross-site scripting vulnerabilities.

Now for the Express version add these routes before the catch-all “/” route:
app.use('/tea/:flavor', function(req, res){
  var tea = req.params.flavor + ' tea';                            //1
  var html = '<img src="/images/sloth.jpg" /><br />'+
             '<h2>I could use some ' + htmlEscape(tea) + '!</h2>';
  res.send(html);                                                  //2
});

app.use(/\/(coffee)+/, function(req, res){                         //3
  var html = '<img src="/images/lemur.jpg" /><br />'+
             '<h2>Lemurs = sloths that had too much coffee!</h2>';
  res.send(html);
});
1. In this first route :flavor is a route parameter which matches anything after /tea/. We can get and use whatever is passed into that parameter with req.params.

2. After we get the text in :flavor we insert it into some HTML (with htmlEscape) and serve it. So localhost:1123/tea/hibiscus serves a picture of a sloth saying “I could use some hibiscus tea!”

3. For our regular expression route, we are using the regular expression /\/(coffee)+/, which matches any route that says “coffee” one or more times to serve a picture of a lemur saying “Lemurs = sloths that had too much coffee!”.

Now let's add our tea route in Gorilla. For the HTML escaping Go has an html package built into the standard library so to get it just add "html" to the list of packages in the import statement in router.go.

For our tea route in router.go add this function before the catch-all "/" route in initRouter:
r.HandleFunc("/tea/{flavor}", func(w http.ResponseWriter, r *http.Request){//1
    params := mux.Vars(r)                                                  //2
    tea    := html.EscapeString(params["flavor"]) + " tea"
    html   := `<body><img src="/images/sloth.jpg" /><br />` +              //3
                `<h2>I could use some ` + tea + `!</h2></body>`
    fmt.Fprintf(w, html)
})
1. We give our path a flavor parameter with curly braces.

2. We get a request's route parameters for by passing the Request into the mux.Vars function, and escape the route parameters with html.EscapeString.

3. Like in the Express version we then put our data into some HTML to serve. Note, by the way, that I made the HTML string with backtick ` quotes; in Go there are no single-quoted strings so if you don't want to escape double quotes in a string, you can put a string between backticks instead of double quotes.

Now if you run go install and then run gorilla-tutorial and go to localhost:1123/tea/hibiscus you should get:

 
Here's our regular expression route:
r.HandleFunc(`/{drink:(coffee)+}`, func(w http.ResponseWriter, r *http.Request){
    html := `<body><img src="/images/lemur.jpg" /><br />`+
            `<h2>Lemurs = sloths that had too much coffee!</h2></body>`
    fmt.Fprintf(w, html)
})
The syntax in Gorilla mux for a regular expression route parameter is to have the parameter's name (drink), then a colon, then the regular expression. Now if you run go install and then run gorilla-tutorial and go to localhost:1123/coffeecoffeecoffeecoffee you should get:


Handling POST requests in Go

So far we've only been working with GET requests, so to finish off this tutorial let's try some POST requests. We are going to make a simple HTML form for ordering at a coffee shop.

For Express, we will need a way to parse POST data so for that we'll use body-parser. If you're following along in Express, run npm install body-parser and at the beginning of server.js add the line

var bodyParser = require('body-parser');

And then add these Express routes before the catch-all route:
app.get('/coffee-shop', function(req, res){                                    //1
  res.send('<form action="/order" method="POST">'+
             'Your name <input type="text" name="name" /><br/ >'+
             'Your beverage order <input type="text" name="beverage" /><br/ >'+
             '<input type="submit" value="Submit"/>'+
           '</form>');
});

app.post('/order', bodyParser.urlencoded({extended:true}), function(req, res){ //2
  var name     = htmlEscape(req.body.name),
      beverage = htmlEscape(req.body.beverage);
  res.send('<h1>One '+ beverage + ' coming right up ' + name + '!</h1>');
});
1. The server handles GET requests to /coffee-shop by serving a form asking for your name and beverage order. The form sends its data to /order.

2. The server handles POST requests to /order by parsing the form's data with body-parser, converting the data to HTML-escaped strings, and then serving the message “One (beverage) coming right up, (name)!”

Note that instead of app.use, we use app.get for /coffee-shop and app.post for /order, so the /coffee-shop route only handles GET requests and /order only handles POST requests.

For the Gorilla implementation, include “log” in the list of packages you import in router.go and add this code before the catch-all route:
r.HandleFunc("/coffee-shop", func(w http.ResponseWriter, r *http.Request){
    html := `<body><form action="order" method="POST">`+
              `Your name <input type="text" name="name"><br />`+
              `Your beverage order <input type="text" name="beverage"><br />`+
              `<input type="submit" value="Submit">`+
            `</form></body>`
    fmt.Fprintf(w, html)
}).Methods("GET")                                                        //1

r.HandleFunc("/order", func(w http.ResponseWriter, r *http.Request){
    err := r.ParseForm()                                                 //2
    if err != nil {
        log.Fatal(err.Error())
    }

    name     := html.EscapeString(r.Form.Get("name"))                    //3
    beverage := html.EscapeString(r.Form.Get("beverage"))
    html := `<body><h1>One ` + beverage + ` coming right up, ` + name + `!</h1></body>`
    fmt.Fprintf(w, html)
}).Methods("POST")                                                       //4
Overall the code is similar to the Express version. Here's what's going on:

1. We serve the form in /coffee-shop with r.HandleFunc. To make /coffee-shop only handle GET requests, after the Router.HandleFunc() call we add .Methods("GET"). Methods is a function in Gorilla for specifying which HTTP methods a route handles.

2. Request.ParseForm() in Go's standard http package parses the form data and puts it in an object in the Request called Form.

3. We then convert the Form data to HTML-escaped strings and serve the data in our HTML message.

4. To make /order only handle POST requests, we use .Methods("POST").

So if in localhost:1123/coffee-shop you fill in the form with the name “&y” and the beverage “dirty chai”


You should get:


But if you try to go directly to localhost:1123/order in your browser, the /order route will not handle your GET request, so your request will go to the “/” catch-all route.


In this tutorial we covered how to serve basic routes in Go's standard http package and Gorilla mux, using FileServer, serving URL parameters and regular expressions in Gorilla, and working with POST requests in Gorilla. For the next tutorial, we will be taking a closer look at how Go http Handlers work and use that to make Express-like middleware. Stay slothful!


Image credits:
  • The author of the picture of the sloth in the examples is Stefan Laube and the picture is public domain.
  • The author of the picture of the lemur in the examples is Alex Dunkel and the picture is licensed under CC BY 3.0.
  • The author of the picture of the sloth at the end of the tutorial is Roy Luck and the picture is licensed under CC BY 2.0.

No comments :

Post a Comment