A proper introduction to REST is out of scope for this recipe. Countless materials describing the REST architectural style are available both online and offline. Wikipedia's entry on Representational State Transfer at http://en.wikipedia.org/wiki/Representational_State_Transfer provides a compact description of REST's main concepts, its limits, and the guiding principles to design RESTful applications. A more tongue-in-cheek introduction to REST can be found on Ryan Tomayko's website at http://tomayko.com/writings/rest-to-my-wife.
There are several ways to execute a REST request using Groovy. In this recipe, we are going to show how to execute a GET
operation against a RESTful resource using RESTClient
from the HTTPBuilder
project (http://groovy.codehaus.org/modules/http-builder/doc/index.html). HTTPBuilder
is a wrapper for Apache HttpClient
, with some Groovy syntactical sugar thrown on top.
For this recipe, we will use the RESTful API exposed by the Bing Map Service (http://www.bing.com/maps/). The API allows performing tasks such as, creating a static map with pushpins, geocoding an address, retrieving imagery metadata, or creating a route.
In order to access the service, an authentication key must be obtained through a simple registration process (see https://www.bingmapsportal.com). The key has to be passed with each REST request.
The following steps will use the Location API, through which we will find latitude and longitude coordinates for a specific location:
@Grab( group='org.codehaus.groovy.modules.http-builder', module='http-builder', version='0.6' ) import static groovyx.net.http.ContentType.JSON import static groovyx.net.http.ContentType.XML import groovyx.net.http.RESTClient class LocationFinder { static final KEY = '...' static final URL = 'http://dev.virtualearth.net' static final BASE_PATH = '/REST/v1/Locations/' def printCityCoordinates(countryCode, city) { def mapClient = new RESTClient(URL) def path = "${countryCode}/${city}" def response = mapClient.get( path: "${BASE_PATH}${path}", query: [key: KEY] ) assert response.status == 200 assert response.contentType == JSON.toString() println response. data. resourceSets. resources. point. coordinates } }
LocationFinder map = new LocationFinder() map.printCityCoordinates('fr', 'paris') map.printCityCoordinates('uk', 'london')
[[[48.856929779052734, 2.3412001132965088]]] [[[51.506320953369141, -0.12714000046253204]]]
The RESTClient
class greatly simplifies dealing with REST resources. It leverages the automatic content type parsing and encoding, which essentially parses the response for us and converts it into the proper type. The get
method accepts a Map
of arguments, including the actual path to the resource, the request content type, query, and headers. For a full list of arguments, refer to the Javadoc at http://groovy.codehaus.org/modules/http-builder/apidocs/groovyx/net/http/HTTPBuilder.RequestConfigDelegate.html#setPropertiesFromMap(java.util.Map).
In the previous example, the response is automatically parsed and transformed into a nested data structure, which makes dealing with the response values a very simple affair.
By default, all request methods return an HttpResponseDecorator
instance, which provides convenient access to the response headers and the parsed response body. The response body is parsed based on the content type. HTTPBuilder's default response handlers function in the same way: the response data is parsed (or buffered in the case of a binary or text response) and returned from the request method. The response either carries no data, or it is expected to be parsable and transformed into some object, which is always accessible through the
getData()
method.
The actual data returned by the service looks like the following code snippet:
{ "authenticationResultCode": "ValidCredentials", "brandLogoUri": "...", "copyright": "...", "resourceSets": [ { "estimatedTotal": 1, "resources": [ { "name": "Paris, Paris, France", "point": { "type": "Point", "coordinates": [ 48.856929779053, 2.3412001132965 ] }, ... } ] } ], "statusCode": 200, "statusDescription": "OK", "traceId": "..." }