Framework for JSON APIs on websocket connections

AppWebsocket connections use JSON messages for communication. Those messages are normaly received and processed by the application. But sometimes it is wanted to build modules for re-occurring communication types that can be reused. This can be done using JSON APIs. The idea is that many JSON APIs can be attached to a single AppWebsocket connection and each of them processes the JSON messages with a specific "api" attribute.

This framework provides base classes and interfaces that are needed to implement JSON APIs and attach them to a connection.

File information

Filecommon/interface/json_api.h

Classes JsonApiContext
UJsonApiContext
JsonApi
IJsonApiConnection

Classes

JsonApiContext

class JsonApiContext {
public:
    JsonApiContext() {
        providers = 0;
    }
    void RegisterJsonApi(class UJsonApiContext * provider) {
        provider->next = providers;
        providers = provider;
    }
    class JsonApi * CreateJsonApi(const char * name, class IJsonApiConnection * connection, class json_io & msg, word base) {
        for (class UJsonApiContext * p = providers; p; p = p->next) {
            if (!strcmp(name, p->Name())) return p->CreateJsonApi(connection, msg, base);
        }
        return 0;
    }
    class JsonApi * JsonApiRequested(const char * name, class IJsonApiConnection * connection) {
        for (class UJsonApiContext * p = providers; p; p = p->next) {
            if (!strcmp(name, p->Name())) return p->JsonApiRequested(connection);
        }
        return 0;
    }
    class UJsonApiContext * providers;
};

Overview

The class JsonApiContext must be used as base class by an object using other Json Api implementations. Typically the instance class of an app uses this class as base class. This class is passed as argument on the constructor of AppWebsocket so that for incoming messages an api implementation can be found.

Public functions

RegisterJsonApi
This function is called for each api implementaion which shall be used. The api implementation is added to the list of "providers"
CreateJsonApi
This function is used to add actively an AppWebsocketSession to an api implementation, when the app has determined that a given connection can be used for the api. For example if a connection is incoming from a PBX this connection can be used for the replicator to replicate a user table, so CreateJsonApi is called.
JsonApiRequested
This function is called by AppWebsocket if a message is received addressing a new api. The list of providers is searched for a matching provider and if found the JsonApiRequested function of the provider is called.

UJsonApiContext

class UJsonApiContext {
public:
    virtual class JsonApi * CreateJsonApi(class IJsonApiConnection * connection, class json_io & msg, word base) = 0;
    virtual class JsonApi * JsonApiRequested(class IJsonApiConnection * connection) { return 0; };
    virtual const char * Name() = 0;
    class UJsonApiContext * next;
};

Overview

This class is used as base class by an api implementation. With this class the api is added to the list of providers for a given JsonApiContext.

Public functions

CreateJsonApi
Called to add a AppWebsocketConnection to the Api implementation. The message used to decide if the AppWebsocket can be used by the api implementation is passed as json_io object.
JsonApiRequested
Called if a message with an api matching the name of this provider was received, for which no JsonApi existed, so that the api provider can create a JsonApi object.

JsonApi

class JsonApi {
public:
    virtual ~JsonApi() {};
    virtual const char * Name() = 0;
    virtual void JsonApiStart() {};
    virtual void Message(class json_io & msg, word base, const char * mt, const char * src) = 0;
    virtual void JsonApiResponseSent() {};
    virtual void JsonApiConnectionClosed() = 0;
};

Overview

This is the base class for JSON APIs. One instance can be attached to one connection. So for each connection the application has to create a new JsonApi instance.

Public functions

Name

Return value

The name of the API, e.g. "com.innovaphone.provisioning".
JsonApiStart
Can be called by the application to start the API. This is needed when the local API object needs to send an initial message before any other API messages are received.
Message
Processes an incoming message. This function is called by the associated IJsonApiConnection.

Parameters

class json_io & msg The JSON structure containing the message.
word base The ID of the base element of the message inside the msg structure. Pass it to the get functions of json_io to read the attributes of the message. Example:
const char * text = msg.get_string(base, "text");
const char * mt The message type of the message.
const char * src The src is used for multiplexing on the remote side. If specified, all answers to this message should contain the same src value.
JsonApiConnectionClosed
Called by the associated IJsonApiConnection if the connection was closed. Implementation must not do any more function calls to the IJsonApiConnection and shutdown (delete) themselves.
JsonApiResponseSent
Called by the associated IJsonApiConnection as response to a JsonApi * response argument set in a JsonApiMessage() or JsonApiMessageText() call. This callback does not indicate that the response was actually received by the peer, but it is only a local flow-control mechanism

IJsonApiConnection

class IJsonApiConnection {
public:
    virtual ~IJsonApiConnection() {}
    virtual void RegisterJsonApi(class JsonApi * api) = 0;
    virtual void UnRegisterJsonApi(class JsonApi * api) = 0;
    virtual void JsonApiMessage(class json_io & msg, char * buffer) = 0;
    virtual void JsonApiMessageComplete() = 0;
    virtual bool JsonApiPermission(const char * api) = 0;
    virtual const char * JsonApiUserDomain() = 0;
    virtual const char * JsonApiUserSip() = 0;
    virtual const char * JsonApiUserDn() = 0;
    virtual const char * JsonApiUserGuid() = 0;
    virtual const char * JsonApiApp() = 0;
    virtual const char * JsonApiInfo() = 0;
    virtual bool JsonApiUnlicensed() = 0;

};

Overview

This interface represents a websocket connection that supports plugging-in JsonApi instances. A well-known implementation of the interface is AppWebsocket.

