Distigme

A little learning is a dangerous thing.


Leave a comment

SMD (Service Mapping Description)

A decade ago, XML web services would describe themselves using the WSDL standard, which tools could use to generate a strongly typed and convenient wrapper for calling the service, such as a Java class. Fast forward halfway to today, and the challenge has moved on from accessing XML services from the server side to accessing JSON services from the browser.

Not that it’s ever been too difficult. JSON first became popular because it was already valid JavaScript (though that fact was only relevant to those willing to call the evil eval()). And JavaScript is dynamically typed. So calling a simple ajax method in modern Dojo might look like this:

require(["dojo/request"], function(request) {
	request.get("/ajax/comments/", {
		handleAs: "json",
		query: { id: 123 }
	}).then(function(result) {
		console.log(result.length + " comments.");
	});
});

JQuery code tends to have these direct ajax calls scattered everywhere. They look similar:

$.ajax({
	url: "/ajax/comments/",
	dataType: "json",
	data: { id: 123 }
}).then(function(result) {
	console.log(result.length + " comments.");
});

But the burden can be still lighter. What exactly was the URL? Does it go like comments/123 or comments?id=123? Was it a POST or a PUT? What if I forget a parameter that the server requires?

So, the natural evolution is to start writing wrapper functions in a new module:

define(["dojo/request"], function(request) {
	return {
		getComments: function(id) {
			return request.get("/ajax/comments/", {
				handleAs: "json",
				query: { id: id }
			});
		}
	};
});

Now the caller can be simplified:

require(["my/service"], function(service) {
	service.getComments(123).then(function(result) {
		console.log(result.length + " comments.");
	});
});

That’s progress, but it’s somewhat expensive progress, and you may question whether it’s really worthwhile for a method you only call once.

Enter SMD. Just write a JSON object (the SMD) that describes the API of your service, and Dojo can generate the methods.

