AJAX requests, X-Requested-With headers, and unexpected cache contents – a post at Steven Luscher’s blog

Cyclops

I stumbled upon some curious browser behavior yesterday, and would like to share it here, lest it save someone a few hours of debugging. This story is about JSON requests, HTTP header based context switching, and browser caching.

The scenario

I was working on a live search plugin for WordPress; one that displays matching results below the search box as you type. The PHP part of the plugin was designed to hook into WordPress’ search facility and make it output JSON, for the Javascript part of the plugin to consume and display. The hook was designed to run only if a search was initiated by an AJAX request. To detect such a request, and implement the context switch, it made use of the “X-Requested-With” header sent by the MooTools framework:

function json_requested() {
  return  isset($_SERVER['HTTP_X_REQUESTED_WITH']) &&
          $_SERVER['HTTP_X_REQUESTED_WITH']  == 'XMLHttpRequest' &&
 
          isset($_SERVER['HTTP_ACCEPT']) &&
          $_SERVER['HTTP_ACCEPT'] == 'application/json';
}
 
if($this->json_requested()) {
  // Output the query as JSON
  add_action('wp', array($this, 'output_search_results_as_json'));
}

The bug

Revisiting the search results page using the browser's back button results in the regurgitation of the content that last responded at that URL, expected format or not.

An unexpected response received upon revisiting the search results page using the back button.

This worked quite well, but my client began to notice an intermittent bug. On occasion, when making a non-AJAX request for the search results page, then leaving the search results page by following a link, then clicking the browser’s back button to return, the browser would display (Internet Explorer 6) or begin to download (Firefox 3) the JSON version of the search results, instead of the expected HTML response. When I heard the word “intermittent,” I smelled a race condition, opened up my trusty Charles web debugging proxy, and set to work trying to recreate the error.

The problem

What I found was that the last response to return for a given URL would be the one that the browser would cache, no matter what kind of response you were expecting given a certain set of request headers.

Here is what Charles says about the order of server responses with the bug absent. You’re looking at a set of four AJAX requests initiated by the Javascript application, and responded to by the server, followed by a non-AJAX request initiated when the user clicked submit on the search form. 

html_response_last

Last response to arrive is the HTML one.

Next, what we have is Charles’ report at the time the bug was discovered. Three AJAX requests receive responses, and while a fourth one is in progress, the user submits the search form. As we can see, the fourth JSON request returns after the HTML search page returns. The last response across the finish line, is the one that the browser will remember for the URL /?s=food.

json_response_last

Last response to arrive is the JSON one.

Solutions

Turns out, you have at least two simple solutions in this case:

  1. Cancel all of the AJAX requests at the time the form is submitted
  2. Use a different URL according to the response you expect

In my case, I opted to make use of the cancel() method on my instance of the MooTools Request class to ensure that any AJAX requests for JSON would be killed before a request for the HTML version of the search results page even got rolling.

Tags: , , ,

be the second person to comment on this post:

Want to include some code? Use Pastie and stick the link into your comment. Linebreaks, block quotes, and preformatted code blocks will be removed.

Please make an attempt to prove that you’re still human before submitting your comment.

If you've written a post of your own in response to this one, feel free to trackback from your own site.

  1. barry Shell said “This reminds me of the psychology thing about wrong way smiles. Learn more here: http://www.drrakeshchopra.com/amoksh/?p=79 Reply to this comment.

be the second person to comment on this entry.