Qore RestHandler Module Reference  1.1
 All Classes Namespaces Files Functions Variables Modules Pages
RestHandler Module

RestHandler.qm Copyright (C) 2013 - 2014 Qore Technologies, sro

Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

Introduction to the RestHandler Module

The RestHandler module provides functionality for implementing REST services with the Qore HTTP server.

To use this module, use "%requires RestHandler" and "%requires HttpServer" in your code.

All the public symbols in the module are defined in the RestHandler namespace.

The main classes are:

  • AbstractRestClass: this class provides the functionality of the REST object currently being accessed (and access to subobjects or subclasses)
  • RestHandler: this class can be used to plug in to the Qore HttpServer to expose REST services

see example file "restserver.q" in the examples directory for an example of using this module to implement REST services.

Data Serialization Support

The RestHandler class uses any of the following modules if the modules are available when the RestHandler module is initialized:

  • json: provides automatic support for JSON payload de/serialization
  • xml: provides automatic support for XML-RPC payload de/serialization
  • yaml: provides automatic support for YAML payload de/serialization

For standard REST web service development, the json module will be required to support JSON serialization.

The RestHandler class will automatically deserialize request bodies if the incoming MIME type is supported and additionally will serialize outgoing messages automatically based on the client's declared capabilities (normally responses are serialized with the same serialization as the incoming message).

Implementing REST Services

The class that models a REST entity or object in this module is AbstractRestClass.

This class should be subclassed for each object type, and methods implemented for each REST method that the object will support.

Incoming requests matched by your RestHandler subclass (when called by the HttpServer when requests are appropriately matched) will be dispatched to methods in your AbstractRestClass specialization (ie your user-defined subclass of this class) according to the following naming convention:

  • httpmethod[RequestRestAction]

For example:

  • "GET /obj HTTP/1.1": matches method AbstractRestClass::get()
  • "POST /obj HTTP/1.1": matches method AbstractRestClass::post()
  • "DELETE /obj/subobj": match method AbstractRestClass::del()
  • "GET /obj?action=status HTTP/1.1": matches method AbstractRestClass::getStatus()
  • "PUT /obj?action=status HTTP/1.1": matches method AbstractRestClass::putStatus()

In other words, if a REST action is given in the request (either as a URI parameter or in the message body), the first letter of the action name is capitalized and appended to a lower case version of the HTTP method name (except "DELETE" is mapped to "del" because "delete" is a keyword); if such a method exists in your AbstractRestClass specialization, then it is called. If not, then, if the REST action exists under a different HTTP method (ie a request comes with "HTTP GET /something?action=setData", and "putSetData" exists, but "getSetData" was used for the search), then a 405 "Method Not Allowed" response is returned. If no variation of the requested REST method exists, then a 501 "Not Implemented" response is returned.

If a REST request is made with no action name, then a method in your class is attempted to be called with just the HTTP method name in lower case (except "DELETE" is mapped to "del" because "delete" is a keyword).

HTTP Method Name to Qore Method Name

HTTP Method Qore Method
GET AbstractRestClass::get()
PUT AbstractRestClass::put()
POST AbstractRestClass::post()
DELETE AbstractRestClass::del()
OPTIONS AbstractRestClass::options()

In all cases, the signature for these methods looks like:

hash method(hash $cx, *hash $ah) {}

The arguments passed to the REST action methods are as follows:

  • cx: a call context hash; the same value as documented in the cx parameter of HttpServer::AbstractHttpRequestHandler::handleRequest(); with the following additions:
    • body: the deserialized message body (if any); if no body was sent, then any parsed URI arguments are copied here
    • hdr: a copy of the header hash as returned by Socket::readHTTPHeader() as received by the HttpServer (for example cx.hdr.path gives the original URI request path)
    • orig_method: the string if any "action" argumnt was sent either in the message body or as a URI argument
    • rest_method: same as orig_method, only with the first letter capitalized (only present if an "action" argument was sent)
    • rest_action_method: the method name that will be called on the actual Qore REST class (a subclass of AbstractRestClass)
  • ah: these are the parsed URI query arguments to the REST call; see Argument and Message Body Handling for more information

The return value for these methods is the same as the return value for HttpServer::AbstractHttpRequestHandler::handleRequest().

consider the following example class:

class AbstractExampleInstanceBaseClass inherits AbstractRestClass {
private {
hash $.h;
}
constructor(hash $n_h) {
$.h = $n_h;
}
hash get(hash $cx, *hash $ah) {
return RestHandler::makeResponse(200, $.h);
}
hash putChown(hash $cx, *hash $ah) {
if (!exists $cx.body.user && !exists $cx.body.group)
return RestHandler::make400("missing 'user' and/or 'group' parameters for chown(%y)", $.h.path);
my int $rc = chown($.h.path, $cx.body.user, $cx.body.group);
return $rc ? RestHandler::make400("ERROR chown(%y): %s", $.h.path, strerror()) : RestHandler::makeResponse(200, "OK");
}
hash putChmod(hash $cx, *hash $ah) {
if (!exists $cx.body.mode)
return RestHandler::make400("missing 'mode' parameter for chmod(%y)", $.h.path);
my int $rc = chmod($.h.path, $cx.body.mode);
return $rc ? RestHandler::make400("ERROR chmod(%y): %s", $.h.path, strerror()) : RestHandler::makeResponse(200, "OK");
}
hash putRename(hash $cx, *hash $ah) {
if (!exists $cx.body.newPath)
return RestHandler::make400("missing 'newPath' parameter for rename(%y)", $.h.path);
try {
rename($.h.path, $cx.body.newPath);
}
catch (hash $ex) {
return RestHandler::make400("rename (%y): %s: %s", $.h.path, $ex.err, $ex.desc);
}
return RestHandler::makeResponse(200, "OK");
}
}