Public functions

RegisterJsonApi
Attaches a JsonApi object to the connection. The object will start receiving the incoming websocket messages with the "api" attibute specified by the object.

Parameters

class JsonApi * api The JsonApi object that shall be attached.
UnRegisterJsonApi
Removes a JsonApi object from the connection. The object will stop receiving messages.

Parameters

class JsonApi * api The JsonApi object that shall be removed.
JsonApiMessage
To be called by JsonApi objects to send a message over the connection.

Parameters

class json_io & msg A JSON structure containing the message.
char * buffer A buffer that is big enough to contain the whole encoded message including NULL termination.
JsonApiMessageComplete
To be called by JsonApi objects if they have finished processing the last Message callback and are ready to receive the next message.
JsonApiPermission
This function is used internally to check if access to a certain API shall be granted.

Parameters

const char * api The name of the API.

Return value

true if access to the API is granted, false otherwise.
JsonApiUserDomain

Return value

Returns the domain of the user that is logged-in.
JsonApiUserSip

Return value

Returns the sip of the user that is logged-in.
JsonApiUserDn

Return value

Returns the display name of the user that is logged-in.
JsonApiUserGuid

Return value

Returns the GUID of the user that is logged-in.
JsonApiApp

Return value

Returns the app that the user is authenticated for. Can be used for example to tell connections for user and admin apps apart.
JsonApiInfo

Return value

Returns the info object from the login. Can be used to get additional information, like the PBX of the logged-in user.
JsonApiUnlicensed

Return value

Returns true, if the logged-in user got no license for the related app.

Example Code

The class EchoJsonApi derived from JsonApi and the corresponding class EchoJsonApiContext derived from UJsonApiContext implements a simple API for demonstation purposes. The method EchoJsonApi::Message responds on an incoming message type "Ping" with a message type "Pong".

The class defenitions in jsonapiexample_echo.h

class EchoJsonApiContext : public UJsonApiContext {
    class JsonApiContext * jsonApiContext;
    char * name;

public:
    EchoJsonApiContext(const char * name, JsonApiContext * jsonApiContext);
    virtual ~EchoJsonApiContext();

    class JsonApi * CreateJsonApi(class IJsonApiConnection * connection, class json_io & msg, word base) override;
    class JsonApi * JsonApiRequested(class IJsonApiConnection * connection) override;
    const char * Name() override;
};

class EchoJsonApi : public JsonApi {
    char * name;
    IJsonApiConnection * connection;

public:
    EchoJsonApi(const char * name, IJsonApiConnection * connection);
    virtual ~EchoJsonApi();

    const char * Name() override;
    void Message(class json_io & msg, word base, const char * mt, const char * src) override;
    void JsonApiConnectionClosed() override;

};

The class implementations in jsonapiexample_echo.cpp

#include "platform/platform.h"
#include "common/interface/json_api.h"
#include "common/ilib/json.h"

#include "jsonapiexample_echo.h"

EchoJsonApiContext::EchoJsonApiContext(const char * name, JsonApiContext * jsonApiContext)
{
    this->name = _strdup(name);
    this->jsonApiContext = jsonApiContext;
    this->jsonApiContext->RegisterJsonApi(this);
}

EchoJsonApiContext::~EchoJsonApiContext()
{
    free(name);
}

class JsonApi * EchoJsonApiContext::CreateJsonApi(IJsonApiConnection * connection, json_io & msg, word base)
{
    return 0;
}

class JsonApi * EchoJsonApiContext::JsonApiRequested(IJsonApiConnection * connection)
{
    return new EchoJsonApi(name, connection);
}

const char * EchoJsonApiContext::Name()
{
    return name;
}

EchoJsonApi::EchoJsonApi(const char * name, IJsonApiConnection * connection)
{
    this->name = _strdup(name);
    this->connection = connection;
    connection->RegisterJsonApi(this);
}

EchoJsonApi::~EchoJsonApi()
{
    free(name);
}

const char * EchoJsonApi::Name()
{
    return name;
}

void EchoJsonApi::Message(json_io & msg, word base, const char * mt, const char * src)
{
    if (!strcmp(mt, "Ping")) {
        const char * text = msg.get_string(base, "text");
        char sb[200];
        class json_io send(sb);
        word base = send.add_object(0xFFFF, 0);
        send.add_string(base, "api", name);
        send.add_string(base, "mt", "Pong");
        if (text) send.add_string(base, "text", text);
        if (src) send.add_string(base, "src", src);
        connection->JsonApiMessage(send, sb);
    }
    connection->JsonApiMessageComplete();
}

void EchoJsonApi::JsonApiConnectionClosed()
{
    delete this;
}

Usage in the app class

A new EchoJsonApiContext object is instantiated directly after the call to the RegisterJsonApi(this); function in the app class constructor. This will register the API "EchoApi" at the JsonApiContext of the app class. As soon as an incoming message adressing the "EchoApi" arrives at the AppWebsocket, the AppWebsocket will call JsonApiRequested function and instantiate an EchoJsonApi object.

...
#include "jsonapiexample_echo.h"
...
jsonapiexample::jsonapiexample(IIoMux * const iomux, class jsonapiexampleService * service, AppInstanceArgs * args) : AppInstance(service, args), AppUpdates(iomux), ConfigContext(nullptr, this)
{

    ...

    RegisterJsonApi(this);

    this->echoJsonApiContext = new EchoJsonApiContext("EchoApi", this);

    Log("App instance started");
}

jsonapiexample::~jsonapiexample()
{
    delete this->echoJsonApiContext;
}