define(["dojox/rpc/Service"], function(RpcService) {
	var smd = {
		"SMDVersion": "2.0",
		"id": "http://www.example.org/ajax/",
		"description": "Example Service",
		"target": "/ajax/", 
		"envelope": "URL",
		"services": {
			"getComments": {
				"target": "comments",
				"transport": "GET",
				"parameters": [
					{ "name": "id", "type": "integer" }
				]
			},
	return new RpcService(smd);
});

This example shows the SMD embedded in a Dojo module, but you can also put it in a separate file.

You can imagine that if a team has one Java or PHP expert writing the API and one JavaScript expert consuming the API, a single file to define the interface is a big help. If the back-end guy needs to change a URL, instead of sifting through the mountain of unfamiliar JavaScript and changing every ajax call, he just changes it in one place in one file. The front-end guy, meanwhile, can look at the SMD and see at a glance what services are available.

Better yet, it should be possible to generate the SMD from, say, a Spring application. I’m not aware of that having been done yet, though. But Struts seems to have something.

In fact, SMD seems to have not yet caught in the five years since Dojo began to support it. There is an old post on SitePen introducing it, and then… not a peep. The Dojo Toolkit now has some basic documentation on dojox/rpc (note that dojo/rpc is legacy), along with the SMD 2.0 proposal, but that’s about it. In contrast, JSON Schema, on which SMD is built, seems quite active. The lack of resources on modern SMD is what motivated me to write this post.

SMDs for a few popular sites (Google, Wikipedia, Twitter) are included in dojox/rpc/SMDLibrary. Naturally, since you would use these in a cross-domain scenario, they all go over JSONP.

Here is a longer example:

define(["dojox/rpc/Service", "dojox/rpc/Rest"], function(RpcService, RpcRest) {
	var smd = {
		"SMDVersion": "2.0",
		"id": "http://www.example.org/ajax/",
		"description": "Example Service",
		"target": "/ajax/", 
		"envelope": "URL",
		"services": {
			"setPassword": {
				"target": "my-password/",
				"transport": "POST",
				"parameters": [
					{ "name": "passwordOld", "type": "string" },
					{ "name": "password", "type": "string" }
				],
				"returns": { 
					"type": "object",
					"properties": {
						"isValidPasswordOld": { "type": "boolean" }
					}
				}
			},
			"status": {
				"target": "status",
				"envelope": "PATH",
				"transport": "GET",
				"parameters": [
					{ "name": "id", "type": "integer" }
				],
				"returns": "string"
			},
			"comment": {
				"target": "comment/",
				"transport": "REST",
				"parameters": [
					{ "name": "id", "type": "integer" }
				]
			}
	};
	return new RpcService(smd);
});

Here we see the setPassword method as a POST, returning a complex object. The status method builds a URL from its parameter like /ajax/status/123 and returns some text. comment is actually a REST endpoint supporting multiple methods (we have to explicitly require dojox/rpc/Rest for this to work). So now we can make calls like these:

require(["my/service"], function(service) {
	service.setPassword("old", "new");
	service.setPassword({ passwordOld: "old", password: "new" });
	service.comment.delete(86);
	service.status(42).then(function(status) {
		alert(status);
	});
});

You can call service methods two different ways, as illustrated here by setPassword: you can pass the parameters in order, the same order specified in the SMD, or you can pass an object that maps parameter names (if you named your parameters in the SMD) to their values, which is handy for when you can load such an object from a form, for example.

Every service method returns a promise, so you can and should handle the states of pending, succeeded, and failed, and you need then() or when() to access return values once they arrive.

SMD is pretty straightforward to use and has clear benefits for structuring your code on a large project. Better still, you can adopt it gradually, using SMD-generated services in some places and direct ajax calls in others. There is also potential for tools to make use of SMD in other ways (e.g., a handy test bench for manually invoking methods), but I don’t expect much of these until SMD sees wider adoption.

Advertisements


4 Comments

Ajax and Spring Security form-based login

If your web application uses a form-based login through Spring security, and the same application uses ajax, you likely have a problem. Say your user opens two tabs for your application in the browser and logs out from one of them, while the other is periodically updating via ajax. Or, say your user comes back after lunch, to a page whose session has long timed out, and does something that attempts ajax. Or, say the user just pushes the Back button after logging out or timing out, and that pages uses some ajax. The default behavior is to treat the ajax request like any other web page and redirect to the login form. Not good.

Aside from the obvious absurdity, there are some real problems. First, it is not straightforward for your JavaScript code to determine what went wrong; it just sees a redirect followed by a successful GET of the login page, which looks fine and dandy apart from being HTML instead of JSON. Second, if the user, in another tab, tries to access a secured page, and an ajax call hits while they are typing in their password, the user will end up looking at a page full of JSON instead of their intended destination. That’s because Spring Security, by default, determines the destination after login from whatever was most recently stored in the session. (Really? The session??? I guess that’s because if you submit a form on an expired session, you don’t want to lose all your typing, but there’s no better place to keep the form data.)

Umar offers a solution based upon writing a custom ExceptionTranslationFilter, which ends up being really cumbersome because the <http> security namespace does not play nicely with it and has to be expanded into all its gory innards.

My solution is similar but overcomes this difficulty. The two steps are to configure the request cache to reject ajax requests and to replace the authentication entry point with one that rejects ajax requests.

<bean id="nonAjaxRequestMatcher" class="org.example.NonAjaxRequestMatcher" />

<bean id="loginUrlAuthenticationEntryPoint" 
	class="org.springframework.security.web.authentication.LoginUrlAuthenticationEntryPoint">
	<constructor-arg value="/login" />
</bean>

<bean id="ajaxAuthenticationEntryPoint" 
	class="org.springframework.security.web.authentication.Http403ForbiddenEntryPoint" />

<bean id="authenticationRequestCache" 
	class="org.springframework.security.web.savedrequest.HttpSessionRequestCache">
	<property name="requestMatcher" ref="nonAjaxRequestMatcher" />
</bean>

<bean id="authenticationEntryPoint" 
	class="org.springframework.security.web.authentication.DelegatingAuthenticationEntryPoint">
	<constructor-arg>
		<map>
			<entry key-ref="nonAjaxRequestMatcher" value-ref="loginUrlAuthenticationEntryPoint" />
		</map>
	</constructor-arg>
	<property name="defaultEntryPoint" ref="ajaxAuthenticationEntryPoint" />
</bean>

<security:http entry-point-ref="authenticationEntryPoint" ...>
	<security:request-cache ref="authenticationRequestCache" />
	...
</security:http>

This works by defining a criterion nonAjaxRequestMatcher that will identify only non-ajax pages that should be redirected as needed to the login page. This can be plugged directly into the authentication request cache. The authentication entry point is a bit more complicated, but it can all be set up in Spring as shown. A DelegatingAuthenticationEntryPoint is used to ask which type of request we’re dealing with and then send interactive requests to a standard loginUrlAuthenticationEntryPoint and ajax requests to ajaxAuthenticationEntryPoint, which returns 403 Forbidden.

The NonAjaxRequestMatcher can be implemented simply:

public class NonAjaxRequestMatcher implements RequestMatcher {
	@Override
	public boolean matches(HttpServletRequest request) {
		return !"XmlHttpRequest".equalsIgnoreCase(request.getHeader("X-Requested-With"));
	}
}

This bit of magic relies on a convention built into most modern JavaScript frameworks (Dojo, JQuery, etc.) to identify ajax requests by a special header.

Lastly, you can also go one step further and write a custom replacement for Http403ForbiddenEntryPoint that will return JSON instead of HTML, as many JavaScript frameworks seem surprised when you say to expect JSON and the error page has HTML instead (!).