With the concepts we have built upto now, let us write our first basic REST service. This service takes the number range (1-10) from the client and returns its Roman string. Very primitive, but better than Hello World.
Design:
Our REST API should take an integer number from the client and serve back the Roman equivalent.
The block of the API design document may look like this:
HTTP Verb | PATH | Action | Resource |
|
| show |
|
Implementation:
Now we are going to implement the preceding simple API step-by-step.
Note
Code for this project is available at https://github.com/narenaryan/gorestful.
As we previously discussed, you should set the GOPATH
first. Let us assume the GOPATH
is /home/naren/go
. Create a directory called romanserver
in the following path. Replace narenaryan with your GitHub username (this is just a namespace for the code belonging to different users):
mkdir -p $GOPATH/src/github.com/narenaryan/romanserver
Our project is ready. We don't have any database configured yet. Create an empty file called main.go
:
touch $GOPATH/src/github.com/narenaryan/romanserver/main.go
Our main logic for the API server goes into this file. For now, we can create a data file which works as a data service for our main program. Create one more directory for packaging the Roman numeral data:
mkdir $GOPATH/src/github.com/narenaryan/romanNumerals
Now, create an empty file called data.go
in the romanNumerals
directory. The src
directory structure so far looks like this:
;
Now let us start adding code to the files. Create data for the Roman numerals:
// data.go package romanNumerals var Numerals = map[int]string{ 10: "X", 9: "IX", 8: "VIII", 7: "VII", 6: "VI", 5: "V", 4: "IV", 3: "III", 2: "II", 1: "I", }
We are creating a map called Numerals. This map holds information for converting a given integer to its Roman equivalent. We are going to import this variable into our main program to serve the request from the client.
Open main.go
and add the following code:
// main.go package main import ( "fmt" "github.com/narenaryan/romanNumerals" "html" "net/http" "strconv" "strings" "time" ) func main() { // http package has methods for dealing with requests http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { urlPathElements := strings.Split(r.URL.Path, "/") // If request is GET with correct syntax if urlPathElements[1] == "roman_number" { number, _ := strconv.Atoi(strings.TrimSpace(urlPathElements[2])) if number == 0 || number > 10 { // If resource is not in the list, send Not Found status w.WriteHeader(http.StatusNotFound) w.Write([]byte("404 - Not Found")) } else { fmt.Fprintf(w, "%q", html.EscapeString(romanNumerals.Numerals[number])) } } else { // For all other requests, tell that Client sent a bad request w.WriteHeader(http.StatusBadRequest) w.Write([]byte("400 - Bad request")) } }) // Create a server and run it on 8000 port s := &http.Server{ Addr: ":8000", ReadTimeout: 10 * time.Second, WriteTimeout: 10 * time.Second, MaxHeaderBytes: 1 << 20, } s.ListenAndServe() }
Note
Always use the Go fmt tool to format your Go code.
Usage example: go fmt github.com/narenaryan/romanserver
Now, install this project with the Go command install
:
go install github.com/narenaryan/romanserver
This step does two things:
- Compiles the package
romanNumerals
and places a copy in the$GOPATH/pkg
directory - Places a binary in the
$GOPATH/bin
We can run the preceding API server as this:
$GOPATH/bin/romanserver
The server is up and running on http://localhost:8000
. Now we can make a GET
request to the API using a client like Browser
or the CURL
command. Let us fire a CURL
command with a proper API GET
request.
Request one is as follows:
curl -X GET "http://localhost:8000/roman_number/5" # Valid request
The response is as follows:
HTTP/1.1 200 OK Date: Sun, 07 May 2017 11:24:32 GMT Content-Length: 3 Content-Type: text/plain; charset=utf-8 "V"
Let us try a few incorrectly formed requests.
Request two is as follows:
curl -X GET "http://localhost:8000/roman_number/12" # Resource out of range
The response is as follows:
HTTP/1.1 404 Not Found Date: Sun, 07 May 2017 11:22:38 GMT Content-Length: 15 Content-Type: text/plain; charset=utf-8 404 - Not Found
Request three is as follows:
curl -X GET "http://localhost:8000/random_resource/3" # Invalid resource
The response is as follows:
"HTTP/1.1 400 Bad request
Date: Sun, 07 May 2017 11:22:38 GMT
Content-Length: 15
Content-Type: text/plain; charset=utf-8
400 - Bad request
Our little Roman numerals API is doing the right thing. The right status codes are being returned. That is the point all API developers should keep in mind. The client should be informed why something went wrong.
We just updated the empty files in one single go and started running the server. Let me now explain each and every piece of the file main.go
:
- Imported a few packages.
github.com/narenaryan/romanNumerals
is the data service we created before. net/http
is the core package we used to handle an HTTP request through itsHandleFunc
function. That function's arguments arehttp.Request
andhttp.ResponseWriter
. Those two deal with the request and response of an HTTP request.r.URL.Path
is the URL path of the HTTP request. For the CURL Request one, it is/roman_number/5
. We are splitting this path and using the second argument as a resource and the third argument as a value to get the Roman numeral. TheSplit
function is in a core package calledstrings
.- The
Atoi
function converts an alphanumeric string to an integer. For the numerals map to consume, we need to convert the integer string to an integer. TheAtoi
function comes from a core package calledstrconv
. - We use
http.StatusXXX
to set the status code of the response header. TheWriteHeader
andWrite
functions are available on the response object for writing the header and body, respectively. - Next, we created an HTTP server using
&http
while initializing a few parameters like address, port, timeout, and so on. - The
time
package is used to define seconds in the program. It says, after 10 seconds of inactivity, automatically return a 408 request timeout back to the client. EscapeString
escapes special characters to become valid HTML characters. For example, Fran & Freddie's becomesFran & Freddie's"
.- Finally, start the server with the
ListenAndServe
function. It keeps your web server running until you kill it.
Gulp is a nice tool for creating workflows. A workflow is a step-by-step process. It is nothing but a task streamlining application. You need NPM and Node installed on your machine. We use Gulp to watch the files and then update the binary and restart the API server. Sounds cool, right?
The supervisor is an application to reload your server whenever the application gets killed. A process ID will be assigned to your server. To restart the app properly, we need to kill the existing instances and restart the application. We can write one such program in Go. But in order to not reinvent the wheel, we are using a popular program called supervisord.
Sometimes your web application may stop due to operating system restarts or crashes. Whenever your web server gets killed, it is supervisor's job to bring it back to life. Even the system restart cannot take your web server away from the customers. So, strictly use supervisord for your app monitoring.
We can easily install supervisord on Ubuntu 16.04, with the apt-get
command:
sudo apt-get install -y supervisor
This installs two tools, supervisor
and supervisorctl
. supervisorctl
is intended to control the supervisord and add tasks, restart tasks, and so on.
On macOS X, we can install supervisor
using the brew
command:
brew install supervisor
Now, create a configuration file at:
/etc/supervisor/conf.d/goproject.conf
You can add any number of configuration files, and supervisord treats them as separate processes to run. Add the following content to the preceding file:
[supervisord] logfile = /tmp/supervisord.log
[program:myserver] command=$GOPATH/bin/romanserver autostart=true autorestart=true redirect_stderr=true
By default, we have a file called .supervisord.conf
at /etc/supervisor/
. Look at it for more reference. In macOS X, the same file will be located at /usr/local/etc/supervisord.ini
.
Coming to the preceding configuration:
- The
[supervisord]
section tells the location of the log file for supervisord [program:myserver]
is the task block which traverses to the given directory and executes the command given
Now we can ask our supervisorctl
to re-read the configuration and restart the tasks (process). For that, just say:
supervisorctl reread
supervisorctl update
Then, launch supervisorctl
with the command:
supervisorctl
You will see something like this:
Since we named our romanserver myserver
in the supervisor configuration file, we can start, stop, and restart that program from supervisorctl
.