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)

Introducing gorilla/mux – a powerful HTTP router

The word Mux stands for the multiplexer. gorilla/mux is a multiplexer designed to multiplex HTTP routes (URLs) to different handlers. Handlers are the functions that can handle the given requests. gorilla/mux is a wonderful package for writing beautiful routes for our API servers.

gorilla/mux provides tons of options to control how routing is done to your web application. It allows a lot of features, such as:

  • Path-based matching
  • Query-based matching
  • Domain-based matching
  • Sub-domain-based matching
  • Reverse URL generation

Which type of routing to use depends on the types of clients requesting the server. We first see the installation and then a basic example to understand the gorilla/mux package.

Installing gorilla/mux

Follow these steps to install the mux package:

  1. You need to run this command in the Terminal (Mac OS X and Linux):
go get -u github.com/gorilla/mux
  1. If you get any errors saying package github.com/gorilla/mux: cannot download, $GOPATH not set. For more details see--go help gopath, set the $GOPATH environment variable using the following command:
export GOPATH=~/go
  1. As we discussed in Chapter 1, Getting Started with REST API Development, all the packages and programs go into GOPATH. It has three folders: bin, pkg, and src. Now, add GOPATH to the PATH variable to use the installed bin files as system utilities that have no ./executable style. Refer to the following command:
PATH="$GOPATH/bin:$PATH"
  1. These settings stay until you turn off your machine. So, to make it a permanent change, add the preceding line to your bash/zsh profile file:
vi ~/.profile
(or)
vi ~/.zshrc

We can import gorilla/mux in our programs, like this:

import "github.com/gorilla/mux"

Now, we are ready to go. Assuming gorilla/mux is installed, we can now explore its basics.

Fundamentals of gorilla/mux

The gorilla/mux package primarily helps to create routers, similar to httprouter. The difference between both is the attachment of a handler function to a given URL. If we observe, the gorilla/mux way of attaching a handler is similar to that of basic ServeMux. Unlike httprouter, gorilla/mux wraps all the information of an HTTP request into a single request object.

The three important tools provided in the gorilla/mux API are:

  • The mux.NewRouter method
  • The *http.Request object
  • The *http.ResponseWriter object

The NewRouter method creates a new router object. That object basically maps a route to a function handler. gorilla/mux passes a modified *http.Request and *http.ResponseWriter object to the function handler. These special objects have lots of additional information about headers, path parameters, request body, and query parameters. Let us explain how to define and use different routers in gorilla/mux with two common types:

  • Path-based matching
  • Query-based matching

Path-based matching

A path parameter in the URL of an HTTP GET request looks like this:

https://example.org/articles/books/123

Since it is passed after the base URL and API endpoint, in this case https://example.org/articles/, they are called path parameters. In the preceding URL, books and 123 are path parameters. Let us see an example of how to create routes that can consume data supplied as path parameters. Follow these steps:

  1. Create a new file for our program at the following path:
touch -p $GOPATH/src/github.com/git-user/chapter2/muxRouter/main.go
  1. The idea is to create a new router, mux.NewRouter, and use it as a handler with in-built http.Server. We can attach URL endpoints to handler functions on this router object. The URL endpoints attached can also be regular expressions. The simple program to collect path parameters from a client HTTP request and return back the same looks like this:
package main

import (
"fmt"
"log"
"net/http"
"time"

"github.com/gorilla/mux"
)

func ArticleHandler(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)
w.WriteHeader(http.StatusOK)
fmt.Fprintf(w, "Category is: %v\n", vars["category"])
fmt.Fprintf(w, "ID is: %v\n", vars["id"])
}

func main() {
r := mux.NewRouter()
r.HandleFunc("/articles/{category}/{id:[0-9]+}", ArticleHandler)
srv := &http.Server{
Handler: r,
Addr: "127.0.0.1:8000",
WriteTimeout: 15 * time.Second,
ReadTimeout: 15 * time.Second,
}
log.Fatal(srv.ListenAndServe())
}
  1. Now run the server using the following command in a shell:
go run $GOPATH/src/github.com/git-user/chapter2/muxRouter/main.go
  1. Make a curl request from another shell and we can get the output as follows:
curl http://localhost:8000/articles/books/123

Category is: books ID is: 123

This example shows how to match and parse path parameters. There is one more popular way to collect variable information from an HTTP request and that is with query parameters. In the next section, we see how to create routes that match HTTP requests with query parameters.

Query-based matching

Query parameters are variables that get passed along with the URL in an HTTP request. This is what we commonly see in a REST GET request. The gorilla/mux route can match and collect query parameters. See this following URL, for example:

http://localhost:8000/articles?id=123&category=books

