All Classes Namespaces Functions Variables Groups Pages
RestHandler Module

# 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

URL form encoding is supported as well for message bodies according to RFC 2738 2.2 with `"Content-Type: application/x-www-form-urlencoded"`.

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()`
- `"PATCH /obj?action=status HTTP/1.1"`: matches method `AbstractRestClass::patchStatus()`

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()`
`PATCH` | `AbstractRestClass::patch()`
`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);
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);
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<ExceptionInfo> 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 `404 Bad Request` response to be returned by (to change this behavior, reimplement unknownSubClassError() in your specialization of RestHandler. Otherwise, if an exception occurs handling the request, a `409 Conflict` response is returned; 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 n_wf) {
wf = n_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) {
*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 n_oh) {
oh = n_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

    PUT /files/info.txt?action=chmod;mode=420 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

    PUT /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" : "chmod", "mode" : 420 }

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.

Note
  The RestHandler class allows for HTTP `GET` requests to be served with a message body, but this is not compliant with HTTP 1.1 RFCs and therefore could lead to compatibility problems should this technique be used with other servers; see httpclient_get_with_body for more information

# 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 ExceptionInfo hash.

# RestHandler Release Notes

## RestHandler v1.2.1

- updated to return a 400 Bad Request error when REST schema validation fails on messages received (issue 2344)
- updated to return a 400 Bad Request error when there are string encoding errors with messages received (issue 2398)
- updated to return a 404 Not Found error when REST subclass does not exist (issue 2405)
- updated to return a 400 Bad Request error when ENCODING-CONVERSION-ERROR occurs during request parsing (issue 2543)

## RestHandler v1.2

- added logic to allow sensitive data to be masked in log messages (issue 1086)
- added RestHandler::handleExternalRequest() to allow for REST API methods to be called internally (i.e. without an HTTP call)
- updated for complex types (issue 1724)
- added support for REST schema validation
- added default support for the HTTP `"PATCH"` method

## RestHandler v1.1

- added support for the HTTP `OPTIONS` method
- return an error if an unsupported HTTP method is used in a REST call
- fixed a type error in the ah member of RestHandler::AbstractRestStreamRequestHandler
- fixed a bug where an error calling an internal nonexistent method would be reported with an incorrect error message
- implemented support for notifying persistent connections when the connection is terminated while a persistent connection is in place
- RestHandler::AbstractRestStreamRequestHandler is now the base abstract class for REST stream request handlers
- send errors are now reported in the RestHandler::AbstractRestStreamRequestHandler object so they can be properly logged (issue 734)
- unknown REST class errors with the base class are now reported consistently like all other such errors (issue 859)

## RestHandler v1.0.1

- added support for URL form encoded formatted message bodies (issue 1436)

## RestHandler v1.0

- the initial version of the RestHandler module