IE7 header issues
July 20th, 2008
I got to deploy a new build to TheWineSpies.com this weekend, which among many other things, upgraded the app to rails 2.1. This process had me refactoring the admin to be RESTfull-er, and incorporating the latest version of Fleximage. I thought everything was dandy.
The Problem
My client was doing some testing on this code with IE7, their browser of choice. But something odd was happening. They would click a link triggering a resources show action, and instead of a html page about that resource, they would just get the image. At first I was stumped. I never thought cross browser issues could change server behavior. Firefox got a page of HTML and IE7 got an image. And I confirmed that the server was actually sending different content to the different browsers. The truly bizarre part, was that after clicking a link to this page, you could refresh and get the content you were expecting.
What the hell?
The Code
The Wine Spies uses Fleximage enahnced models across a few different resources, but here is a simple action that was causing me grief.
1 2 3 4 5 6 7 8 9 10 |
def show @product = Product.find(params[:id]) respond_to do |format| format.html format.jpg { render :template => '/some/path/small.flexi' } format.xml { render :xml => @product.to_xml } end end |
This is pretty straightforward.
- If
htmlis requested, render the default template - If
jpgis requested, render some.flexitemplate - If
xmlis requested, dump the object to xml and send it out
But still, IE7 on a url of /products/123 would trigger the jpg format renderer. Somehow rails was thinking that IE7 wanted a jpeg image instead of HTML. Where other browser were making rails think they want HTML unless overridden by a format extension on the url.
Tracking down the cause
I began to think back to the DHH keynote “World of Resources” and some of the early stuff that was forming around rails, and luckily, I remembered something important. I tend to use this format pattern a lot, and I usually trigger it by url extensions like /products/123.jpg or /products/123.xml. But rails looks at something else too.
Your browser, or any HTTP client, sends along a header named HTTP_ACCEPT with any request. I decided to add some debug code to my action to see what was in this header:
raise request.headers['HTTP_ACCEPT'] |
In Firefox 3, the result was (minor edit for simplicity):
text/html,application/xhtml+xml,application/xml,*/* |
This is a comma separated list of MIME types. The purpose of this header is to let to receiver know what kind of data the client wants, or what it will “accept”. It’s a prioritized list where the first items are wanted more then the items at the end of the list. Firefox is saying “Please give me html. If you don’t have that give me xhtml. But I guess xml would be ok too if that’s all you got. You got none of those? Well give me whatever you got then, I don’t care.”
Ok what does IE7 send?
image/gif, image/x-xbitmap, image/jpeg, image/pjpeg, application/x-shockwave-flash, application/ag-plugin, */* |
Until I refreshed the page, instead of clicking a link to this page, and got:
*/* |
What the hell?
IE7 doesn’t say it wants HTML at all! It says it would rather have a gif, bitmap, jpg, flash, or even a plugin, rather than any other MIME type, including html. Rails gets this header, and sees that is no explicit format on the URL, so it goes down the list:
- Does this action respond to the
image/gifformat? nope. - How about the
image/x-bitmapformat? no. - Maybe the
image/jpegformat? Oh look at that yes it does!
The item that would have matched the html renderer */* is too far down the list, and we don’t get to it before a suitable responder is found.
Since IE7 sends a different HTTP_ACCEPT of */* on a refresh, it is declaring it has no preferred format at all. Rails then renders the default: html.
The Solution
After trying many ways to capture the damn header, override it somehow, shoving some Mime::Type object into request.format, and other failed solutions, here is what I finally ended up with:
1 2 3 4 5 6 7 |
#application.rb before_filter :set_default_format def set_default_format params[:format] ||= 'html' end |
This basically tells rails to ignore the HTTP_ACCEPT header. If a format is not explicitly declared on the URL, then force it to be HTML. It’s as if every html page on the site has a .html appended to the URL.
And finally salvation from that circle of hell.
Conclusions
What makes a good HTTP_ACCEPT header? I think Firefox has it right. Being a web browser, its main job is to display HTML, with embedded media. So it should want HTML more than anything since the images are just there in support of the html (mostly anyway).
IE7 makes 2 mistakes:
- It sends different headers depending on how the page was loaded
- It states that is prefers images over HTML
HTTP headers are a powerful tool. They can make interfacing with web applications and API’s much cleaner. But as we depend on them increasingly, we need have a level of trust with the clients that people use to connect. IE7 said it wanted one thing, and I forced to create a hack in order to tell IE7 “No, you don’t really want that. You really want something else.” And that makes me sad.
I thought clients who don’t know what they want are pain, but now browsers have the same problem. IE7 is a huge improvement over IE6, however it obviously still has its issues for web developers. The simple fact is that both versions of this browser have cost me way too much time, and that means it cost me money. The number of hours I have spent troubleshooting issues like this, as well as CSS and javascript issues, is way more than I want to admit to.
And that makes cranky. So tell your parents, “Save a web developer. Switch to Firefox.” Then maybe the world will be a better place.
September 30th, 2008 at 11:11 AM
Thank you. Works well.
November 19th, 2008 at 06:48 PM
Hi I tend to push this kind of logic higher up the chain to Apache which is the first-responder to all HTTP requests in my applications.
Here’s an alternate approach: If your app does not intend to respect the mime-type preferences of a client, you could simply alter them on the way in. In other words using mod_headers, strip HTTP_ACCEPT (or replace its value with “/“), et voila. Your Rails app will get the consistent requests it expected.
Remember the cardinal rule of web development: don’t trust the client. That’s usually applied to security, but it’s a good rule in general. If your app logic is relying on assumptions about consistency across client configuration, well, you’re kind of asking for trouble. REST is good, but in a non-academic context brute force methods like this can be a satisfying and reliable hammer.
Cheers Chris
November 25th, 2008 at 02:34 PM
I had a similar problem with IE once and it turns out the order of the responses in your controller is significant. Just swapping the order did the trick for me.
February 2nd, 2009 at 04:26 PM
I’m glad someone else went through the trouble of tracking down this bug so that lazy people like me can just google it :)
Nice fix. Your story was remarkably similar to the experience I had. Client was testing the site and was prompted to download an xls file rather than getting the HTML and was like WTF? I hadn’t tested that particular action in IE7, so when I started up IE7 and got the same thing. I was also like WTF. Microsoft strikes again! Anyways, thanks for the post.
February 13th, 2009 at 07:10 AM
thank you very much.
“Save a web developer. Switch to Firefox.”
should become an overall email signature.
April 21st, 2009 at 11:46 PM
I agree with Chris. I think the app/framework code should assume the client is spec compliant. There should be some sort of translation layer that rewrites incorrect headers to something spec compliant. It centralizes the rules, and allows them to be reused between different projects. It also makes it easy to remove them as a specific browser becomes less common. Doing it in Apache is good, but I think this would be a perfect fit for a Rack handler.
August 5th, 2009 at 08:41 AM
Thanks a lot Alex. I’ve searched for such a long time and couldn’t find a reason for this behavior. Always when I have tested my little rails application with firefox 3, there was this strange rendering. So I decided to use the firefox 2 instead, but in the last days i had to upgrade my firefox and found your excellent description. So everthing works fine.