Assuming this object corresponds to URL path /files/info.txt, the following requests are supported:

  • "GET /files/info.txt HTTP/1.1": calls the get() method above (no action)
  • "PUT /files/info.txt?action=chown;user=username HTTP/1.1": calls the putChown() method above (such arguments could also be passed in the message body; see Argument and Message Body Handling for more information)
  • "PUT /files/info.txt?action=chmod;mode=420 HTTP/1.1": calls the putChmod() method above (note that 420 = 0644)
  • "PUT /files/info.txt?action=rename;newPath=/tmp/old.txt HTTP/1.1": calls the putRename() method above

Implementing Object Hierarchies

To implement a hisearchy of REST objects in the URL, each AbstractRestClass subclass reimplements the subClass() method that returns a child AbstractRestClass object representing the child object.

If no such sub object exists, then the method should return NOTHING, which will cause a "REST-CLASS-ERROR" exception to be thrown by default (to change this behavior, reimplement unknownSubClassError() in your specialization of RestHandler. See Exception Handling in REST Calls for more information

The following is an example of a class implementing a subClass() method:

class WorkflowOrderRestClass inherits AbstractRestClass {
private {
# this holds the information or state of the REST object
hash $.wf;
}
constructor(hash $wf) {
$.wf = $wf;
}
# the name of the REST object
string name() {
return "orders";
}
# supports subclass requests by returning an object if the id is recognized
*AbstractRestClass subClass(softint $order_id, hash $cx, *hash $ah) {
my *hash $h = $sysinfo.getOrderInfo($wf.id, $order_id);
if ($h)
return new WorkflowOrderInstanceRestClass($h);
}
# supports \c GET requests on the object
hash get(hash $cx, *hash $ah) {
return RestHandler::makeResponse(200, $.wf);
}
}
class WorkflowOrderInstanceRestClass inherits AbstractRestClass {
private {
# this holds the information or state of the REST object
hash $.oh;
}
constructor(hash $oh) {
$.oh = $oh;
}
# the name of the REST object
string name() {
return $.oh.order_instanceid;
}
# supports \c PUT requests where action=retry on the object
hash putRetry(hash $cx, *hash $ah) {
return RestHandler::makeResponse(200, $api.retryOrder($.oh.order_instanceid));
}
# supports \c GET requests on the object
hash get(hash $cx, *hash $ah) {
return RestHandler::makeResponse(200, $.oh);
}
}
Note
REST classes directly under the base handler object inheriting RestHandler can add subclasses by calling RestHandler::addClass() as well; the value of the AbstractRestClass::name() method provides the name of the subclass.

Argument and Message Body Handling

There are two ways to pass arguments to REST action methods:

  • 1: pass them as URI query arguments
  • 2: pass them as a hash in the message body

REST action methods have the following signature:

hash method(hash $cx, *hash $ah) {}

In the usual case when only one of the above methods is used, then the argument information is copied to both places, making it easier for the Qore REST implementation to handle arguments in a consistent way regardless of how they were passed. This means arguments, including any action argument, can be passed in either the URI path as URI query arguments, or in the message body as a hash.

Therefore, the following cases are identical:

Request With URI Argument

GET /files/info.txt?action=info;option=verbose HTTP/1.1
Accept: application/x-yaml
User-Agent: Qore-RestClient/1.0
Accept-Encoding: deflate,gzip,bzip2
Connection: Keep-Alive
Host: localhost:8001

Request With Arguments in the Message Body

GET /files/info.txt HTTP/1.1
Accept: application/x-yaml
User-Agent: Qore-RestClient/1.0
Accept-Encoding: deflate,gzip,bzip2
Connection: Keep-Alive
Host: localhost:8001
Content-Type: application/json;charset=utf-8
Content-Length: 36

{ "action" : "info", "verbose" : 1 }
REST URI Arguments
REST URI arguments are available in the "$ah" argument to the method. REST URI arguments are provided internally with the parse_uri_query function.

If no URI query argument were provided in the request, then the "$ah" parameter is populated by the deserialized message body, if it is deserialized to a hash.
REST Message Body
The REST request message body is automatically deserialized and made available as "$cx.body" in REST action methods.

If no message body was included in the client request, then any parsed URI parameters are copied to "$cx.body".

For requests with long or complex arguments should send arguments as message bodies and not as serialized URI query arguments.

Exception Handling in REST Calls

Qore exceptions are serialized in a consistent way by the RestHandler class so that they can be recognized and handled appropriately by clients.

Qore exceptions are returned with a 409 "Conflict" HTTP return code with the message body as an exception hash.

RestHandler Release Notes

RestHandler v1.1

  • added support for the HTTP OPTIONS method
  • return an error if an unsupported HTTP method is used in a REST call

RestHandler v1.0