IE doesn't let us REST

Posted by Jonathan

The reason why wrote up my rant about not getting too exited about REST is that I had some fun time paying for my blind usage of REST.

While developing Webistrano I thought that this would be a nice project for playing with all the RESTful stuff that Rails currently offers. So I started to map my resources and thereby only allowing certain HTTP verbs to certain URLs.

This all works nice until reality in the form of IE hunts you down.

Until recently all my Ajax calls used HTTP POST for getting updates from the server. I used POST for so long that I didn't remember why. In Webistrano I use Ajax to periodically get status updates on a running deployment. As getting status updates translates perfectly to HTTP GET on the resource I used this code for it:

# controller
def show
  @deployment = @stage.deployments.find(params[:id])

  respond_to do |format|
    format.html # show.rhtml
    format.xml  { render :xml => @deployment.to_xml }
    format.js { render :partial => 'status' }
  end
end

# view _status.rhtml
<% unless @deployment.completed? %>
  <script type="text/javascript">    
    function update_status(){
      new Ajax.Updater('status_info','<%=h project_stage_deployment_path(current_project, current_stage, @deployment) %>',{
        method: 'get',
        evalScripts: true
      });
    }
    
    setTimeout(update_status, 3000);
  </script>
<% end %>    

This worked nicely in Safari and Firefox but Internet Explorer would update the status-div with the whole page. So you got the page-in-a-page effect. I've spend several hours trying to debug from where IE was getting this strange output and why there were no requests to the server. And then I found the answer and remembered why in the past I always used HTTP POST for my Ajax calls.

IE was caching the GET Ajax call.

In order to prevent IE from caching Ajax calls your need to either supply different parameters on each request or switch to POST. Switching to POST is not so easy as Rails will not allow POST requests to the .../deployments/1 resource. So unique parameters on each request it is:

# view _status.rhtml
<% unless @deployment.completed? %>
  <script type="text/javascript">    
    function update_status(){
      new Ajax.Updater('status_info','<%=h project_stage_deployment_path(current_project, current_stage, @deployment) %>',{
        method: 'get',
        evalScripts: true,
        parameters: {
          random_differentiator: Math.floor(Math.random()*50000) // work around IE caching bug
        }
      });
    }
    
    setTimeout(update_status, 3000);
  </script>
<% end %>

The alternative would be to define a custom action on the deployment resource that would answer to a HTTP POST but this destroys the whole "one resource URL, different representations" REST thing.

So long for RESTful Web Applications with IE.

Comments

Leave a response

  1. geoffbSeptember 23, 2007 @ 12:29 AM

    What if you explicitly add the .js extension to the url in the periodical updater?

    formatted_project_stage_deployment_path(current_project, current_stage, @deployment, :js)

    Does that get around IE’s caching?

  2. Tammer SalehSeptember 23, 2007 @ 02:03 AM

    That’s an IE bug that I didn’t know about. I’ll keep that in mind when writing ajax against a restful controller. Would it be easier to set a parameter variable to the current time in your ajax call?

  3. Dan KubbSeptember 23, 2007 @ 05:56 AM

    Are you sure that using POST is the only option? What about setting the Cache-Control and Expires headers for the response so that IE doesn’t store it in its cache? Searching for IE and “Cache-Control” returns several articles on how to do this. Hopefully this helps.

  4. JonathanSeptember 23, 2007 @ 12:09 PM

    @Tammer:

    Yes, you are right, using the current time would be a better solution.

    @Dan:

    I havent tried setting Cache-Control yet, but I also did not want to have extra header calls in development mode. In production I globally set the expire header through Apache. BTW Rails always sets the ‘Cache-Control: private’ header, so IE should not cache at all.

  5. geoffbSeptember 23, 2007 @ 05:50 PM

    NM my previous comment, I misunderstood the issue here.

    I just did a test—you can successfully tunnel a GET request via POST, using the _method parameter.

    new Ajax.Updater(‘status_info’,’<%=h project_stage_deployment_path(current_project, current_stage, @deployment) %>’,{ method: ‘post’, postBody: ‘_method=get’, evalScripts: true });

  6. geoffbSeptember 23, 2007 @ 06:55 PM

    I just noticed that the most recent version of jQuery has the current time hack built-in to its Ajax method—when you set the ‘cache’ option to ‘false’, it adds a parameter named ’_’ to the querystring with a value of (new Date()).getTime().

    Would be a nice feature to add to Prototype—you’d just set ‘cache’ to ‘false’ and you’d be good to go.

  7. NikSeptember 24, 2007 @ 06:54 AM

    Hi,

    We’ve had the same problem. I think our fix is actually checked into Edge Rails.

    Give this a go in your cache-control header:

    private, max-age=0, must-revalidate

    Result of many hours reading caching write-ups :)

  8. Richard QuadlingOctober 03, 2007 @ 02:52 PM

    A better way is to add a datetimestamp to the url of the request for ALL Ajax requests.

    Something like this …

    Ajax.Responders.register ( { onCreate: function(o_Requester) { // Timestamp each AJAX action. var o_Date = new Date(); o_Requester.url = o_Requester.url + (o_Requester.url.indexOf(’?’) == -1 ? ’?’ : ‘&’) + ‘Stamp=’ + o_Date.getTime(); } } );