Archive for September, 2009

18
Sep

Data Transport with JSON-RPC via Ext.Direct and Zend Framework Part 3

Now, with the results from Part 1 and Part 2, let’s take a deeper look into the Ext.Direct Providers to see how they work and what to do.

Prepareing a JSON Service Map Description

To use the Ext.direct framework we need to register a provider. For a remoting provider we need a service mapping description.

Ext.Direct.addProvider(Ext.apply(Ext.app.JSONRPC_API, {

    'type'     : 'zfprovider',
    'url'      : Ext.app.JSONRPC_API.target
}));

Ext.app.JSONRPC_API holds the service mapping description which in the Tine 2.0 case we deliver it with our registry data, but you can also fetch it e.g. by a javascript include as suggested in the “EXT.DIRECT REMOTING SPECIFICATION”.

<script src=”index.php?method=Tinebase.getServiceMap” type=”text/javascript”></script>

The Service Map could be generated like this:

$server = new Zend_Json_Server();

// set all classes you want to expose
$server->setClass('MyModule_Service_Json', 'MyModul');

$server->setTarget('index.php')
    ->setEnvelope(Zend_Json_Server_Smd::ENV_JSONRPC_2);

$smdArray = $server->getServiceMap()->toArray();
// save some bytes
unset($smdArray['methods']);

echo "Ext.app.JSONRPC_API = " . Zend_Json::encode($smdArray);

Ext.ux.direct.ZendFrameworkProvider

To Enable Ext.Direct to understand the JSON-SMD data and also communicate using the JSON-RPC protocol we create an own provider:

Ext.ux.direct.ZendFrameworkProvider = Ext.extend(Ext.direct.RemotingProvider, {

...

At first we need to parse the JSON-SMD and to create the stubs. This is done by overwriting the initAPI method:

// private

    initAPI : function() {
        for (var method in this.services){
            var mparts = method.split('.');
            var cls = this.namespace[mparts[0]] || (this.namespace[mparts[0]] = {});
            cls[mparts[1]] = this.createMethod(mparts[0], Ext.apply(this.services[method], {
                name: mparts[1],
                len: this.services[method].parameters.length
            }));
        }
    },


All we do is to transform the JSON-SMD definition data in to a from the original createMethod method can understand to create the stubs.

It’s important to understand, that the createMethod creates stubs which do only trigger further processing of the provider and do _NOT_ contain all the code to do requests and protocol processing.

Once a stub is called, it applies the doCall function of our provider. In order to support named parameters we inspect the call and transform a named parameter call into a positional parameter call. This is needed, cause the ZendFrameworks Zend_Json_Server is itself not able to understand named parameter calls witch by the way are indeed part of the JSON-RPC specification.

// private

    doCall : function(c, m, args) {
        // support named parameters
        if (args[args.length-1].paramsAsHash) {
            var o = args.shift();
            for (var i = 0; i < m.parameters.length; i++) {
                args.splice(i,0, o[m.parameters[i].name]);
            }
        }

        return Ext.ux.direct.ZendFrameworkProvider.superclass.doCall.call(this, c, m, args);
    },

After the parameters a prepared, the provider assembles the request according to the protocol definitions. This is done in the getCallData method.

// private

    getCallData: function(t){
        return {
            jsonrpc: '2.0',
            method: t.action + '.' + t.method,
            params: t.data,
            id: t.tid
        };
    },


This protocol encapsulation only deals with single transaction calls. The processing for batched calls is done elsewhere. Luckily The both protocols use the same simple outer array for batched calls, so that we don’t need to touch it.

Finally the response needs to be decoded according to the protocol and the callback function needs to get called using the decoded data. This is done in the onData method:

// private

    onData: function(opt, success, xhr) {
        var rpcresponse = Ext.decode(xhr.responseText);
        xhr.responseText = {
            type: rpcresponse.result ? 'rpc' : 'exception',
            result: rpcresponse.result,
            tid: rpcresponse.id
        };

        return Ext.ux.direct.ZendFrameworkProvider.superclass.onData.apply(this, arguments);
    }

And voila, thats it. This is our custom provider to enable Ext.Direct to use and talk pure standard JSON-PRC/JSON-SMD.

Finally let’s register the provider for lazy invocation

Ext.Direct.PROVIDERS['zfprovider'] = Ext.ux.direct.ZendFrameworkProvider;

Server Side Batched Requests

As noted in Part 2, the biggest problem with the Zend_Json_Server is the fact that it’s not capable to handle batched requests yet. To overcome this in a simple way, you can monitor the first character of an JSON request in your dispatcher and dispatch multiple request if it’s a ‘[‘. Of course this is far away from optimum, but it’s a start point till we have the feature in the Zend_Json_Server.

04
Sep

Data Transport with JSON-RPC via Ext.Direct and Zend Framework Part 2

As promised in my previous post, I’ll introduce Ext.Direct and discuss how it fits with the new JSON-RPC standards and especially with the Zend_Json_Server component.

Ext.direct is a namespace and a bit of a buzzword in the ExtJS 3.0 release. In short, the Ext.direct stuff introduces high level communication features.

The key component of these new features is the “EXT.DIRECT REMOTING SPECIFICATION” which covers the aspects ‘remote procedure call‘ and ’service description’. Ext.Direct implements this specifications within the ExtJS framework as service consumer and for multiple server side stacks as service provider.

Besides having a well defined and documented communication and transport layer Ext.direct also has the advantage, that other ExtJs classes dealing with data can work on the stubs, created based on the ’service map descriptions’. The Ext.tree.TreeLoader can be configured with a directFn to fetch it’s data. More over the Ext.data.DirectStore can be configured with a complete set of direct functions for CRUD actions.

An other cool thing about the javascript implementation of the Ext.direct.RemotingProvider is, that is queues request of the direct stubs for a configurable time span. After this span, it sends one batched request to the server.

However, its important to note, that the “EXT.DIRECT REMOTING SPECIFICATION” is different form the JSON-RPC/JSON-SMD specifications introduced in part 1.

While digging in the specs I found some pros and cons for the “EXT.DIRECT REMOTING SPECIFICATION” compared to the JSON-RPC standardization:

  • + It covers form posts which are needed for special actions like file upload
  • + It covers events send by the server
  • - It lacks of function parameter description
  • - It does not support named function parameters
  • - It does not support optional parameters
  • - It is not compatible to the JSON-RPC standardization efforts

The last point is IMHO the strongest point why not to use this specification. While the rest of the javascript/webservice addicted world tries to find a common standard, ExtJs goes its own way. There are already a number of implementations for the JSON-RPC and JSON-SMD out there, and more and more will follow. I also expect to see service consumers written in PHP which ease writing server-server web-services using the same API.

For that reasons I choose to take the standard Zend_Json_Server implementation and to write a Ext.ux.direct.ZendFrameworkProvider we can use in Tine 2.0 and other Zend Framework based projects.

It’s only fair to note, the the Zend_Json_Server also has several issues which needs to be improved. Most notably:

  • - It does not support batched requests
  • - It does not support named parameters

After all this theory I’ll cover the actual implementation of the Ext.ux.direct.ZendFrameworkProvider in part 3.