A short Introduction to REST
REST (Representational State Transfer) is a set of rules, guidelines, architecture descriptions, etc., on how to use and combine different (Web) Standards to establish a system that is capable of communicating and exchanging information.
The underlying core structure of REST are resources, with each resource having a unique accessible identifier (URI). The REST architectural style defines rules on how resources should be accessed (using HTTP for example).
Accessing Resources in REST(++):
The four (primary) Methods for accessing resources, as defined by REST, are GET, POST, PUT, DELETE. For REST++ we added a new Method called EXECUTE.
GET: “Gets” the representation of a resource. Should not change the state of the resource in any way.
Example (1):
Request:
GET /students HTTP/1.1
Host: www.someserver.com
Response:
HTTP 200 OK
Content-Length: 57
<students><mnr>0123456</mnr><mnr>0123457</mnr></students>
Note: A GET Request on the “collection” resource /students returns a list containing a number identifying each student.
Example (2):
Request:
GET /students/0123456 HTTP/1.1
Host: www.someserver.com
Response:
HTTP 200 OK
<student><mnr>0123456</mnr><name>Max Muster</name></student>
Note: A GET Request on a “field” of the “collection” returns the representation of the selected field/resource.
POST: Used to update resources. Posting to a complex or “collection” resource adds a subresource.
Example:
Request:
POST /students HTTP/1.1
Host: www.someserver.com
Content-Length: 61
<student><mnr>0123458</mnr><name>Hans Hansen</name></student>
Response:
HTTP/1.1 201 Created
Location: /students/0123458
Note: When using POST, the (server side) service decides on the identifier of the resource (and if an resource is to be created at all).
PUT: Used to create (or overwrite) a resource. Puting to a complex or “collection” resource is used to (explicitly) set an element in the complex resource.
Example:
Request:
PUT /students/paula_paulus
Host: www.someserver.com
Content-Length: 62
<student><mnr>0123459</mnr><name>Paula Paulus</name></student>
Response:
HTTP/1.1 201 Created
Note: We created a new “student” resource on “/students/paula_paulus”. The resource “/students/0123459″ must not point to the same resource (and if it wasn’t created in advance, should not exist at all).
DELETE: Used to remove a resource.
Example:
Request:
DELETE /students/paula_paulus
Host: www.someserver.com
Response:
HTTP/1.1 204 No Content
EXECUTE: Performs an operation on a resource and unlike post/put does not necessarily change the state of the resource. Let’s assume our server exposes a resource “/students/calculate_average_grade”. If we were to access this resource with GET we would get the average grade of every grade of every student.
Now, if we want to get the average grade for a single student (or group of students)? Without EXECUTE there are 3 different options: a) use GET with parameters (e.g GET /students/calculate_average_grade?student=0123456) b) expose the calculate_average_grade resource for every student (e.g GET /students/0123456/calculate_average_grade) c) use POST. While a) and c) are feasible they do not conform to the REST architectural style. We could calculate the expected result from the responses of b) but this might not be possible for more complex operations.
Example:
Request:
EXECUTE /students/calculate_average_grade HTTP/1.1
Host: www.someserver.com
<students><mnr>0123456</mnr><mnr>0123457</mnr></students>
Response:
HTTP 1.1 200 OK
Content-Length: 34
<average_grade>2.5</average_grade>
Concrete Implementation of the Student Service
I’ll now show how to create the (simple) student service used in the samples above, that runs on the simple Rest++/http server I’ve created for my praktikum. (Server was written for iOS in Objective-C).
1) Create a Student class
A simple class that contains three fields: mnr, name, grades
Student.h
@interface Student: NSObject
{
NSString *name;
NSString *mnr;
NSArray *grades;
}
@property (readwrite, assign) NSString *name;
@property (readwrite, assign) NSString* mnr;
@property (readwrite, assign) NSArray *grades;
+ (Student *)fromXML:NSData* xml;
@end
2) Create a StudentService class
This class handles requests on “/students/<someExistingStudentId>” and has the following requirements:
* Houses and manages a single student object
* Exposes show and update functionality (GET and POST/PUT)
* GET: Generate and show an xml data containing mnr and name
* POST: Parse (xml) input and set new name/mnr/grades
* PUT: Parse (xml) input and set new name/mnr/grades but ignore PUT creation requests for subresources
StudentService.h
@interface StudentService : RootService // Note: Inheriting from RootService allows this class to expose get/put/post/execute methods
{
Student *student;
}
@property (readwrite, assign) Student *student;
@end
StudentService.m
@implementation StudentService
// implementation for GET requests (on /students/<mnr>)
- (NSData *) get: (NSData *) data withHeaders:(NSDictionary *)dict
{
NSString *outputstr = [NSString stringWithFormat:@"<student><mnr>%@</mnr><name>%@</name></student>];
return [outputstr dataUsingEncoding:NSUTF8StringEncoding];
}
// implementation for POST requests (on /students/<mnr>). Parse (xml) data and update fields
- (NSData *) post: (NSData *) data withHeaders:(NSDictionary *)dict
{
Student *updatedStudent = [Student fromXML:data]
if (updatedStudent == nil) return [@"<notupdated></notupdated>" dataUsingEncoding:NSUTF8StringEncoding];
[[self student] setMnr:[updatedStudent mnr]];
[[self student] setName:[updatedStudent name]];
[[self student] setGrades:[updatedStudent grades]];
return [@"<updated></updated>" dataUsingEncoding:NSUTF8StringEncoding];
}
// implementation for PUT requests (on /students/<mnr>). Same as POST.
- (NSData *) put: (NSData *) data withHeaders:(NSDictionary *)dict
{
return [self post:data withHeaders:dict];
}
3) Create a StudentRootService class
This class handles requests on “/students” (for GET/POST), on “/students/calculate_average_grade” (for EXECUTE) and on “/students/<someNonExistingStudentId>” (for PUT). Requirements for this class are:
* Manages a list/collection of StudentService objects
* GET: Generate and show xml data containing the mnr of each student
* POST: Parse (xml) input and add a new StudentService subservice
* PUT: Ignore put requests on /students (this means that we don’t support replacing all students at once), but allow explicit creation of sub (student) resources (i.e /students/somecoolstudentid).
* EXECUTE: implement the calculate_average_grade method
StudentRootService.h
@interface StudentRootService : RootService
{
NSMutableArray *students;
}
- (NSData *) calculate_average_grade:(NSData *)data;
@end
StudentRootService.m
@implementation StudentRootService
// implementation of init and dealloc omitted
//
// Implementation for GET requests (on /students).
- (NSData *) get: (NSData *) data withHeaders:(NSDictionary *)dict
{
NSMutableString *output = [[NSMutableString alloc] init];
[output appendString:@"<students>"]
for (StudentService *studentService in [self students])
{
Student* student = [StudentService student]
[output appendFormat:@"<mnr>%@</mnr>", [student mnr]];
}
[output appendString:@"</students>"];
NSData *data = [output dataUsingEncoding:NSUTF8StringEncoding];
[output release];
return data;
}
// Implementation for POST requests (on /students).
- (NSData *) post: (NSData *) data withHeaders:(NSDictionary *)dict
{
Student *student = [Student fromXML:data];
if (student == nil) return [@"<notcreated></notcreated>" dataUsingEncoding:NSUTF8StringEncoding];
StudentService *studentService = [[StudentService alloc] init];
[studentService setStudent: student];
[self addSubService:studentService withName:[student mnr]];
return [@"<created></created>" dataUsingEncoding:NSUTF8StringEncoding];
}
// implementation for PUT request on /student/<someneworexistingstudentid>
- (NSData *) putSubService: (NSData *) data withName:(NSString *)name
{
Student *student = [Student fromXML:data];
if (student == nil) return [@"<notcreated></notcreated>" dataUsingEncoding:NSUTF8StringEncoding];
StudentService *studentService = [[StudentService alloc] init];
[studentService setStudent: student];
[self addSubService:studentService withName:name]; // NOTE: The name of the subservice is set by the PUT request
return [@"<created></created>" dataUsingEncoding:NSUTF8StringEncoding];
}
- (NSData *) calculate_average_grade:(NSData *)data
{
// parse input data
// select students
// calculate average grade and return xml
}
@end
4) Register a StudentRootService instance with the server
RestServer *server = [RestServer instance];
RootService *rootService = [RootService alloc] init];
[rootService setAbsolutePath:@"/"];
[rootService setServiceName:@"/"]‘;
StudentRootService *studentRootService= [[StudentRootService alloc] init];
[rootService addSubService:studentRootService];
[server setRoot:rootService]:
5) Start the Server
The following resources should now be exposed:
/students : for GET, POST
/students/<existingstudentID> : for GET, POST
/students/<nonexistingstudentID> : for PUT
/students/calculate_average_grade : for EXECUTE
Rest vs Rest++
To show why the extension in Rest++ makes life a little bit easier I’ll show a small comparision between a service that only uses Rest (without extensions) and Rest++. Let’s assume we run a service for a factory that has employees that produce various goods. We have the following resources:
1) /employees :
* On GET: Returns a list of URIs to employees (e.g. /employees/1, /employees/2, etc)
2) /employees/<employeeId>
* On GET: Return some information about the employee (for example if the employee is currently on vacation or not).
3) /students/<employeeId>/goodsProduced
* On GET: Returns a list of goods and how many of each good was produced by this employee. Note: the goods we produce change, one week we might produce soap and butter the next week we might produce tobacco. Also not every worker is producing the same goods.
(e.g <goods><soap><produced>15</produced></soap><butter><produced>10</produced></butter></goods>)
We now want to periodically check which employee produced the least of some good (and kindly encourage him to increase his output).
With Rest (without any extensions) we’d have to do the following:
- Send GET to /employees to get a list of each employee
- Send GET to each /employees/<employeeID> and check if the employee is currently on vacation (employees on vaction will always produce 0 goods and would skew our result)
- Send GET to each /employees/<employeeID>/goodsProduced
- Filter the results and determine the ID of the worker with the least productivity for a given good.
With Rest++ we could implement a special resource on /employees/determine_id_of_lowest_productivity_worker that accepts a list of goods (e.g <goods><soap></soap><butter></butter></goods>) and returns the id of the employee with the lowest productivity for the given goods. We then would only have to perform one action:
- Send EXECUTE with a list of goods to /employees/determine_id_of_lowest_productivity_worker
Note: You could also replace an EXECUTE call to /employees/determine_id_of_lowest_productivity_worker with a special POST/GET sequence.
- Use POST on /employees/determine_id_of_lowest_productivity_worker with a list of goods, the server then creates a special resource that contains our result (e.g /employees/determine_id_of_lowest_productivity_worker/result_0000023)
- Use GET on /employees/determine_id_of_lowest_productivity_worker/result_0000023
References:
Put vs Post:
- http://jcalcote.wordpress.com/2008/10/16/put-or-post-the-rest-of-the-story/
- http://www.elharo.com/blog/software-development/web-development/2005/12/08/post-vs-put/
- http://blog.zacharyvoase.com/2009/07/03/http-post-put-diff/
- http://blog.arkesystems.com/post/2010/12/HTTP-PUT-vs-POST.aspx
On Rest: