Sending JSON-RPC 2.0 requests with jQuery

Dispatching JSON-RPC 2.0 – style requests with jQuery is easy. Just use the jQuery.post() method:

var url = "http://rpc-service.net/jsonrpc2/";

var request = {};
request.method = "ldap.search";
request.params = {};
request.params.CID = "45d0677d-a336-463b-ad99-c82137d03a00";
request.params.baseDN = "ou=people,dc=example,dc=com";
request.params.scope = "ONE";
request.params.filter = "(givenName=John)";
request.id = 1;
request.jsonrpc = "2.0";

function displaySearchResult(response) {

        if (response.result)
                alert(response.result);

        else if (response.error)
                alert("Search error: " + response.error.message);
};

$.post(url, JSON.stringify(request), displaySearchResult, "json");

The above example assumes that you have jQuery 1.4+ and the json2 encoder/decoder installed.

For those of you who are new to JSON-RPC 2.0 – this is the second release of the simple remote-procedure call (RPC) protocol using JSON messages. I’ve incorporated it in a number of my own software products, the idea being that it would be better for all of us – customers and developers alike – to standardise on a common JSON protocol for web services, instead of every one coming up with their own ad-hoc JSON protocol. I’ve spun off my minimalist Java implementation into a separate client/server library.

The JSON-RPC community has its forum at Google groups: http://groups.google.com/group/json-rpc

JSON-RPC 2.0 wisdom

Bits of wisdom related to the second release of the JSON-RPC protocol and its use.

Why JSON-RPC 2.0?

Why choose version 2.0 of the JSON-RPC protocol over the original 1.0?

Because JSON-RPC 2.0 has:

  • Named parameters You’re no longer limited to array parameters. RPC methods can now accept parameters specified by name (as key-value pairs in a JSON object).
  • A structured error object The original version didn’t specify how the error object should be structured, which can lead to incompatibilities. With 2.0 the format of the error object has been standardised to include an integer code, message and optional data.

Choose named parameters whenever you can

What do you gain from using named instead of positional parameters in your JSON-RPC 2.0 APIs?

  • Self-descriptive requests Each parameter is in effect labeled. This improves readability and reduces the likelihood of coding errors. Consider for example the equivalent positional and named params
    [1001, 1010, 1995.05, true]
    {"sender":1001, "recipient":1010, "amount":1995.05, "receipt":true }
    
  • Flexibility Named parameters can be entered in any order. Named parameter APIs also make it easier to have methods with multiple optional parameters. And finally, new parameter names can be added in future without breaking backward API compatiblity.

Composing JSON-RPC 2.0 requests in JavaScript

This is easy. After all, JSON has its origin in JavaScript.

Composing a positonal params request:

var request = {};
request.method = "ldap.search";
request.params = [];
request.params[0] = "45d0677d-a336-463b-ad99-c82137d03a00";
request.params[1] = "ou=people,dc=example,dc=com";
request.params[2] = "ONE";
request.params[3] = "(givenName=John)";
request.id = 1;
request.jsonrpc = "2.0";

Composing a named params request:

var request = {};
request.method = "ldap.search";
request.params = {};
request.params.CID = "45d0677d-a336-463b-ad99-c82137d03a00";
request.params.baseDN = "ou=people,dc=example,dc=com";
request.params.scope = "ONE";
request.params.filter = "(givenName=John)";
request.id = 1;
request.jsonrpc = "2.0";

PS: This info originally appeared in the Tips + Tricks section of my JSON-RPC 2.0 Base + Shell software.

JSON-RPC 2.0 Shell 1.4

JsonRpc2 Console ClientThe interactive shell for querying remote JSON-RPC 2.0 web services received a new feature in line with the recent release of CORS Filter 1.0 – there is now a command line option to set an “Origin” header to simulate cross-origin browser requests. For those of you who still don’t know, Cross-Origin Resource Sharing (CORS) is a recent W3C effort to introduce a standard mechanism for making cross-origin requests from browsers. CORS is now supported by Firefox 3.5+, IE 8, Safari 4+ and Chrome 3+.

The origin request header can be used by cross-site aware web servers to determine whether a particular HTTP request is CORS and what access policy to apply to it.

Here is an example “Origin” HTTP header which the browser inserts in cross-site XHR to indicate that the original web page/script is from http://example.com:

Origin: http://example.com

To set an “Origin” request header for a particular JsonRpc2 Shell session use the optional -o, --origin argument:

java -jar jsonrpc2shell.jar -o http://example.com http://rpc-host.net:8080/jsonrpc2/

The latest version of the JSON-RPC 2.0 Shell is available at http://software.dzhuvinov.com/json-rpc-2.0-shell.html

The CORS W3C specification: http://www.w3.org/TR/cors/

The CORS Filter software is at http://software.dzhuvinov.com/cors-filter.html

Performance vs availability

I recent discussion on the LDAP experts group made me reflect on a common issue with server applications where one occasionally has to make a compromise between performance and availability. The particular example was that multi-master LDAP directory installations are significantly slower than single servers.

System architects often have a requirement to provide “high availability” and “immediate” failover. But what are the end-user perceptions of that? In my opinion users tend to appreciate responsive apps much more than redundant but painfully slow service. Provided outages are relatively rare (e.g. not more often than couple of times per year) and don’t disrupt business by much (e.g. for less than an hour). Which should be quite a reasonable expectation with modern high-grade hardware and standard proven software.

Next JsWorld release scheduled for November 2010

JsWorld iconToday I looked up Unicode’s tentative schedule for their next CLDR 1.9 release. It is planned for the 10th of November, which means that I’ll be able to update the JsWorld locale definition files soon thereafter and release in turn a JsWorld update. The next JsWorld version will probably be 2.4.

For those if you who don’t know, JsWorld is a comprehensive JavaScript library for formatting and parsing of numbers, currency and date/time inside the browser, in a user specified locale. The locale data strictly follows Unicode’s sources and I strive to update it as soon as the consortium releases a new snapshot of their database. Currently Unicode keeps track of over 300+ world locales, 200+ languages and 100+ currencies.

The future of the web is cross-domain, not same-origin

CORS FilterLast month I released CORS Filter, the first universal software solution for fitting Cross-Origin Resource Sharing (CORS) support to Java web applications. CORS is a recent W3C effort to introduce a standard mechanism for enabling cross-site requests in web browsers and servers.

Since the early days of the web (think Netscape 2.0) browsers have enforced, to various degrees, a same origin policy to prevent leaking of confidential user data to third party sites. The same origin policy was carried over to the game-changing XHR which appeared in the early 2000’s. Modern web applications, however, incresingly seek to dynamically integrate content and services from third parties, which is currently achieved through “hacks” such as JSONP. CORS was created in recognition that cross-domain requests are here to stay and therefore they’d better be standardised.

The web context of CORS
The web context of CORS

The philosophy of CORS

CORS works in two directions:

  1. From a browser script perspective: By imposing tighter controls on data exchanged during a cross-site request. Cookies, for instance, are blocked unless specifically requested by the XHR caller and allowed by the remote web service. This is done to reduce the said risk of data leaks.
  2. From a web service perspective: By requiring the browser to report the origin URL to the target cross-site service, so the latter can determine, based on its origin policy, whether to allow or deny the request.

The original CORS specification is available at http://www.w3.org/TR/cors/

Note that in order for CORS to work, it must be supported by both browser and web server.

Browsers supporting CORS

The following major browsers support CORS as of October 2010:

  1. Firefox 3.5+
  2. Internet Explorer 8+ (Partial support via the XDomainRequest object)
  3. Apple Safari 4+
  4. Google Chrome 3+

Ajax style LDAP access

Json2Ldap iconAjax-style directory access is easy with Json2Ldap.

What you need:

  1. A Json2Ldap installation to take in directory requests in the form of JSON messages and translate them to the binary LDAP protocol (and then back).
  2. An LDAP v3 compatible directory server, such as OpenLDAP, Microsoft Active Directory, IBM Tivoli Directory Server or Novell eDirectory.
  3. A JavaScript library to streamline the dispatch of XMLHttpRequests, my favourite is jQuery. Also a JSON encoder/decoder.

To utilise a remote directory you must connect to it first. Instead of devising its own message schema, Json2Ldap speaks standard JSON-RPC 2.0. Here is how the connect request is composed in JavaScript:

var request = {};
request.method = "ldap.connect";
request.params = {};
request.params.host = "ldap.host.net";
request.params.port = 389;
request.id = 0;
request.jsonrpc = "2.0";

The host and port parameters specify the network location of the LDAP server. Serialised to JSON the request may look like that:

{
 "method" : "ldap.connect",
 "params" : { "host" : "ldap.host.net", "port" : 389 },
 "id" : 0,
 "jsonrpc" : "2.0"
}

We then send off the request using jQuery’s HTTP POST wrapper. If all goes well the callback function will receive a result that contains an LDAP connection identifier, which we must save for later requests.

// The Json2Ldap URL
var url = "http://tomcat.host.net:8080/json2ldap/";

// The LDAP connection CID
var cid = null;

// The callback function
function saveCID(response) {
	if (response.result)
		cid = response.result;
	else if (response.error)
		alert("Connect error: " + response.error.message);
};

$.post(url, JSON.stringify(request), saveCID, "json");

Json2Ldap’s web API covers the entire set of standard LDAP commands as well as a few extended controls and operations. Search is however perhaps the most commonly used directory command.

function displaySearchResult(response) {	
	if (response.result)
		alert("Found " + response.result.matches.length + "match(es)");		
	else if (response.error)
		alert("Search error: " + response.error.message);
};

var request = {};
request.method = "ldap.search";
request.params = {};
request.params.CID = cid;
request.params.baseDN = "ou=people,dc=example,dc=com";
request.params.scope = "ONE";
request.params.filter = "(givenName=Agnese)";
request.id = 1;
request.jsonrpc = "2.0";

$.post(url, JSON.stringify(request), displaySearchResult, "json");

Here is an example search result entry, formatted as LDIF (users have got choice – Json2Ldap allows for JSON as well as LDIF result formatting).

Sample search result entry in LDIF format

In one of my next posts I’ll give you some cool mashup examples utilising the Json2Ldap web service 🙂

Json2Ldap with improved directory search

Json2Ldap iconJson2Ldap 1.5 is out.

The most recent release of the web gateway for connecting to LDAP v4 compatible directories via JSON-RPC delivers several incremental improvements, the most notable being the expanded capabilities of the ldap.search command. It now gives programmers finer control over the entry attributes which the method returns.

The available choices:

  • Return all user attributes.
  • Return all operational attributes (attributes associated with a directory object for administrative purposes).
  • Return only the specified attributes.
  • Don’t return any attributes.

Let’s illustrate these choices with a few example JSON-RPC 2.0 requests/responses.

First, connect to the default remote directory with ldap.connect.

The connect request:

{ "method"  : "ldap.connect",
  "id"      : 1,
  "jsonrpc" : "2.0" }

The connect response, returning a string token to identify the LDAP connection (CID = connection identifier) in later requests:

{ "result"  : "-31ccd4bdbe6170b69956fe1c2eeffb42", 
  "id"      : 1,
  "jsonrpc" : "2.0" }

Example 1: Return all user attributes.

This is the default action. To do that simply omit the attributes parameter (or set it to “*”).

The request:

{ "method" : "ldap.search",
  "params" : { "CID"    : "-31ccd4bdbe6170b69956fe1c2eeffb42",
               "scope"  : "ONE",
               "baseDN" : "ou=people,dc=example,dc=com",
	       "filter" : "(uid=user.0)" },
  "id":1,
  "jsonrpc" : "2.0" }

The response:

{ "result"  : { "matches" : [ { "DN"          : "uid=user.0,ou=People,dc=example,dc=com",
                                "objectClass" : [ "person",
						  "inetorgperson",
						  "organizationalperson",
						  "top"],
			        "uid"         : ["user.0" ],
				"cn"          : ["Aaccf Amar"],
				"sn"          : ["Amar"],
				"givenName"   : ["Aaccf"],
				"initials"    : ["ASA"],
				"mail"        : ["user.0@maildomain.net"],
				"street"      : ["0125 Chestnut Street"],
				"l"           : ["Panama City"],
				"mobile"      : ["+1 010 154 3228"] } ],
                "referrals" : [] },
  "id"      : 1,						
  "jsonrpc" : "2.0" }

Example 2: Return all operational attributes

Set the attributes parameter to “+”:

The request:

{ "method" : "ldap.search",
  "params" : { "CID"        : "-31ccd4bdbe6170b69956fe1c2eeffb42",
               "scope"      : "ONE",
               "baseDN"     : "ou=people,dc=example,dc=com",
	       "filter"     : "(uid=user.0)",
	       "attributes" : "+" },
  "id":1,
  "jsonrpc" : "2.0" }

