Work with user-defined data types

Sometimes, you may need to pass arbitrary/user-defined binary data in a stream separate from application state or views. This is achieved by defining a custom service-side multipart response provider, with a corresponding multipart handler on the client.

With user-defined data types, you must perform the serialization and de-serialization of your structs manually, as the service will send the data as a byte array, but the client will receive it as an array buffer.

Steps

Create a custom response provider

APIs:

(I)StateManagerPlugin | C++ | .Net | Java
(I)ResponseProvider | C++ | .Net | Java
ContentInfo | C++ | .Net | Java

PureWeb's StateManager works by using a collection of plugins, such as ViewManager, CommandManager, and XmlStateManager. You can create custom plugins as well, such as custom response providers for user-defined data types.

Creating a response provider involves several interfaces from the service APIs:

  • IStateManagerPlugin: This is a mandatory interface that must be implemented by all plugins. It provides methods for initializing and uninitializing the plugin, It also provides a SessionConnected method, which is used to notify the plugin that the specified client session is connected. This is where you define what actions the StateManager should take on session connect in regards to your plugin. For a response provider, this includes adding the provider to the SessionManager.
  • IResponseProvider: As the name indicates, this interface is needed for response providers. It consists of a single method, GetNextResponses, which determines when and how to get the next response from the plugin.
  • ContentInfo: Once you construct the byte array that will contain the multipart data to stream to the service, you must store this array as a ContentInfo object.
  • GenericResponseAggregator: This is a utility class used to store and retrieve responses by sessionID. In particular, your plugin will need to call the addResponse method when it is ready to add the data (expressed as a ContentInfo object) to the response. Note that this method expects a destination parameter, this is a string that uniquely identifies the multipart handler, as registered on the client (the client-side code is provided further down this page).
  • In addition, in C++, you will likely use the Autolock and Mutex classes, which provide mutual exclusion for shared data accessed by different threads.

The sample code below is a modified version of the Scribble sample application, and illustrates how you could stream user-defined random scribbles from the service to the client. This is a somewhat academic example, but provides all the parts of the puzzle in relatively short and easy-to-digest snippets. A command handler is implemented as part of the custom plugin, to respond to requests from the client to generate scribbles.

C++

Create the header file for the plugin:

#include "PureWeb.h"

using namespace CSI;
using namespace CSI::PureWeb;
using namespace CSI::PureWeb::Server;

class RandomScribblePlugin : public IStateManagerPlugin, public IResponseProvider
{
private:
    StateManager* m_pStateManager;
    GenericResponseAggregator m_responses;
    mutable Threading::Mutex m_mutex;              

public:
    RandomScribblePlugin();
    virtual ~RandomScribblePlugin();
                
    // IStateMangerPlugin
    virtual void Initialize(StateManager* pStateManager);
    virtual void Uninitialize();
    virtual void SessionConnected(Guid sessionId, Typeless const& command);
    virtual void SessionDisconnected(Guid sessionId, Typeless const& command);

    // IResponseProvider
    virtual Collections::List<ResponseInfo> GetNextResponses(Guid sessionId);
  
    // Command handler to generate random scribbles
    void generateRandomScribbles(Guid sessionId, Typeless const& command, Typeless& responses);
};

Define the plugin:

#include "stdafx.h"
#include "RandomScribblePlugin.h"

using namespace CSI::Collections;
using namespace CSI::Threading; 
         
RandomScribblePlugin::RandomScribblePlugin() : m_pStateManager(NULL)
{
}

RandomScribblePlugin::~RandomScribblePlugin()
{
}                        

void RandomScribblePlugin::Initialize(StateManager* pStateManager)
{
    AutoLock lock(m_mutex);
    m_pStateManager = pStateManager;

    m_pStateManager->CommandManager().AddUiHandler("RandomScribblesCommand", Bind(this, &RandomScribblePlugin::generateRandomScribbles)); 
}

void RandomScribblePlugin::Uninitialize()
{
    AutoLock lock(m_mutex);

    m_pStateManager->CommandManager().RemoveUiHandler("RandomScribblesCommand"); 
    m_pStateManager = NULL;
}

void RandomScribblePlugin::SessionConnected(Guid sessionId, Typeless const& command)
{
    m_pStateManager->SessionManager().AddResponseProvider(sessionId, this);
}

void RandomScribblePlugin::SessionDisconnected(Guid sessionId, Typeless const& command)
{
    AutoLock lock(m_mutex);
    m_pStateManager->SessionManager().RemoveResponseProvider(sessionId, this);
}

List<ResponseInfo> RandomScribblePlugin::GetNextResponses(Guid sessionId)
{
    AutoLock lock(m_mutex);
    return m_responses.GetNextResponses(sessionId);
}

void RandomScribblePlugin::generateRandomScribbles(Guid sessionId, Typeless const& command, Typeless& responses)
{
    Array<double> coords(200);

    for (int i = 0; i < 200; i += 2)
    {
        coords[i] = static_cast<double>(std::rand()) / RAND_MAX;
        coords[i + 1] = static_cast<double>(std::rand()) / RAND_MAX;
    }

    AutoLock lock(m_mutex);

    // The name "RandomScribblesResponseHandler" comes from the client-side multipart handler registration code
    ContentInfo cinfo(ContentInfo::ApplicationOctetStreamType(), coords.ReinterpretAs<Byte>());
    m_responses.AddResponse(sessionId, "RandomScribblesResponseHandler", cinfo);
}


Create a custom multipart handler

APIs:

WebClient.addMultipartHandler() | HTML5 | iOS | Android

Before a client can use a multipart handler, it must register this handler. This is a single line of code which accomplishes the following:

  • It gives the handler a unique name. When adding responses for this handler, the service must use this name.
  • It associates a response handling function. This is the function that the client will run when the handler is called.
  • It registers the handler with the StateManager, so that PureWeb can do its magic under the hood.

HTML5

framework.getClient().addMultipartHandler('MyClientMultipartHandler', MyResponseHandlingFunction);

The syntax for the response handling function itself (the function that will be triggered by the multipart handler) takes three arguments: the mime type of the response, the bytes of the response, and an object containing additional response parameters.

The logic within the function will need to de-serialize the structs sent from the service.

HTML5

var MyResponseHandlingFunction = function(mimeType, data, parameters) {
{
    // Do something useful
}

Example

Below is the sample code for the multipart handler that corresponds to the response provider for random scribbles, which we created on the service earlier.

HTML5

Add a button to generate random scribbles:

<button onclick="generateScribbles();">Random Scribbles</button>

Define the button's onclick function to queue RandomScribblesCommand:

function generateScribbles() {
    pureweb.getClient().queueCommand("RandomScribblesCommand");
}

Add a multipart handler:

framework.getClient().addMultipartHandler('RandomScribblesResponseHandler', multipartHandler);

Define the handler to process random scribbles expressed in normalized coordinates. The scribbles are simply drawn to the canvas (note that the scribbles are transient).

var multipartHandler = function(mimeType, data, parameters) {
    var coords = new Float64Array(data); // assumes endianess is same on both client and server
    var canvas = scribbleView.getCanvasElement();
    var ctx = canvas.getContext('2d');
    ctx.save();
    ctx.beginPath();
    ctx.strokeStyle = '#000000';
    ctx.strokeWidth = 3;
    ctx.moveTo(coords[0] * canvas.width, coords[1] * canvas.height);
    for (var i = 2; i .... coords.length; i += 2) {
        ctx.lineTo(coords[i] * canvas.width, coords[i + 1] * canvas.height);
    }
    ctx.stroke();
    ctx.restore();
};