REST is the most popular software architecture on the internet today because it is founded on well defined, strict standards and it's easy to understand and expand. More and more websites are basing their designs on top of REST. In this section, we are going to have a close look at implementing the REST architecture in Go and (hopefully) learn how to leverage it to our benefit.
The first declaration of the concept of REST (REpresentational State Transfer) was in the year 2000 in Roy Thomas Fielding's doctoral dissertation, who also just happens to be the co-founder of the HTTP protocol. It specifies the architecture's constraints and principles and anything implemented with this architecture can be called a RESTful system.
Before we understand what REST is, we need to cover the following concepts:
-
Resources
REST is the Presentation Layer State Transfer, where the presentation layer is actually the resource presentation layer.
So what are resources? Pictures, documents or videos, etc., are all examples of resources and can be located by URI.
-
Representation
Resources are specific information entities that can be shown in a variety of ways within the presentation layer. For instance, a TXT document can be represented as HTML, JSON, XML, etc; an image can be represented as jpg, png, etc.
URIs are used to identify resources, but how do we determine its specific manifestations? You are referred to the Accept and Content-Type in an HTTP request header; these two fields describe the presentation layer.
-
State Transfer
An interactive process is initiated between client and server each time you visit any page of a website. During this process, certain data related to the current page state need to be saved. However, you'll recall that HTTP is a stateless protocol! It's obvious that we need to save this client state on our server side. It follows that if a client modifies some data and wants to persist the changes, there must be a way to inform the server side about the new state.
Most of the time, clients inform servers of state changes using HTTP. They have four operations with which to do this:
-GET is used to obtain resources -POSTs is used to create or update resources -PUT updates resources -DELETE deletes resources
To summarize the above:
- (1)Every URI represents a resource.
- (2)There is a representation layer for transferring resources between clients and servers.
- (3)Clients use four HTTP methods to implement "Presentation Layer State Transfer", allowing them to operate on remote resources.
The most important principle of web applications that implement REST is that the interaction between clients and servers are stateless; every request should encapsulate all of the required information. Servers should be able to restart at any time without the clients being notified. In addition, requests can be responded by any server of the same service, which is ideal for cloud computing. Lastly, because it's stateless, clients can cache data for improving performance.
Another important principle of REST is system delamination, which means that components in one layer have no way of interacting directly with components in other layers. This can limit system complexity and encourage independence in the underlying components.
Figure 8.5 REST architecture
When RESTful constraints are judiciously abided by, web applications can be scaled to accommodate massive numbers of clients. Using the REST architecture can also help reduce delays between clients and servers, simplify system architecture and improve the visibility of sub-system end points.
Figure 8.6 REST's expansibility.
Go doesn't have direct support for REST, but since RESTful web applications are all HTTP-based, we can use the net/http
package to implement it on our own. Of course, we will first need to make some modifications before we are able to fully implement REST.
REST uses different methods to handle resources, depending on the interaction that's required with that resource. Many existing applications claim to be RESTful but they do not actually implement REST. I'm going to categorize these applications into several levels depends on which HTTP methods they implement.
Figure 8.7 REST's level.
The picture above shows three levels that are currently implemented in REST. You may not choose to follow all the rules and constraints of REST when developing your own applications because sometimes its rules are not a good fit for all situations. RESTful web applications use every single HTTP method including DELETE
and PUT
, but in many cases, HTTP clients can only send GET
and POST
requests.
- HTML standard allows clients send
GET
andPOST
requests through links and forms. It's not possible to sendPUT
orDELETE
requests without AJAX support. - Some firewalls intercept
PUT
andDELETE
requests and clients have to use POST in order to implement them. Fully RESTful services are in charge of finding the original HTTP methods and restoring them.
We can simulate PUT
and DELETE
requests by adding a hidden _method
field in our POST requests, however these requests must be converted on the server side before they are processed. My personal applications use this workflow to implement REST interfaces. Standard RESTful interfaces are easily implemented in Go, as the following example demonstrates:
package main
import (
"fmt"
"github.com/julienschmidt/httprouter"
"log"
"net/http"
)
func Index(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
fmt.Fprint(w, "Welcome!\n")
}
func Hello(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
fmt.Fprintf(w, "hello, %s!\n", ps.ByName("name"))
}
func getuser(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
uid := ps.ByName("uid")
fmt.Fprintf(w, "you are get user %s", uid)
}
func modifyuser(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
uid := ps.ByName("uid")
fmt.Fprintf(w, "you are modify user %s", uid)
}
func deleteuser(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
uid := ps.ByName("uid")
fmt.Fprintf(w, "you are delete user %s", uid)
}
func adduser(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
// uid := r.FormValue("uid")
uid := ps.ByName("uid")
fmt.Fprintf(w, "you are add user %s", uid)
}
func main() {
router := httprouter.New()
router.GET("/", Index)
router.GET("/hello/:name", Hello)
router.GET("/user/:uid", getuser)
router.POST("/adduser/:uid", adduser)
router.DELETE("/deluser/:uid", deleteuser)
router.PUT("/moduser/:uid", modifyuser)
log.Fatal(http.ListenAndServe(":8080", router))
}
This sample code shows you how to write a very basic REST application. Our resources are users, and we use different functions for different methods. Here, we imported a third-party package called github.com/julienschmidt/httprouter
. We've already covered how to implement a custom router in previous chapters -the julienschmidt/httprouter
package implements some very convenient router mapping rules that make it very convenient for implementing RESTful architecture. As you can see, REST requires you to implement different logic for different HTTP methods of the same resource.
REST is a style of web architecture, building on past successful experiences with WWW: statelessness, resource-centric, full use of HTTP and URI protocols and the provision of unified interfaces. These superior design considerations have allowed REST to become the most popular web services standard. In a sense, by emphasizing the URI and leveraging early Internet standards such as HTTP, REST has paved the way for large and scalable web applications. Currently, the support that Go has For REST is still very basic. However, by implementing custom routing rules and different request handlers for each type of HTTP request, we can achieve RESTful architecture in our Go webapps.