Activate your free membership today | Log-in

Friday, April 18th, 2008

100 Line Ajax Wrapper

Category: XmlHttpRequest, IE, Browsers

Kris Zyp gives us a glimpse at a potential future with his 100 line Ajax wrapper that tries to do the right thing cross browser for the various cross-domain models:

With IE8’s new XDomainRequest feature, a new API is added for cross-site requests, instead of using the W3C cross-site access proposal. Just for fun, I thought I would provide a little glimpse of what the classic Ajax request wrapper function may look like for the next era of web developers. Just a few simple calls to XMLHttpRequest would be way too easy, so instead we get do this:

JAVASCRIPT:
  1.  
  2. function doRequest(method,url,async,onLoad,onProgress) {
  3.     var xhr;
  4.     if ((onProgress || isXDomainUrl(url)) && window.XDomainRequest) {
  5.         // if it is x-domain or streaming/incremental updates are needed we will use IE's XDomainRequest for IE
  6.         // streaming/interactive mode is broken in IE's XHR, but for some reason works in XDR (with onprogress), so we will
  7.         // need to use XDR if incremental updates are necessary
  8.         // see bug https://connect.microsoft.com/IE/feedback/ViewFeedback.aspx?FeedbackID=334813
  9.          if (url.match(/^https:/) && !onProgress) {
  10.             // XDR doesn’t work for secure https communication
  11.             // see bug https://connect.microsoft.com/IE/feedback/ViewFeedback.aspx?FeedbackID=333380
  12.             loadUsingScriptTag(url); // script tag insertion can be more secure than XDR
  13.                                 // in some situations because it supports https
  14.             return;
  15.         }
  16.         xhr = new XDomainRequest;
  17.         // relative paths don’t work in XDomainRequest, see bug https://connect.microsoft.com/IE/feedback/ViewFeedback.aspx?FeedbackID=333275
  18.         if (!url.match(/^http:/)) { // test to see if it is an absolute url
  19.             url = absoluteUrl(location.href,url); // must have a function to turn it into an absolute url
  20.         }
  21.         if (!(method == “GET” || method == “POST”)) {
  22.             // XDomainRequest does not support methods besides GET and POST
  23.             // see bug https://connect.microsoft.com/IE/feedback/ViewFeedback.aspx?FeedbackID=334809
  24.             // We will try to add the method as a parameter and hope the server will understand… good luck :/
  25.             url += “&method=” + method;
  26.             method = “POST”;
  27.         }
  28.         function xdrLoad() {
  29.            if (xhr.contentType.match(/\/xml/)){
  30.                 // there is no responseXML in XDomainRequest, so we have to create it manually
  31.                 var dom = new ActiveXObject(”Microsoft.XMLDOM);
  32.                 dom.async = false;
  33.                 dom.loadXML(xhr.responseText,200);
  34.                 onLoad(dom);
  35.            }
  36.            else {
  37.                 onLoad(xhr.responseText,200); // we will assume that the status code is 200, XDomainRequest rejects all other successful status codes
  38.                 // see bug https://connect.microsoft.com/IE/feedback/ViewFeedback.aspx?FeedbackID=334804
  39.            }
  40.         }
  41.         if (async === false) {
  42.             // XDomainRequest does not support synchronous requests
  43.             // see bug https://connect.microsoft.com/IE/feedback/ViewFeedback.aspx?FeedbackID=336031
  44.             // so we will try to block execution on our own (which is not really possible in any reasonable manner)
  45.             var loaded;
  46.             xhr.onload = function() {
  47.                 loaded = true;
  48.                 xdrLoad();
  49.             }
  50.             xhr.open(method,url);
  51.             xhr.send(null);
  52.             while(!loaded) { // try to block until the response is received
  53.                 // I am sure the user won’t mind just clicking OK so we can block execution
  54.                 alert(”Waiting for the response, please click OK because it probably is here now”);
  55.             }
  56.             return;
  57.         }
  58.         else {  // do an asynchronous request with XDomainRequest
  59.             xhr.onload = xdrLoad;
  60.             xhr.open(method,url);
  61.             xhr.onprogress = onProgress;
  62.         }
  63.     }
  64.     // we will mercifully skip all the branches for ActiveXObject(”Microsoft.XMLHTTP”) to accomodate IE6 and lower
  65.     else {
  66.         xhr = new XMLHttpRequest; // use the standard XHR for same origin and browsers that implement cross-site
  67.                         // W3C requests and streaming
  68.         xhr.open(method,url,async);
  69.         xhr.onreadystatechange = function() {
  70.             if (xhr.readyState == 3) // interactive mode
  71.                 onProgress(xhr.responseText);
  72.             if (xhr.readyState == 4) // finished
  73.                 onLoad(xhr.responseText,xhr.status);
  74.         }
  75.     }
  76.     xhr.send(null); // finally send the request whether it be XDR or XHR
  77.  
  78.         // and supporting functions
  79.         function absoluteUrl : function(baseUrl, relativeUrl) {
  80.                 // This takes a base url and a relative url and resolves the target url.
  81.                 // For example:
  82.                 // resolveUrl(”http://www.domain.com/path1/path2″,”../path3″) ->”http://www.domain.com/path1/path3″
  83.                 //
  84.                 if (relativeUrl.match(/\w+:\/\//))
  85.                         return relativeUrl;
  86.                 if (relativeUrl.charAt(0)==’/') {
  87.                         baseUrl = baseUrl.match(/.*\/\/[^\/]+/)
  88.                         return (baseUrl ? baseUrl[0] : ”) + relativeUrl;
  89.                 }
  90.                         //TODO: handle protocol relative urls:  ://www.domain.com
  91.                 baseUrl = baseUrl.substring(0,baseUrl.length - baseUrl.match(/[^\/]*$/)[0].length);// clean off the trailing path
  92.                 if (relativeUrl == ‘.’)
  93.                         return baseUrl;
  94.                 while (relativeUrl.substring(0,3) == ‘../’) {
  95.                         baseUrl = baseUrl.substring(0,baseUrl.length - baseUrl.match(/[^\/]*\/$/)[0].length);
  96.                         relativeUrl = relativeUrl.substring(3);
  97.                 }
  98.                 return baseUrl + relativeUrl;
  99.         }
  100.         function loadUsingScriptTag(url) {
  101.                 … do JSONP here if we want
  102.         }
  103. }

I think that says.... please just implement the standard IE team! :)

Posted by Dion Almaer at 6:45 am
2 Comments

++++-
4 rating from 8 votes

Wednesday, April 16th, 2008

Last call for W3C XMLHttpRequest comments

Category: XmlHttpRequest, Standards

The W3C has issued a last call on the XMLHttpRequest spec:

The Web API Working Group has published the Last Call Working Draft of The XMLHttpRequest Object. The XMLHttpRequest Object specification defines an API that provides scripted client functionality for transferring data between a client and a server. Comments are welcome through 2 June. Learn more about the Rich Web Client Activity.

It is nice to see things all speced out, including the fact that you can now get back real errors (SECURITY_ERR, NETWORK_ERR, ABORT_ERR).

There are future thoughts for the spec.next too:

  • load event and onload attribute;
  • error event and onerror attribute;
  • progress event and onprogress attribute;
  • abort event and onabort attribute;
  • Timers have been suggested, perhaps an ontimeout attribute;
  • Property to disable following redirects;
  • responseXML for text/html documents;
  • Cross-site XMLHttpRequest;
  • responseBody to deal with byte streams;
  • overrideMimeType to fix up MIME types;
  • getRequestHeader() and
    removeRequestHeader().

If you have some final thoughts, let them know!

Posted by Dion Almaer at 11:01 am
2 Comments

++++-
4.5 rating from 16 votes

Tuesday, April 15th, 2008

Dojo XHR Plugins; How do you want your XHR today?

Category: XmlHttpRequest, Dojo

Neil Roberts goes into the XHR Plugins that Dojo uses and how you can extend the system to have your own.

If you look at dojo.xhrGet you will see "Acceptable values are: text (default), json, json-comment-optional, json-comment-filtered, javascript, xml", but:

What you may not know is that the handleAs parameter is merely a way of specifying what plugin to use. Knowing where these plugins are, how they work, and how they can be adapted to suit your project will allow you to make repetitive tasks easy and less error-prone.

He starts by piggybacking on the json style callback, and adds a hook so when you do a query, if there are updated objects out there, they come back in the JSON so you can deal with them without having an extra remote call:

JAVASCRIPT:
  1.  
  2. dojo._contentHandlers.json = (function(old){
  3.   return function(xhr){
  4.     var json = old(xhr);
  5.     if(json.updated){
  6.       processUpdatedObjects(json.updated);
  7.       delete json.updated;
  8.     }
  9.     return json;
  10.   }
  11. })(dojo._contentHandlers.json);
  12.  

Next, Neil goes on to write a system that auto updates nodes:

JAVASCRIPT:
  1.  
  2. dojo.xhrGet({
  3.   url: "node-updates.php",
  4.   handleAs: "node-update-server"
  5. });
  6.  
  7. dojo.xhrGet({
  8.   url: "node-content.php?node=sidebar",
  9.   node: "sidebar",
  10.   handleAs: "node-update"
  11. });
  12.  

which is as easy as:

JAVASCRIPT:
  1.  
  2. dojo.mixin(dojo._contentHandlers, {
  3.   "node-update-server": function(xhr){
  4.     var json = dojo._contentHandlers.json(xhr);
  5.     dojo.forEach(json.updates, function(update){
  6.       var node = dojo.byId(update.id);
  7.       if(node){
  8.         node.innerHTML = update.html;
  9.       }
  10.     });
  11.   },
  12.   "node-update": function(xhr){
  13.     var node = dojo.byId(xhr.args.node);
  14.     if(node){
  15.       node.innerHTML = dojo._contentHandlers.text(xhr);
  16.     }
  17.   }
  18. });
  19.  

In conclusion:

Dojo makes it incredibly easy to change the way that your Ajax calls work. You can change the format of JSON your server returns without having to change any of your callbacks, you can change the handleAs type for a single function to change the data given to your callback, you can get rid of callbacks altogether and use the arguments to your xhr call determine what should be done with your results.

Posted by Dion Almaer at 6:59 am
2 Comments

++++-
4.7 rating from 15 votes

Thursday, January 10th, 2008

Cross-Site XMLHttpRequest in Firefox 3

Category: XmlHttpRequest, Security

John Resig has written up documentation of Cross-Site XMLHttpRequest that discusses the W3C Access Control working draft which Firefox 3 implements.

He gives us a nice example:

In a nutshell, there are two techniques that you can use to achieve your desired cross-site-request result: Specifying a special Access-Control header for your content or including an access-control processing instruction in your XML.

In HTML:

PHP:
  1.  
  2. <?php header('Access-Control: allow <*>'); ?>
  3. <b>John Resig</b>
  4.  

In XML:

XML:
  1.  
  2. <?xml version="1.0" encoding="UTF-8"?>
  3. <?access-control allow="*"?>
  4. <simple><name>John Resig</name></simple>
  5.  

And the XHR code itself isn't different from any other XHR code:

JAVASCRIPT:
  1.  
  2. var xhr = new XMLHttpRequest();
  3. xhr.open("GET", "http://dev.jquery.com/~john/xdomain/test.php", true);
  4. xhr.onreadystatechange = function(){
  5.   if ( xhr.readyState == 4 ) {
  6.     if ( xhr.status == 200 ) {
  7.       document.body.innerHTML = "My Name is: " + xhr.responseText;
  8.     } else {
  9.       document.body.innerHTML = "ERROR";
  10.     }
  11.   }
  12. };
  13. xhr.send(null);
  14.  

Some are excited to see the cross domain work, and some are concerned.... e.g.

I agree with Thomas. I never understood the NEED to modify the client security model to allow for this. If this is something the software needs to do, then the developer can implement a proxy on the server side. At least in this way the developer has sole discretion on the connections. Just more to go wrong if you ask me.

-

I'm still under the impression - and correct me if I'm wrong - that all these means are tailored to protect the server and its documents. But I thought the issue was to protect the client!

-

What exactly is the reason we need this? Has anybody here really understood why XMLHttp is currently limited to one host and cannot communicate cross-domain? I really do not understand that. If XMLHttp cannot do this by default, why it is still possible to load scripts and images from other servers? Why can I do exactly the same type of cross-domain communication using Flash, maybe using Silverlight in the future? What is the original reason for this limitation? Is this documented anywhere?

If, as mentioned in the spec, HTTP DELETE is problematic, because it may delete data, why cannot we filter such actions when detecting a cross-domain communication? GET and POST are possible in the same way when submitting simple form. It is even possible to generate these form elements dynamically. And this also works cross-domain. At least these two HTTP methods should be enabled by default to allow cross-domain communication. The open web, as often mentioned by Alex Russell, really needs features comparable with closed source software e.g. Flash or Silverlight.

-

I agree with those saying that this spec is misguided. But bothering users too much is also not good. How are they to know in every case what things mean?

What do you think?

Posted by Dion Almaer at 12:29 pm
11 Comments

++++-
4.1 rating from 22 votes

Wednesday, November 21st, 2007

Cross Domain XHR W3C proposal

Category: XmlHttpRequest, Security

The W3C has a new proposal titled Enabling Read Access for Web Resources which defines access control primitives to be used for cross domain XHR.

You can set control via a HTTP header:

HTML:
  1.  
  2. Access-Control: allow <*.example.org> exclude <*.public.example.org>
  3.  

or an XML processing instruction:

XML:
  1.  
  2. <?access-control allow="allow.example.org" deny="deny.example.org"?>
  3.  

so no cross_domain.xml.

Kris Zyp has written up his thoughts on the proposal:

A number of things to understand about this proposal:

  • It does not create any new vulnerabilities with existing servers. Cross domain XHR will always fail with existing servers until they have specifically added headers to define the access control. In other words it doesn’t add new vulnerabilities to the web, rather it allows those who want to add cross site access the ability to due it in a secure manner without hacks like JSONP or fragment identifier messaging.
  • Both GET and POST can currently be executed cross site with scripts tags or form submission, so many threats such as CSRF and DOS already exist, the proposal does not introduce them.
  • The proposal states that cookies should be removed from cross site requests. This will reduce the incident of cross site request forgery, and forces developers to use more secure explicit forms of authentication maintanence.
  • Developers that allow cross site access still must ensure that they are not providing privileged information to sites that should not be accessing the information. Developers that allow POST and other modifying operations should take similiar precautions.
  • This provides a fine-grained access control level. When servers define access control headers that allow cross site access, they can specify which web page domains are allowed to access their resources.

Kris also has a comparison with JSON approaches:

Cross Domain JSON Comparison

The core XMLHttpRequest object itself has also seen a recent new draft.

Posted by Dion Almaer at 6:56 am
6 Comments

+++--
3.3 rating from 44 votes

Friday, September 15th, 2006

Eliminating async Javascript callbacks by preprocessing

Category: XmlHttpRequest, Toolkit, Programming

According to Harry Fuecks in this post on the SitePoint PHP blog, using Ajax should be easier:

The Catch 22 of AJAX is, for the sake of an easy life, most of the time we want to write “synchronous code” but asynchronous is the only way to avoid some rather nasty usability issues. This means rather than being able to write simple code, as we’d like to. We’re required instead to handle this via callbacks, but that’s now introduced a whole load more potential issues.

These issues he mentions include requiring a global XMLHttpRequest object to be available and handling multiple calls to a javascript function (like if the user gets a little too impatient). To help combat these issues, Harry recommends a two projects out there that have the functionality to make life a little bit simpler:

Posted by Chris Cornutt at 8:26 am
10 Comments

+++--
3.1 rating from 41 votes

Wednesday, August 9th, 2006

Cross-Domain Ajax Insecurity

Category: Ajax, XmlHttpRequest, Security

Chris Shiflett has posted his look today at cross-domain Ajax requests and some of the security implications that can come with it, especially in a world where more and more developers are beginning to think it's okay.

Since the birth of Ajax (the term, not the technology), there has been an increasing interest in various client-side technologies, especially JavaScript. Those who have forged ahead in an attempt to innovate new ways of applying Ajax have inevitably run into the same-domain security policy of XMLHttpRequest(). As a result, there has been an increasing demand for cross-domain Ajax, and there are several creative techniques in use today to get around the same-domain restriction (none of which I consider cross-domain Ajax).

He talks about other methods that can capture the data in an Ajax request (post scanner), but notes that one of the real dangers is removing a barrier for cross-site request forgeries that most normal sites already have in place.

To illustrate, he mentions an issue Digg had with a "self-digging story" a little while back. He also includes a sort of how-to on the method that they used to accomplish the task - basically a Javascript form submit on each viewing. There are checks in place for