Book Image

Hands-On RESTful Web Services with Go - Second Edition

By : Naren Yellavula
Book Image

Hands-On RESTful Web Services with Go - Second Edition

By: Naren Yellavula

Overview of this book

Building RESTful web services can be tough as there are countless standards and ways to develop API. In modern architectures such as microservices, RESTful APIs are common in communication, making idiomatic and scalable API development crucial. This book covers basic through to advanced API development concepts and supporting tools. You’ll start with an introduction to REST API development before moving on to building the essential blocks for working with Go. You’ll explore routers, middleware, and available open source web development solutions in Go to create robust APIs, and understand the application and database layers to build RESTful web services. You’ll learn various data formats like protocol buffers and JSON, and understand how to serve them over HTTP and gRPC. After covering advanced topics such as asynchronous API design and GraphQL for building scalable web services, you’ll discover how microservices can benefit from REST. You’ll also explore packaging artifacts in the form of containers and understand how to set up an ideal deployment ecosystem for web services. Finally, you’ll cover the provisioning of infrastructure using infrastructure as code (IaC) and secure your REST API. By the end of the book, you’ll have intermediate knowledge of web service development and be able to apply the skills you’ve learned in a practical way.
Table of Contents (16 chapters)

Understanding httprouter – a lightweight HTTP router

httprouter, as the name suggests, routes the HTTP requests to particular handlers. httprouter is a well-known package in Go for creating simple routers with an elegant API. The developers coming from the Python/Django community are very familiar with a full-blown URL dispatcher in the Django framework. httprouter provides similar features:

  • Allows variables in the route paths
  • Matches the REST methods (GET, POST, PUT, and so on)
  • No compromise of performance

We are going to discuss these qualities in more detail in the following section. Before that, there are a few noteworthy points that make httprouter an even better URL router:

  • httprouter plays well with the in-built http.Handler
  • httprouter explicitly says that a request can only match to one route or no route
  • The router's design encourages building sensible, hierarchical RESTful APIs
  • You can build simple and efficient static file servers

In the next section, we see the installation of httprouter and its basic usage.

Installing httprouter

httprouter is an open source Go package and can be installed using the go get command. Let us see the installation and basic usage in the steps given as follows:

  1. Install httprouter using this command:
go get github.com/julienschmidt/httprouter

We can import the library in our source code, like this:

import "github.com/julienschmidt/httprouter"
  1. The basic usage of httprouter can be understood through an example.
    Let us write a REST service in Go that provides two things:
  • Gets the Go compiler version
  • Gets the content of a given file

We need to use a system package called os/exec to fetch the preceding details.

  1. The os/exec package has a Command function, using which we can make any system call and the function signature is this:
// arguments... means an array of strings unpacked as arguments
// in Go

cmd := exec.Command(command, arguments...)
  1. The exec.Command function takes the command and an additional argument's array. Additional arguments are the options or input for the command. It can then be executed by calling the Output function, like this:
out, err := cmd.Output()
  1. This program uses httprouter to create the service. Let us create it at the following path:
touch -p $GOPATH/src/github.com/git-user/chapter2/httprouterExample/main.go

The program's main function creates two routes and two function handlers. The responsibilities of function handlers are:

  • To get the current Go compiler version
  • To get the contents of a file

The program is trying to implement a REST service using httprouter. We are defining two routes here:

  • /api/v1/go-version
  • /api/v1/show-file/:name
package main

import (
"fmt"
"io"
"log"
"net/http"
"os/exec"

"github.com/julienschmidt/httprouter"
)


func main() {
router := httprouter.New()
router.GET("/api/v1/go-version", goVersion)
router.GET("/api/v1/show-file/:name", getFileContent)
log.Fatal(http.ListenAndServe(":8000", router))
}

:name is a path parameter. The basic Go router cannot define these special parameters. By using httprouter, we can match the REST methods. In the main block, we are matching GET requests to their respective routes.

Now we are coming to the implementation of three handler functions:

func getCommandOutput(command string, arguments ...string) string {
out, _ := exec.Command(command, arguments...).Output()
return string(out)
}

func goVersion(w http.ResponseWriter, r *http.Request, params httprouter.Params) {
response := getCommandOutput("/usr/local/go/bin/go", "version")
io.WriteString(w, response)
return
}

func getFileContent(w http.ResponseWriter, r *http.Request, params httprouter.Params) {
fmt.Fprintf(w, getCommandOutput("/bin/cat", params.ByName("name")))
}

exec.Command takes the bash command and respective options as its arguments and returns an object. That object has an Output method that returns the output result of command execution. We are utilizing this utility getCommandOutput function in both goVersion and getFileContent handlers. We use shell command formats such as go --version and cat file_name in handlers.

If you observe the code, we used /usr/local/go/bin/go as the Go executable location because it is the Go compiler location in Mac OS X. While executing exec.Command, you should give the absolute path of the executable. So, if you are working on an Ubuntu machine or Windows, use the path to your installed Go executable. On Linux machines, you can easily find that out by using the $ which go command.

Now create two new files in the same directory. These files will be served by our file server program. You can create any custom files in this directory for testing:

Latin.txt:

Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Aenean commodo ligula eget dolor. Aenean massa. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Donec quam felis, ultricies nec, pellentesque eu, pretium quis, sem. Nulla consequat massa quis enim. Donec pede justo, fringilla vel, aliquet nec, vulputate eget, arcu.

Greek.txt:

Οἱ δὲ Φοίνιϰες οὗτοι οἱ σὺν Κάδμῳ ἀπιϰόμενοι.. ἐσήγαγον διδασϰάλια ἐς τοὺς ῞Ελληνας ϰαὶ δὴ ϰαὶ γράμματα, οὐϰ ἐόντα πρὶν ῞Ελλησι ὡς ἐμοὶ δοϰέειν, πρῶτα μὲν τοῖσι ϰαὶ ἅπαντες χρέωνται Φοίνιϰες· μετὰ δὲ χρόνου προβαίνοντος ἅμα τῇ ϕωνῇ μετέβαλον ϰαὶ τὸν ϱυϑμὸν τῶν γραμμάτων. Περιοίϰεον δέ σϕεας τὰ πολλὰ τῶν χώρων τοῦτον τὸν χρόνον ῾Ελλήνων ῎Ιωνες· οἳ παραλαβόντες διδαχῇ παρὰ τῶν Φοινίϰων τὰ γράμματα, μεταρρυϑμίσαντές σϕεων ὀλίγα ἐχρέωντο, χρεώμενοι δὲ ἐϕάτισαν, ὥσπερ ϰαὶ τὸ δίϰαιον ἔϕερε ἐσαγαγόντων Φοινίϰων ἐς τὴν ῾Ελλάδα, ϕοινιϰήια ϰεϰλῆσϑαι.

Now run the program with this command. This time, instead of firing a curl command, let us use the browser as our output for GET. Windows users may not have curl as the first-hand application. They can use API testing software such as the Postman client while developing the REST API. Take a look at the following command:

go run $GOPATH/src/github.com/git-user/chapter2/httprouterExample/main.go

The output for the first GET request looks like this:

curl -X GET http://localhost:8000/api/v1/go-version

The result will be this:

go version go1.13.5 darwin/amd64

The second GET request requesting Greek.txt is:

curl -X GET http://localhost:8000/api/v1/show-file/greek.txt

Now, we will see the file output in Greek:

Οἱ δὲ Φοίνιϰες οὗτοι οἱ σὺν Κάδμῳ ἀπιϰόμενοι.. ἐσήγαγον διδασϰάλια ἐς τοὺς ῞Ελληνας ϰαὶ δὴ ϰαὶ γράμματα, οὐϰ ἐόντα πρὶν ῞Ελλησι ὡς ἐμοὶ δοϰέειν, πρῶτα μὲν τοῖσι ϰαὶ ἅπαντες χρέωνται Φοίνιϰες· μετὰ δὲ χρόνου προβαίνοντος ἅμα τῇ ϕωνῇ μετέβαλον ϰαὶ τὸν ϱυϑμὸν τῶν γραμμάτων. Περιοίϰεον δέ σϕεας τὰ πολλὰ τῶν χώρων τοῦτον τὸν χρόνον ῾Ελλήνων ῎Ιωνες· οἳ παραλαβόντες διδαχῇ παρὰ τῶν Φοινίϰων τὰ γράμματα, μεταρρυϑμίσαντές σϕεων ὀλίγα ἐχρέωντο, χρεώμενοι δὲ ἐϕάτισαν, ὥσπερ ϰαὶ τὸ δίϰαιον ἔϕερε ἐσαγαγόντων Φοινίϰων ἐς τὴν ῾Ελλάδα, ϕοινιϰήια ϰεϰλῆσϑαι.
Never give the user the power to execute system commands over the REST API. In the exec example, we made handlers use a getCommandOutput helper function to execute system commands.

The endpoint /api/v1/show-file/ we defined in the exec example is not so efficient. Using httprouter, we can build advanced and performance-optimized file servers. In the next section, we'll learn how to do that.

Building a simple static file server in minutes

Sometimes, an API can serve files. The other application of httprouter, apart from routing, is building an efficient file server. It means that we can build a content delivery platform of our own. Some clients need static files from the server. Traditionally, we use Apache2 or Nginx for that purpose. If one has to create something similar purely in Go, they can leverage httprouter.

Let us build one. From the Go server, in order to serve the static files, we need to route them through a universal route, like this:

/static/*

The plan is to use http package's Dir method to load the filesystem, and pass filesystem handler it returns to httprouter. We can use the ServeFiles function of the httprouter instance to attach a router to the filesystem handler. It should serve all the files in the given public directory. Usually, static files are kept in the /var/public/www folder on a Linux machine. Create a folder called static in your home directory:

mkdir -p /users/git-user/static

Now, copy the Latin.txt and Greek.txt files, which we created for the previous example, to the preceding static directory. After doing that, let us write the program for the file server using the following steps. You will be amazed at the simplicity of httprouter:

  1. Create a program at the following path:
touch -p $GOPATH/src/github.com/git-user/chapter2/fileServer/main.go
  1. Update the code like the following. You have to add a route that links a static file path route to a filesystem handler:
package main

import (
"log"
"net/http"

"github.com/julienschmidt/httprouter"
)

func main() {
router := httprouter.New()
// Mapping to methods is possible with HttpRouter
router.ServeFiles("/static/*filepath",
http.Dir("/Users/git-user/static"))
log.Fatal(http.ListenAndServe(":8000", router))
}

  1. Now run the server and see the output:
go run $GOPATH/src/github.com/git-user/chapter2/fileServer/main.go
  1. Open another Terminal and fire this curl request:
http://localhost:8000/static/latin.txt
  1. Now, the output will be a static file content server from our file server:
Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Aenean commodo ligula eget dolor. Aenean massa. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Donec quam felis, ultricies nec, pellentesque eu, pretium quis, sem. Nulla consequat massa quis enim. Donec pede justo, fringilla vel, aliquet nec, vulputate eget, arcu.

In the next section, we discuss about a widely used HTTP router called gorilla/mux.