Calling a JsonRpcMethod using jquery $.ajax

178 views
Skip to first unread message

NimbleLotus

unread,
Aug 27, 2008, 6:33:30 AM8/27/08
to Jayrock
I'd like to use jquery $.ajax to call a JsonRpcMethod instead of using
the proxy. I believe the jquery approach gives me some extra
capabilities that aren't covered by the proxy e.g. timeout, caching
etc.

Can somebody explain how I construct a URL to call a specific
JsonRpcMethod?

e.g.

MyService.ashx
public class MyService : JsonRpcHandler, IRequiresSessionState
{

[JsonRpcMethod]
public JsonObject GetSomething()
{
JsonObject result = new JsonObject();
result.Put("foo","bar");
return result;
}
}

MyScript.js
$(document).ready(function() {
var ajax_REQ=$.ajax({
url: '../handlers/myservice.ashx',
type: 'GET',
cache: true,
data: "x=" + x + "&y=" + y,
dataType: 'json',
timeout: 10000,
error: errorHandler,
success: function(jsondata){
if (ajax_REQ) {ajax_REQ.abort();}
successHandler(jsondata);
}
});
}

Are my reasons valid for wanting to bypass the proxy and use jquery
instead or am I missing something?

NimbleLotus

unread,
Aug 27, 2008, 10:25:29 AM8/27/08
to Jayrock
Sorry, the solution was far simpler than I'd imagined... all I needed
to do was append the method name to the url thus:

url: '../handlers/myservice.ashx/GetSomething',

Atif Aziz

unread,
Aug 27, 2008, 10:31:12 AM8/27/08
to jay...@googlegroups.com
>>
I'd like to use jquery $.ajax to call a JsonRpcMethod instead of using
the proxy. I believe the jquery approach gives me some extra
capabilities that aren't covered by the proxy e.g. timeout, caching
etc.
Are my reasons valid for wanting to bypass the proxy and use jquery
instead or am I missing something?
<<

You don't need to bypass the proxy at all. Getting all the extra control and candy you desire is precisely why the proxy allows you to supply your own channel. A channel for the proxy is any object that supports a method named "rpc", accepting a single argument representing the call object. The call object is populated with various properties, like the URL of the service, remote method, parameters, local callback function and so forth. All you need to do is provide a jQuery-based channel. I've taken your original $.ajax call and re-wired it as a proxy channel (not much work):

function JQueryScriptChannel() {
this.rpc = function(call) {
if (call.request.params.constructor === Array)
throw new Error('Positional parameters are not supported.');
var params = [];
$.each(call.request.params, function(k, v) {
if (v) params.push(k + '=' + encodeURIComponent(v));
});
var ajax_REQ = $.ajax({
url: call.url + '/' + call.request.method,
type: 'GET',
cache: true,
data: params.join('&'),
dataType: 'json',
timeout: 10000,
// TODO error: ...
success: function(data) {
if (ajax_REQ) { ajax_REQ.abort(); }
call.callback(data);
}
});
}
}

Given the above, you can now setup a service proxy with the channel like this (DemoService below assumes you've sunk in the proxy for the demo service that's supplied with Jayrock):

var demo = new DemoService();
demo.channel = new JQueryScriptChannel();
demo.kwargs = true; // send args by name

And continue to make calls conveniently:

demo.sum(123, 456, function(response) {
alert(response.result); });
}

demo.echo('Jayrock + JQuery', function(response) {
alert(response.result); });
}

You can find a full version:
http://gist.github.com/7470

>>
Can somebody explain how I construct a URL to call a specific
JsonRpcMethod?
<<

The gory details are:

== Call Encoding Using HTTP GET ==

When using HTTP GET, the target procedure and parameters for the call are entirely expressed within the Request-URI of the HTTP message. The target procedure MUST appear as the last component of the Request-URI path component. The procedure's name MUST therefore be preceded by a forward-slash (U+002F or ASCII 47) but MUST NOT end in one.

The parameters are placed in the query component (as defined in RFC 3986) of the Request-URI, which is then formatted using the same scheme as defined for HTML Forms with the get method. Each parameters consists of a name/position and value pair that is separated by the equal sign (U+003D or ASCII 61) and parameters themselves are separated by an ampersand (U+0026 or ASCII 38):

name1=value1&name2=value2&name3=value3...

A client MAY send the parameters in a different order than in which the formal argument list of the target procedure defines them.

= Encoding of Call Parameter Values =

The client MUST NOT use the JSON syntax for the parameter values. All values MUST be plain text and observe the escaping rules defined for the query component of the URL. After decoding, the server MUST treat all values as if they were sent as JSON String values. The server MAY then perform conversions at its discretion (on a best-attempt basis) if the formal arguments of the target procedure expects other non-String values. This specification does not define any conversion rules or methods.

Parameters named identically on the query string MUST be collapsed into an Array of String values using the same order in which they appear in the query string and identified by the repeating parameter name. For instance, the following query string specifies two parameters only, namely scale and city:

