Samuel Williams Friday, 06 May 2011

I recently implemented some cross-domain AJAX using jQuery. I wanted to POST data using JavaScript's XMLHttpRequest to another site, and this required the use of the new HTTP access control headers1.

You can try out the functionality on the new site litepanels.co.nz - simply click the "Contact Us" link in the footer and send us message. That form POSTs data to http://www.lucidsystems.co.nz/company/contact-us/send which is responsible for actually sending us a message.

To make this work, you need to respond to the HTTP OPTIONS method with appropriate access control headers. When the client web browser tries to send the XMLHttpRequest, it first initiates an OPTIONS request to the same URI, and checks the headers. These headers specify things such as what domains can make XMLHttpRequests to this URI.

Server-side implementation

Here is an example of how to respond to the HTTP options method using the Utopia framework:


ACCESS_CONTROLS = {
	"Access-Control-Allow-Origin" => "*",
	"Access-Control-Allow-Headers" => "X-Requested-With",
	"Access-Control-Max-Age" => "60"
}

on 'index' do |request, path|
	# ...
	if request.options?
		# Allow AJAX requests from different domains.
		return :status => 200, :headers => ACCESS_CONTROLS
	end
	
	# ...
end

You can check this using curl -i -X OPTIONS $URI:

$ curl -i -X OPTIONS http://www.lucidsystems.co.nz/company/contact-us/index 
HTTP/1.1 200 OK
Date: Fri, 06 May 2011 02:25:28 GMT
Server: Apache/2.2.16 (Debian)
X-Powered-By: Phusion Passenger (mod_rails/mod_rack) 3.0.5
<span class="highlight">Access-Control-Allow-Headers: X-Requested-With</span>
<span class="highlight">Access-Control-Allow-Origin: *</span>
<span class="highlight">Access-Control-Max-Age: 60</span>
Content-Length: 0
Status: 200
Content-Type: text/plain

One problem I encountered when executing XMLHttpRequests was the fact that they process 3xx redirections transparently. So, if you return a 3xx redirect, it won't actually return this status code to your handler, but instead process the redirection3. For cross-domain requests, this can be a big problem unless you correctly specify OPTIONS for the redirected page too. Therefore, it is wise to ensure that controllers that process XMLHttpRequests return 2xx or 4xx status codes.

It is also important that controllers return the access control headers.

if request.xhr?
	# You also need to provide access control headers here.
	return :status => :success, :headers => ACCESS_CONTROLS
else
	return redirect(params["from"] ? "success" : "success-no-reply")
end

Client-side implementation

On the litepanels.co.nz, I use JavaScript and jQuery to serialize the contact form and send this data to http://www.lucidsystems.co.nz/company/contact-us/send:

$(function() {
	// The contact form element
	var contact = $("#contact");

	contact.validate({
		rules: {
			subject: "required",
			from: {
				email: true
			},
			message: "required"
		},
		submitHandler: function() {
			$('#contact-popup .main').slideUp();
			$('#contact-popup .sending').slideDown();
			
			$.ajax({
				url: contact.attr('action'),
				type: 'post',
				data: contact.serialize(),
				crossDomain: true,
				headers: {
					"X-Requested-With": "XMLHttpRequest"
				},
				success: function() {
					$('#contact-popup .sending').slideUp();
					$('#contact-popup .success').slideDown();
				},
				error: function(request, status, error) {
					$('#contact-popup .sending').slideUp();
					$('#contact-popup .error').slideDown();
				}
			});
		}
	});
})

jQuery by default doesn't set X-Requested-With for cross-domain requests, so you need to do this manually.

Further Reading

  • Mozilla Developer: HTTP Access Controls
  • WebKit DOM Programming Topics: Security Considerations
  • XMLHttpRequest: Infrastructure for the send method
  • Secure login using AJAX
  • Rack Memory Usage
  • Comments

    whats the browsers coverage on this?

    @wookiehangover All recent versions of Firefox, Safari and Chrome support this. IE supports this via XDomainRequest which isn’t supported by jQuery yet (probably because its proprietary).

    Interesting article.

    If anybody is looking to implement and consume a cross-domain JSON API with Rails and jQuery (using JSONP), I’ve written about it here: Consuming a public Rails API with jQuery.

    Like David said, I think what you are looking for is jsonp through jQuery. I did something similar to David in Grails using jsonp having jQuery consume rest service data. The only difference in my code was adding a check for “html” type requests instead of json or xml so that it could append the callback param onto the beginning. There are several examples out there. You can check out one such example here.

    If you have no control over the end point code and are consuming someone else’s data, then you might want to consider doing ajax locally and having your server invoke the endpoint to consume, format and return the data back to the client.

    Leave a comment

    Please note, comments must be formatted using Markdown. Links can be enclosed in angle brackets, e.g. <www.codeotaku.com>.