|
|
|
Resource (REST) handling
Project Zero simplifies the task of implementing RESTful resources. The following sections of this article provide information about handling REST resources:
RESTful design
RESTful designs often make use of collections. A collection is a simple model for manipulating
a set of resources. Collections have member items that you can add, remove, update and delete. You can also get a list of members in the collection. For example:
HTTP Method URI Description
----------- --- ----------------
GET /people list members
POST /people create member
GET /people/1 retrieve member
PUT /people/1 update member
DELETE /people/1 delete member
Simple convention for resources
Zero supports the collection model natively within the <apphome>/app/resources virtualized directory. Each script within the resources directory represents a resource handler, which implements the collection and member operations.
Resource handlers are accessed via a simple URL convention, based upon the following pattern:
/resources/<collection name>[/<member identifier>[/<path info>]]
For example, a request to /resources/people/1 has the collection name "people" and member identifier "1". The collection name identifies the resource handler (here, <apphome>/app/resources/people.groovy) and the value of member identifier is provided as a request parameter with name "<collection name>Id" (here, zget("/request/params/peopleId") == 1).
The resource handler is a typical Zero event handler, designed to handle resource CRUD events. HTTP methods are mapped into collection and member events according to the following:
| Resource | GET | PUT | POST | DELETE |
| Collection | list | putCollection | create | deleteCollection |
| Member | retrieve | update | postMember | delete |
Following is a more detailed example of request patterns and corresponding events and parsed data:
| HTTP Method | URI | ...invokes method in app/resources/people.groovy | ...with event data |
GET | /resources/people | onList() | |
POST | /resources/people | onCreate() | |
GET | /resources/people/1/accounts | onRetrieve() | zget("/request/params/peopleId")==1 zget("/event/pathInfo")==/accounts |
PUT | /resources/people/1 | onUpdate() | zget("/request/params/peopleId")==1 |
DELETE | /resources/people/1 | onDelete() | zget("/request/params/peopleId")==1 |
PHP Refer to Resource (REST) handling in PHP for more information on developing resource handlers using PHP.
Sample collection handler
Showing a Groovy implementation:
<apphome>/app/resources/people.groovy
def onList() {
// Get configured DataManager for data access
def data = zero.data.groovy.Manager.create('peopleDB')
def result = data.queryArray('SELECT * FROM people')
// Serialize list to JavaScript Object Notation format
request.view = 'JSON'
request.json.output = result
render()
}
def onCreate() {
// Convert entity to JSON object
def emp = zero.json.Json.decode(request.input[])
def data = zero.data.groovy.Manager.create('peopleDB')
def memberId = data.insert(
"""INSERT INTO people (firstname, lastname)
VALUES ($emp.firstname, $emp.lastname)""", ['id'])
// Set a Location header with URI to the new record
locationUri = getRequestedUri(false) + '/' + memberId
request.headers.out.Location = locationUri
request.status = HttpURLConnection.HTTP_NO_CONTENT
}
def onRetrieve() {
// Member id is parsed from the path
String id = request.params.peopleId[]
def data = zero.data.groovy.Manager.create('peopleDB')
def person = data.queryFirst("SELECT * FROM people WHERE id=$id")
if(person!= null) {
// Use ViewEngine JSON rendering
request.view='JSON'
request.json.output = person
render()
} else {
// Error handling
request.status = HttpURLConnection.HTTP_NOT_FOUND
request.error.message = "username $username not found."
request.view = 'error'
render()
}
}
def onUpdate() {
def emp = zero.json.Json.decode(request.input[])
def data = zero.data.groovy.Manager.create('personDB')
data.update("UPDATE employees "
+ "SET firstname=$emp.firstname, lastname=$emp.lastname "
+ "WHERE id=$emp.id")
request.status = HttpURLConnection.HTTP_NO_CONTENT
}
def onDelete() {
def id = request.params.peopleId[]
def data = zero.data.groovy.Manager.create('personDB')
data.update("DELETE FROM people WHERE id=$id");
request.status = HttpURLConnection.HTTP_NO_CONTENT
}
Nested resources
By default, resources in Zero are presumed to be flat in terms of the URL pattern. Resources can be nested by specifying the valid relationships in a bonding file associated with the resource handler. For example, the bonding file associated with employees.groovy would be employees.bnd. If the bonding file is present within the app/resources virtualized directory, then the content specifies the valid access patterns for invoking the resource.
For example, consider an example of nested resources:
/resources/people/<peopleId>/accounts/<accountsId>
Although it is possible to handle this request with the people resource handler and pathInfo, the implementation may be better factored by implementing the account resource directly as shown in the following example:
<apphome>
+ app/
+ resources/
- people.groovy
- accounts.groovy
- accounts.bnd
accounts.bnd is a text file that contains one nesting pattern per line. The nesting pattern is a slash-delimited list of resource names. The hash mark (#) indicates a comment line.
Example: bonding file
# Nesting relationships for the account resource
people/accounts
When invoked as a nested resource, member ids of parent resources are provided to the target resource handler as query parameters named as <collectionName>Id. The following table provides an example.
Note that the patterns in a bonding file are the only ways to invoke that resource. In the above example, a request for /resources/accounts would result in a 404 because accounts does not appear as a pattern. To also support /resources/accounts, then the bonding file could be changed to:
# Nesting relationships for the account resource
accounts
people/accounts
Hierarchies of flat resources
Another pattern of RESTful design uses flat resources and query parameters to implement hierarchical relationships.
The example of nested resources:
/resources/people/<peopleId>/accounts/<accountsId>
could be recast with flat resources and query parameters as:
/resources/accounts/<accountsId>?people=<peopleId>
Because Zero provides member ids of parent collections as query parameters in the nested resources pattern, just as you would receive in this pattern, the handler implementation for a given resource could be identical for both patterns.
The following table provides an example of this pattern.
Generating and displaying REST API tables
By default, all Zero applications have the ability to display API documentation for their RESTful resources; once your Zero application is running, you can visit http://localhost:8080/resources/docs to see an index of all RESTful resources in an application and REST tables that describe the syntax and semantics of their HTTP operations. You can learn more by reading about Zero's RESTdoc tool.
|