city=london&scale=farenheit&city=zurich&city=new+york

The parameter scale has the single String value of "farenheit" whereas city is an Array of String values equivalent to the JSON text [ "london", "zurich", "new york" ].

It is specifically not possible to send parameters of type Object using HTTP GET.

NimbleLotus

unread,
Aug 27, 2008, 11:06:23 AM8/27/08
to Jayrock
Thanks for your very thorough explanation Atif. That works perfectly.
Jayrock and it's creators rock!

Atif Aziz

unread,
Aug 27, 2008, 2:02:32 PM8/27/08
to jay...@googlegroups.com
> That works perfectly.

Just for kicks and FWIW, I've update the code at:
http://gist.github.com/7470

It now demonstrates 3 channels over jQuery:

- jQuery JSONP with XSS
- jQuery XHR GET with caching
- jQuery XHR GET without caching

The HTML page supports dynamically switching among these channels.

The funny thing is that if you browse to the "raw" version of the above paste in IE then you can try it live in the browser with the jQuery-based JSONP channel:
http://gist.github.com/raw/7470/cb50074ab4177c62e37418ba6d59659d35e09982

IE seems to ignore the Content-Type of text/plain hinted by the server, sniffs the content as HTML, renders the page and runs all scripts. With JSONP, one gets XSS and so you can call the demo service cross-domain using the corresponding channel. The other two channels based on XHR GET will throw a permission denial error.

Firefox and Safari will not run the page and (correctly) render the source directly as plain text.

Enjoy,
Atif

NimbleLotus

unread,
Aug 29, 2008, 12:58:39 PM8/29/08
to Jayrock
Hi Atif,

My proxy js code seems to be different to yours although both are
stamped with the same version (0.9.8316). Consequently none of the rpc
channel stuff seems to be implemented.

e.g. all of this code is missing

function Call(method, params, callback)
{
this.url = url;
this.callback = callback;
this.proxy = self;
this.idempotent = idems[method];
this.request =
{
id : ++nextId,
method : m[method],
params : params
};
}

function rpc(call)
{
return self.channel != null && typeof(self.channel.rpc) ===
'function' ?
self.channel.rpc(call) : call;
}

this.kwargs = false;
this.channel = new JayrockChannel();

function JayrockChannel()
{
this.rpc = function(call)
{
var async = typeof(call.callback) === 'function';
var xhr = newXHR();
xhr.open('POST', call.url, async, this.httpUserName,
this.httpPassword);
xhr.setRequestHeader('Content-Type', this.contentType ||
'application/json; charset=utf-8');
xhr.setRequestHeader('X-JSON-RPC', call.request.method);
if (async) xhr.onreadystatechange = function()
{ xhr_onreadystatechange(xhr, call.callback); }
xhr.send(JSON.stringify(call.request));
call.handler = xhr;
if (async) return call;
if (xhr.status != 200) throw new Error(xhr.status + ' ' +
xhr.statusText);
var response = JSON.eval(xhr.responseText);
if (response.error != null) throw response.error;
return response.result;
}

function xhr_onreadystatechange(sender, callback)
{
if (sender.readyState == /* complete */ 4)
{
try {
sender.onreadystatechange = null; // Avoid IE7
leak (bug #12964)
}
catch (e) {
/* IE 6/Mobile throws for onreadystatechange =
null */
}

var response = sender.status == 200 ?
JSON.eval(sender.responseText) : {};

callback(response, sender);
}
}

function newXHR()
{
if (typeof(window) !== 'undefined' &&
window.XMLHttpRequest)
return new XMLHttpRequest(); /* IE7, Safari 1.2,
Mozilla 1.0/Firefox, and Netscape 7 */
else
return new ActiveXObject('Microsoft.XMLHTTP'); /* WSH
and IE 5 to IE 6 */
}

Am I missing something?

Simon

Atif Aziz

unread,
Aug 29, 2008, 4:55:26 PM8/29/08
to jay...@googlegroups.com
> Am I missing something?

Simon, check the *file* rather than the assembly version numbers. If your proxy code does not contain the channel support then I am certain the file versions will differ. If your file version is older, you'll need to pick up the latest developer build from below to get the channel stuff:

ftp://ftp.berlios.de/pub/jayrock

The developer build is currently equally stable as the last released version.

You can find out the version numbers by calling the "system.about" method on a service. The easiest way to do this is via the service test page. You'll get back a string like this:

> Jayrock, 0.9.8316.0 (Release build 0.9.10027.1802)
> Copyright (c) 2005, Atif Aziz. All rights reserved.
> For more information, visit http://jayrock.berlios.de/\r\n"

The version number in the parentheses indicates the assembly *file* version number. The assembly version (0.9.8316.0) remains the same as long as the builds are compatible by contract and behavior. Since the developer builds have remained compatible with the last released version, you won't notice a change there.

Hope this helps and clears up things a bit.

- Atif
Reply all
Reply to author
Forward
0 new messages