Cappuccino : JSON and JSONP services call / CPJSONPConnection

Posted: October 1, 2008 in Apple, IT/Dev
Tags: , , , ,

Client applications based on Cappuccino communicate with servers (remote services) through basic HTTP requests, whose message content (body) can be in various formats, either XML (the schema as to be defined, there isn’t any rule there – this is called XML-RPC) or the standardized and easy to read JSON format (isn’t XML). The message could also be wrapped in a SOAP enveloppe, but it is less frequent (because more complex to deal with) when calling webservices from a rich client (especially if the caller isn’t a Java program – wich is the case with Cappuccino, pure javascript – because we cannot then use generated Axis proxies). Then we will consider here that HTTP requests are originated from javascript client code (that is AJAX – the use of XMLHttpRequest javascript class), the only way to provide a partial refresh in the client-side page. The problem that arises is that, for security concerns, Ajax (XMLHttpRequest class) isn’t allowed to make cross-domain calls, that is the domain of the requested url must be the same as the one from the current page (this is the case at Cjed Audio Home site : news uncollapsing/collapsing is done sending an Ajax request to a php script in the same domain).

In order to circumvent this limitation, a trick has been used, that is known as JSONP (JSON with padding). The point is to dynamically add in the page (through DOM) a script element of javascript type, whose source is pointing to the service url in the other domain. In this situation the cross-domain call is allowed. As we are in an AJAX call context, the end of processing on the server side (extracting news content, doing a search, etc.) must trigger a callback function on the client-side in order for the refresh to happen (use of the result).
The second trick is to not simply return the result – in JSON or other format -, as it wouldn’t produce any result on the client side (the call originates from a script include), but to return a javascript method call instruction – in javascript – (the famous callback function), with passing the result string computed on the server side as a parameter to that callback function call (this parameter can be in JSON or XML format).
For example the result string will be myCallBackFunction( { “x”: 10, “y”: 15} ) if the process returns two integer values in JSON format. Then the interpreted result will trigger the callback javascript function on the client-side (possible from the script area), with the return result passed as a parameter to it. As for the server to know what callback function name to prepend at the start of the result string (myCallBackFunction area), we have to pass that information by adding an aditionnal request parameter to the service url.
Examples :
http://search.yahooapis.com/ImageSearchService/V1/imageSearch?appid=YahooDemo
&output=json&callback=callBackResult&query=searchValue

http://www.flickr.com/services/rest/?method=flickr.photos.search&tags=searchTag&media=photos&machine_tag_mode=any
&format=json&api_key=ca4dd89d3dfaeaf075144c3fdec76756
&jsoncallback=CPJSONPConnectionCallbacks.callbackXX.

The name of the parameter that stores the callback function name varies depending on the JSONP service called (here callback for Yahoo and jsoncallbacl for Flickr), the same for another parameter that provides the result format (json).
We simply simulate here how the XMLHttpRequest API operates, without relying on in (because it cannot be used for security concerns)… but there is to know if it is a secure way…

Through the source code of Flickr Photo Demo Cappuccino application, we discover the CPJSONPConnection class.
It is instanciated (in the AppController class in that example) through the following Objective-J (javascript) instructions :

var request = [CPURLRequest requestWithURL:"JSONP service url"];
(this url contains a parameter that specifies the result format in the returned string, json here)
var connection = [CPJSONPConnection sendRequest: request callback: "jsoncallback" delegate: self];
(for that service, Flickr JSONP, the parameter that stores the callback method’s name is called jsoncallback)

In the CPJSONPConnection source code we discover that the CPJSONPConnectionCallbacks.callbackXX value is used as for the callback name (that is the value passed to the jsoncallback request parameter – that parameter name is an instance variable of the CPJSONPConnection class). This value is defined as a javascript function (stored in a javascript callback functions array) that contains the following Objective-J (javascript) code :
[_delegate connection:self didReceiveData:data];
[self removeScriptTag];

The connection: didReceiveData method is then called (when the returned javascript is interpreted from the script area) on the AppController class (defined as the delegate when using CPJSONPConnection sendRequest: callback: delegate: – we passed self). This delegate method signature is as follows :
(void)connection:(CPJSONPConnection)aConnection didReceiveData:(CPString)data
Inside that method we can directly extract informations from the returned string – data parameter – (JSON format here, that is the expected result – ie without the prepended callback method name).

If the called service url is hosted in the same domain as the current page, the previous JSONP mechanism isn’t required, and we can simply use the XMLHttpRequest class, wrapped (hidden) by the CPURLConnection class (the call is then pure classical Ajax). We specify the delegate object during the CPURLConnection instance creation, using the following method :
connectionWithRequest:(CPURLRequest)aRequest delegate:(id)aDelegate
(the passed url parameter is built the same way as previsously, creating a CPURLRequest object)
However, as here the call will finally be triggered by XMLHttpRequest (will provide the callback method name – always the same) there is no need anymore to pass the callback method name to the service through the url (that information is passed to the XMLHttpRequest class, that manages the callback triggering). So the service only returns the JSON message in the resulting string (no need to prepend the javascript callback method name, we are in a classical Ajax call context).
We only have to implement, in the delegate object (typically AppController), the following method (this name is hard-coded as the XMLHttpRequest callback name in CPURLConnection class implementation, when the later start method is called) :
-(void)connection:(CPURLConnection)aConnection didReceiveData:(CPString)data

If the returned data string is in JSON format (and we use either a CPJSONPConnection or a classical Ajax call through CPURLConnection) we can easily deserialize that string into a structured javascript object, thanks to the CPJSObjectCreateWithJSON method :
Example :
-(void)connection:(CPURLConnection)aConnection didReceiveData:(CPString)data
{
var myJSObject = CPJSObjectCreateWithJSON(data);
...
}

For rich client applications, the requested business services are more likely to be hosted on other severs (and so other domains) that the client interface page, then the JSONP solution will be required.
If the server service is implemented in php, its returning string will look like this (considering the result part is in JSON format) :
<?=$_GET['jsoncallback']?>( { "greeting":"Hello from request."} )

Update : see also the CP2JavaWS Cappuccino client to Java remote services bridge, that allows easy call of remote business services, using provided proxy (client-side) and JSON servlet (server-side). It completely hides the CPJSONPConnection and CPURLConnection classes, manages encoding/decoding and namespace of services methods’s parameters and return (full objects graphs including nested heterogeneous collection elements), call of a delegate handlers for success response and fail (passing the decoded return graph), in the same way (use syntax) as GWT does (but without any generation step). Objects attributes serialization is automatic but can be redefined by implementing the CPCoding protocol in custom objects.

About these ads
Comments
  1. user says:

    Hi.

    For some reason when I change the Flikr url to a url of choice, the method “- (void)connection:(CPJSONPConnection)aConnection didReceiveData:(CPString)data” isnt called. The url i am providing return json data in the exact format of the Flikr url. have you any idea why this is happening?

    Thanks

    • Stu says:

      Funny, I am having the same problem with the didReceiveData method not being called when I supply a different url. Any luck finding the problem?

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s