It has id and category as query parameters. All query parameters begin after the ? character.

Let us modify our copy of our previous example into a new one with the name queryParameters/main.go. Modify the route object to point to a new handler called QueryHandler, like this:

// Add this in your main program
r := mux.NewRouter()
r.HandleFunc("/articles", QueryHandler)

In QueryHandler, we can use request.URL.Query() to obtain query parameters from the HTTP request. QueryHandler looks like this:

// QueryHandler handles the given query parameters
func QueryHandler(w http.ResponseWriter, r *http.Request) {
queryParams := r.URL.Query()
w.WriteHeader(http.StatusOK)
fmt.Fprintf(w, "Got parameter id:%s!\n", queryParams["id"][0])
fmt.Fprintf(w, "Got parameter category:%s!", queryParams["category"][0])
}

This program is similar to the previous example, but processes query parameters instead of path parameters.

Run the new program:

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

Fire a curl request in this format in a Terminal:

curl -X GET http://localhost:8000/articles\?id\=1345\&category\=birds

We need to escape special characters in the shell. If it is in the browser, there is no problem of escaping. The output looks like this:

Got parameter id:1345! 
Got parameter category:birds!

The r.URL.Query() function returns a map with all the parameter and value pairs. They are basically strings and, in order to use them in our program logic, we need to convert the number strings to integers. We can use Go's strconv package to convert a string to an integer, and vice versa.

We have used http.StatusOK to write a successful HTTP response. Similarly, use appropriate status codes for different REST operations. For example, 404 – Not found, 500 – Server error, and so on.

Other notable features of gorilla/mux

We have seen two basic examples. What next? The gorilla/mux package provides many handy features that makes an API developer's life easy. It gives a lot of flexibility while creating routes. In this section, we try to discuss a few important features. The first feature of interest is generating a dynamic URL with the reverse mapping technique.

In simple words, reverse mapping a URL is getting the complete API route for an API resource. Reverse mapping is quite useful when we share links to our web application or API. However, in order to create a URL from data, we should associate a Name with the gorilla/mux route. You can name a multiplexer route, like this:

r.HandlerFunc("/articles/{category}/{id:[0-9]+}", ArticleHandler).
Name("articleRoute")

Now, we can get a dynamically generated API route by using the url method:

url, err := r.Get("articleRoute").URL("category", "books", "id", "123")
fmt.Printf(url.Path) // prints /articles/books/123

If a route consists of the additional path parameters defined, we should pass that data as arguments to the URL method.

The next important feature of a URL router is path prefix. A path prefix is a wildcard route for matching all possible paths. It matches all the routes to the API server, post a root word. The general use case of path prefixes is a static file server. Then, when we serve files from a static folder, the API paths should match to filesystem paths to successfully return file content.

For example, if we define /static/ as a path prefix, every API route that has this root word as a prefix is routed to the handler attached.

These paths are matched:

  • http://localhost:8000/static/js/jquery.min.js
  • http://localhost:8000/static/index.html
  • http://localhost:8000/static/some_file.extension

Using gorilla/mux's PathPrefix and StripPefix methods, we can write a static file server, like this:

r.PathPrefix("/static/").Handler(http.StripPrefix("/static/", http.FileServer(http.Dir("/tmp/static"))))

The next important feature is strict slash. A strict slash activated on a gorilla/mux router allows a URL to redirect to the same URL with / appended at the end and vice versa.

For example, let us say we have an /articles/ route that is attached to an ArticleHandler handler:

r.StrictSlash(true)
r.Path("/articles/").Handler(ArticleHandler)

In the preceding case, strict slash is set to true. The router then redirects even /articles (without '/' at the end) to the ArticleHandler. If it is set to false, the router treats both /articles/ and /articles as different paths.

The next important feature of a URL router is to match encoded path parameters. The gorilla/mux UseEncodedPath method can be called on a router to match encoded path parameters.

A server can receive encoded paths from a few clients. We can match the encoded path parameter, we can even match the encoded URL route and forward it to the given handler:

r.UseEncodedPath()
r.NewRoute().Path("/category/id")

This can match the following URL:

http://localhost:8000/books/2

As well as this:

http://localhost:8000/books%2F2

Where %2F2 stands for /2 in encoded form.

Its pattern-matching features and simplicity push gorilla/mux as a popular choice for an HTTP router in projects. Many successful projects worldwide are already using mux for their routing needs.

We are free to define routes for our application. Since routes are entry points to any API, developers should be careful about how they process the data received from a client. Clients can be attackers too, who can inject malicious scripts into the path or query parameters. That situation is called a security vulnerability. APIs are prone to a common application vulnerability called SQL injection. In the next section, we introduce it briefly and see possible countermeasure steps.