The response:

{ "result"  : { "matches" : [ { "DN"                : "uid=user.0,ou=People,dc=example,dc=com",
                                "subschemaSubentry" : ["cn=schema"],
			        "entryUUID"	    : ["ad55a34a-763f-358f-93f9-da86f9ecd9e4"],
			        "entryDN"	    : ["uid=user.0,ou=people,dc=example,dc=com"],
			        "modifiersName"     : ["cn=Directory Manager,cn=Root DNs,cn=config"],
			        "modifyTimestamp"   : ["20100622033521Z"] } ],
                "referrals" : [] },
  "id"      : 1,						
  "jsonrpc" : "2.0" }

Example 3: Return only the specified attributes

Set the attributes parameter to a string listing the required attribute names.

The request:

{ "method" : "ldap.search",
  "params" : { "CID"        : "-31ccd4bdbe6170b69956fe1c2eeffb42",
               "scope"      : "ONE",
               "baseDN"     : "ou=people,dc=example,dc=com",
	       "filter"     : "(uid=user.0)",
	       "attributes" : "cn mail" },
  "id":1,
  "jsonrpc" : "2.0" }

The response:

{ "result"  : { "matches" : [ { "DN"   : "uid=user.0,ou=People,dc=example,dc=com",
                                "cn"   : ["Aaccf Amar"],
				"mail" : ["user.0@maildomain.net"] } ],
                "referrals" : [] },
  "id"      : 1,						
  "jsonrpc" : "2.0" }

Example 4: Don’t return any attributes

Set the attributes parameter to an empty string.

The request:

{ "method" : "ldap.search",
  "params" : { "CID"        : "-31ccd4bdbe6170b69956fe1c2eeffb42",
               "scope"      : "ONE",
               "baseDN"     : "ou=people,dc=example,dc=com",
	       "filter"     : "(uid=user.0)",
	       "attributes" : "" },
  "id":1,
  "jsonrpc" : "2.0" }

The response:

{ "result"  : { "matches"   : [ { "DN" : "uid=user.0,ou=People,dc=example,dc=com" } ],
                "referrals" : [] },
  "id"      : 1,						
  "jsonrpc" : "2.0" }

So, what’s next on the Json2Ldap roadmap? HTTP Cross-Origin Resource Sharing (CORS) support!

Retrieving enum parameters from JSON-RPC 2.0 requests

JSON-RPC 2.0Since version 1.9.2 of the JsonRpc2-Base library its utility classes add support for convenient retrieval of enumerated request parameters. Such occur in situations when an RPC parameter has a fixed set of possible values. A typical example is a person’s sex, which can be either “male” or “female”.

So, if you’ve got an enum class defined, you can directly retrieve the parameter as the matching enum constant.

The enum class definition:

public enum Sex { 
        MALE, 
        FEMALE 
}

Retrieving the enum parameter from a JSON-RPC 2.0 request (assuming named parameters):

JSONRPC2Request req = JSONRPC2Request.parse("... the request string ...");

// Create a new retriever for named parameters
Map params = (Map)req.getParams();
NamedParamsRetriever ret = new NamedParamsRetriever(params);

// Get the enum param as constant
Sex sex = ret.getEnum("sex", Sex.class);

if (sex == Sex.MALE)
       System.out.println("Got male");
else
       System.out.println("Got female");

The first argument of the getEnum method specifies the parameter name, the second the defining enum class.

Clever bit: If the parameter value is not matched by a constant in the enum class the method will automatically throw a standard “Invalid parameters” JSONRPC2Error with code -32602.

There are also variations of the getEnum method that allow for case insensitive matching or for optional parameters that will be given a default value if they are omitted from the request.

24 часовият маратон издание 2010 наближава!

На 11+12 септември Крива спица събира байк терминатори от всички краища на България на 24 часов планински маратон по жестоки пътечки около х. Здравец. Елате, преживяването ще бъде яко и за всички – както за състезатели, така и за зрители!!!

Маратонът, тази година в четвъртото си издание, вече се превръща в традиция. Какво да очакваме този път? Ново трасе, по-големи награди, а може би и нови лица и нови спортни постижения 🙂 Крива спица осигурява перфектната организация, а вие – състезатели и зрители – хубавата емоция 🙂

За подробности – регистрация, трасе, правила – вижте специалния 24h сайт на Крива спица.