Quantcast
Channel: DSGPB - DSG Praktika Blog » Extending Restful Services
Viewing all articles
Browse latest Browse all 2

Rest++, Making Resources Talk

$
0
0

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:

  1. Send GET to /employees to get a list of each employee
  2. 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)
  3. Send GET to each /employees/<employeeID>/goodsProduced
  4. 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:

  1. 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.

  1. 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)
  2. Use GET on /employees/determine_id_of_lowest_productivity_worker/result_0000023

References:

Put vs Post:

On Rest:


Viewing all articles
Browse latest Browse all 2

Latest Images

Trending Articles





Latest Images