Published on November 11, 2012 by Toran Billups
I've spent the last few weeks getting some exposure to the world of cross domain http and all the joys that come with trying to support it cross browser. After a few weeks of painful thrashing I decided it was time to share a few insights that would have saved our team a great deal of man hours (hindsight is 20/20 after all).
So instead of the usual jQuery ajax with an inline callback
You will need to add some type of callback to the query string so your remote server can invoke it directly. My first nieve implementation looked something like the below.
One of the problems with the above is that I didn't get to use the familiar $.ajax api, but also I had to define a global callback and manage wiring it up myself. (clearly my first attempt at jsonp was less than ideal). What I learned a few days into the process was that jQuery already supports this in a more elegant way using the jsonp dataType and crossDomain flag.
Now with the client-side in order it was time to modify how the backend returned the data so the above jsonp example would actually work cross domain. With the above example your remote server can't simply return the json data as it traditionally would.
... because of the cross domain limitation mentioned above. Instead you need to pad the json data using the callback that was passed in the query string .
This technique worked well and landed us 90% of the functionality we needed. But it turns out jsonp has one very big limitation, it only works with the http GET verb (not POST sadly). So if you only pass small bits of data that can fit in a query string your off to the races. But the moment you need to pass more data, like doing a multipart http post for example, you can't use jsonp anymore. (like trying to pass a large binary file over the wire)
The new challenge was less blogged about and seemingly difficult to get right cross browser. I should clarify that cross browser in this case means supporting IE8 / Firefox / Chrome (just for clarity).
The first step required that I create a form dynamically and append it to the dom. Not normally an issue but with IE8 I found this had to be done in 2 steps. Normally you could just create a form and assign the id inline.
But for some odd reason this didn't make IE8 happy so instead I had to do the following
Now that I had a form I needed a way to post it cross domain. If I just added a form and tried to post it from one domain to another I'd get the usual cross domain error. One technique my co-worker found that worked was to create an iframe and set the action of this iframe to the endpoint on another domain. Next you append this iframe to the body. Now the form we created dynamically can be appended to the iframe (as we want to do a full http post using this form). You also need to set the target on the form to the iframe itself. If this all feels like a hack that's probably because it is.
One last hack for IE to get the multipart form post working correctly was to set both the 'enctype' and the 'encoding'. Without both of these attributes on the form IE wouldn't actually submit as a multipart form post for some odd reason.
The final client side part of this solution looks something like the below
One last issue that effects every browser in this custom file upload example is that once the response is returned to the iframe you can't reach into it to parse the response coming back. We had to work around this issue with yet another hack (long polling essentially). We found that once the iframe is used to post the data to another domain you lose access to it from within the parent body (makes sense as it would be a cross domain security violation). This is less of a problem if you can simply 'fire and forget' but if you want to give the user some feedback about when the file is finished uploading you will need to invent some type of long polling solution to tell the client side that the file upload is complete or still streaming.
Looking back it was a painful few weeks, but when you are building an epic workaround like this for something the browser was never intended to support, it's sorta expected.