Loading...
 
HowTo: Implement a REST Server Service

Implementing a REST service in Qorus is straightforward; the service should have the REST implementation either as library code or implemented directly in the init method.

In the following example, we'll implement a service that handles the /rest-example URI path with the following URI paths, methods, and actions:
  • PUT /rest-example/caller?action=updateStatus
  • POST /rest-example/data
  • GET /rest-example/data?action=check;filename=filename

Note: Qore and Qorus implement REST as an API; the HTTP message body for REST messages is assumed to be serialized data (and not HTML, although the REST server implementation does support HTML rendering as a response to HTTP requests. See REST Data Serialization Support for more information.

1. Implement the Root REST Handler Class

The first step will be to implement the root REST handler class, which must be a subclass of AbstractServiceRestHandler; this will be the main entry point for external REST requests in the service.

For example:
# the root REST handler object
class ExampleRestHandler inherits AbstractServiceRestHandler {
    constructor() : AbstractServiceRestHandler("rest-example") {
    }
}

The string argument in the constructor() above gives the root URI path component that this handler will service; see the documentation in the preceding link for more information.

2. Implement URI Path Handler Classes

Implement subclasses of AbstractRestClass for each URI path component to be handled.
For example:
# class for handling REST data operations
class DataRestClass inherits AbstractRestClass {
    string name() {
        return "data";
    }
}

# class for handling REST caller operations
class CallerRestClass inherits AbstractRestClass {
    string name() {
        return "caller";
    }
}

2.1. Implement Action Method Handlers

Implement action method handlers for each URI path and each HTTP method and action that you want to handle in your AbstractRestClass URI path handling classes.

Method action handlers are implemented with the following naming convention:
  • method[Action]

Where method is an HTTP method in lower case, and [Action] is an optional action.

See examples in the following table:

Request Example Method
GET /path (no action)
hash get(hash cx, *hash ah) {}
PUT /path (no action)
hash put(hash cx, *hash ah) {}
POST /path (no action)
hash post(hash cx, *hash ah) {}
DELETE /path (no action)
hash del(hash cx, *hash ah) {}
OPTIONS /path (no action)
hash options(hash cx, *hash ah) {}
GET /path?action=check
hash getCheck(hash cx, *hash ah) {}
PUT /path?action=updateStatus
hash putUpdateStatus(hash cx, *hash ah)  {}


For example:
# class for handling REST data operations
class DataRestClass inherits AbstractRestClass {
    string name() {
        return "data";
    }

    hash post(hash cx, *hash ah) {
        log(LL_INFO, "POST received with args: %y", ah);
        return RestHandler::makeResponse(200, "OK");
    }

    hash getCheck(hash cx, *hash ah) {
        if (!exists ah.filename)
            throw "DATA-ERROR", "missing 'filename' argument";

        log(LL_INFO, "checking filename: %y (OK)", ah.filename);
        # fake the response here
        return RestHandler::makeResponse(200, "OK");
    }
}

class CallerRestClass inherits AbstractRestClass {
    string name() {
        return "caller";
    }

    hash putUpdateStatus(hash cx, *hash ah) {
        log(LL_INFO, "PUT received with args: %y", ah);
        return RestHandler::makeResponse(200, "OK");
    }
}


See Implementing REST Services for more information.

3. Add URI Path Handlers

Add the URI path handlers that you've created in the constructor of the class subclassing AbstractServiceRestHandler by calling AbstractServiceRestHandler::addClass() with AbstractRestClass objects
For example:
# the root REST handler object
class ExampleRestHandler inherits AbstractServiceRestHandler {
    constructor() : AbstractServiceRestHandler("rest-example") {
        addClass(new DataRestClass());
        addClass(new CallerRestClass());
    }
}

To add URI path handlers for further path components, reimplement the AbstractRestClass::subClass() method and return the appropriate URI path handler object (AbstractRestClass object) corresponding to the path component in the request as given by the name argument to AbstractRestClass::subClass().

4. Instantiate the REST Handler Class and Bind the HTTP Handler

In the service's init method function, instantiate the REST handler class and call svc_bind_http() to bind the handler to the HTTP server.

If the AbstractServiceHttpHandler::addListener() call is made, then the REST handler will be bound to all global HTTP listeners for Qorus Integration Engine.

For example:
sub init() {
    # create the REST handler object
    ExampleRestHandler lh();
    # bind the handler to all global Qorus listeners
    svc_bind_http(lh);
}

5. Optional: Implement Custom User Authentication and Authorization

By default, a DefaultQorusRBACAuthenticator object is passed to the constructor, which results in standard Qorus RBAC security being applied (which is only enforced if rbac-security is enabled, in which case users must have at least the LOGIN role to connect to the REST handler).

Custom user authentication can be implemented for REST handlers by passing an object from a user-defined class that inherits directly from AbstractAuthenticator as the auth argument to the AbstractServiceRestHandler::constructor() method.

To allow any user to connect to the REST service, even if rbac-security is enabled, use a PermissiveAuthenticator object instead as in the following example:
class ExampleRestHandler inherits AbstractServiceRestHandler {
    constructor() : AbstractServiceRestHandler("rest-example", False, new PermissiveAuthenticator()) {
        addClass(new DataRestClass());
        addClass(new CallerRestClass());
    }
}

6. Example Service Source

The following code is in the rest-example-v1.0.qsd example file.
# -*- mode: qore; indent-tabs-mode: nil -*-
# service: rest-example
# serviceversion: 1.0
# servicedesc: REST API example service
# serviceauthor: Qore Technologies, s.r.o.
# parse-options: PO_NEW_STYLE, PO_REQUIRE_TYPES, PO_STRICT_ARGS
# autostart: true
# define-group: EXAMPLES: example interface objects
# define-group: REST-SERVICE-EXAMPLE-1: REST service example 1 interface objects
# groups: EXAMPLES, REST-SERVICE-EXAMPLE-1
# ENDSERVICE

# name: init
# desc: initializes the service and sets up the REST handler

# class for handling REST data operations
class DataRestClass inherits AbstractRestClass {
    string name() {
        return "data";
    }

    hash post(hash cx, *hash ah) {
        log(LL_INFO, "POST received with args: %y", ah);
        return RestHandler::makeResponse(200, "OK");
    }

    hash getCheck(hash cx, *hash ah) {
        if (!ah.filename)
            throw "DATA-ERROR", "missing 'filename' argument";

        log(LL_INFO, "checking filename: %y (OK)", ah.filename);
        # fake the response here
        return RestHandler::makeResponse(200, "OK");
    }
}

# class for handling REST caller operations
class CallerRestClass inherits AbstractRestClass {
    string name() {
        return "caller";
    }

    hash putUpdateStatus(hash cx, *hash ah) {
        log(LL_INFO, "PUT received with args: %y", ah);
        return RestHandler::makeResponse(200, "OK");
    }
}

# the root REST handler object
class ExampleRestHandler inherits AbstractServiceRestHandler {
    constructor() : AbstractServiceRestHandler("rest-example") {
        addClass(new DataRestClass());
        addClass(new CallerRestClass());
    }
}

sub init() {
    # create the REST handler object
    ExampleRestHandler lh();
    # bind the handler to all global Qorus listeners
    bind_http(lh);
}
# END