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:
- 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"
- 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.
- 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...)
- 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()
- 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.