Four Tips for Setting up HTTP File Downloads

calendarMarch 24, 2010 in Firefox , HTTP , HTTPS , HttpWatch , Internet Explorer , Javascript , Optimization

Web sites don’t just contain pages; sometimes you need to provide files that users can download. Putting a file on your web server and linking to it from an HTML page is just the first step. You also need to be aware of the HTTP response headers that affect file downloads.

These four tips cover some of the issues you may run into:

Tip #1: Forcing a Download and Controlling the File Name

Providing a download link in the HTML is easy:

...
<a href="http://download.httpwatch.com/httpwatch.exe">Download</a>
...

It works well for binary files like setup programs and ZIP archives that the browser doesn’t know how to display. A dialog is displayed allowing the user to save the file locally:

IE File Save Dialog

The trouble is that the browser behaves differently if the file is something that it can display itself. For example, if you link to a plain text file the browser just opens it and doesn’t prompt to save the download:

Plain Text in IE

You can force the use of the file download dialog by adding the following response header:

Content-Disposition: attachment; filename=<file name.ext>

The header also allows you to control the default file name. This can be handy if you’re generating the content in something like getfile.aspx but you want to supply a more meaningful file name to the user.

For static content you can manually configure the additional header in your web server. For example, here’s the setting in IIS:

content_disposition_header

For dynamically generated content you would need to add this header in the page’s server side code.

After adding the header, the browser will always prompt the user to download the file:

plain_text_download

Tip #2: Use Effective HTTP Caching

Like any other content, it’s worth setting up HTTP caching to maximize the speed of download and minimize your bandwidth costs. Usually content needs to expire immediately or be cached forever.

Our example download of the HTTP spec (RFC2616) could be cached forever because it is not expected to change. You can see here in HttpWatch we have set up a far futures Expires value and set Cache-Control to public :

effective_caching

This allows future downloads of the file to be delivered from the local browser cache or an intermediate proxy. If the file is subject to frequent changes, you may want to expire it immediately so that a fresh copy is always downloaded. You can do this by setting Expires to -1 or any date in the past.

Tip #3: Don’t break HTTPS downloads in IE

It’s tempting to use the no-store and no-cache directives with the Cache-Control response header to prevent any caching of a file that is often updated:

Cache-Control: no-store, no-cache

This works in Firefox, but watch out for Internet Explorer. It interprets these flags as meaning that the content should never be saved to the disk when HTTPS is being used and causes the file download dialog to hang at 0% for several minutes:

https_ie_hang

It eventually displays an error message:

https_ie_error

There’s more information about this problem and other possible causes in a post on Eric Lawrence’s IEInternals blog.

Tip #4: Don’t Forget to Setup Analytics

You’ll probably want to track file downloads along with other metrics from your web site. Javascript based solutions such as Google Analytics are very popular, but will not show file downloads by default. This is because downloading a file does not cause any Javascript to be executed.

With Google Analytics you need to add an onlick handler to enable download tracking:

...
<a onclick="pageTracker._trackPageview('/httpwatch.exe');" href="...">Download</a>
...

You can see the Google Analytics call being made just before the file download starts:

ga_download

Using Protocol Relative URLs to Switch between HTTP and HTTPS

calendarFebruary 10, 2010 in HTTPS , HttpWatch , Internet Explorer

Attempting to use HTTP resources on an secure web page is a guaranteed way to annoy IE users, because it generates a confusing security warning for anyone running with the default IE settings. Our previous post on fixing this message in IE 8 is responsible for more than 50% of the traffic and comments on this blog. Hopefully, Microsoft will take note and change this in a future version of IE.

In the meantime, it’s important to avoid this warning by ensuring that every image, CSS and Javscript file on a secure page is accessed using HTTPS. For content on the same domain it’s quite straightforward – you just need to use relative URLs. A relative URL contains the ‘offset’ URL that needs to be applied to the page’s absolute URL in order to find a resource.

For example, here’s a screen shot from HttpWatch accessing our home page over HTTPS:

HttpWatch Home Page Access with HTTPS

Searching for one of the images in HttpWatch shows that it was specified with a relative URL and the switch to HTTPS happened automatically:

Relative URL

A problem arises though, if you attempt to access a resource from a different domain because you can’t use the simple path-relative URL to access the resource. This often happens when you attempt to use a third party service such as Google Analytics or a third party Ajax library CDN.

Google Analytics solves the problem with its external javascript file by recommending the use of this code to dynamically switch protocols:

var gaJsHost = (("https:" == document.location.protocol) ?
    "https://ssl." : "http://www.");
document.write(unescape("%3Cscript src='" + gaJsHost +
    "google-analytics.com/ga.js' type='text/javascript'%3E%3C/script%3E"));

Another solution is to hard code the external resource with an HTTPS based URL regardless of how the containing page is accessed. This avoids any security warnings in IE, but does mean that the HTTPS resource download will consume more CPU resources in cases where the HTTP download would have been sufficient.

The solution we prefer to use is to specify a Protocol Relative URL. It’s just like a regular URL except that you leave out the protocol prefix. For example, when Microsoft recently announced SSL support for their Ajax CDN the recommended script tag for secure pages was:

<script src=”https://ajax.microsoft.com/ajax/jquery/jquery-1.3.2.min.js” type=”text/javascript”></script>

But if you change this to a protocol relative URL:

<script src=”//ajax.microsoft.com/ajax/jquery/jquery-1.3.2.min.jstype=”text/javascript”></script>

You get the automatic use of HTTPS on secure pages and avoid the overhead of HTTPS on non-secure pages.

We put together a simple jQuery demo page using this technique. In HttpWatch, you can see that the resource is automatically downloaded with HTTP for non-secure access:

and HTTPS with secure access:

One downside of this approach is that there will be separate cached copies for both the HTTP and HTTPS based URLs. If a vistor to your site first uses a non-secure HTTP based URL and then switches to HTTPS they would have to download fresh copies of resources that had already been accessed over HTTP.

UPDATE: Steve Souders pointed out another downside to using protocol relative URLs; they cause a double download of CSS files in IE 7 & 8.

You can check SSL/TLS configuration our new SSL test tool SSLRobot . It will also look for potential issues with the certificates, ciphers and protocols used by your site. Try it now for free!

Even more problems with the IE 8 mixed content warning

calendarSeptember 17, 2009 in HTTPS , HttpWatch , Internet Explorer

We have previously written about the pointless and confusing ‘Do you want to view only the webpage content that was delivered securely‘ message in IE 8. It is displayed by default when a secure web page attempts to use non-secure content such as images, javascript or CSS. That post has been so popular that it attracts 40% of the traffic to this blog.

The IE 8 mixed content dialog is pointless because 99.9% of web users just want it to go away and let them get on with what they were doing. For the 0.1% of web surfers who do care, it is confusing because of the way it is worded:

IE 8 Security Warning

The blog post described how you can disable this warning and from the comments it looks like many users are now doing this.

Even if you do this, IE still silently performs the check and hides the re-assuring padlock icon that you normally see on HTTPS pages:

No Padlock icon when mixed content present

This could be disturbing for anyone on a checkout page who is about to enter their credit card details. So if you’re web site developer you really need to avoid using mixed content – even for users who have disabled this warning. Firefox has the mixed content warning turned off by default. Let’s hope Microsoft do the same turn in the next version of IE.

You can normally fix the mixed content warning by ensuring that all the content on a secure page is served up with HTTPS. In HttpWatch you can quickly check a page by using a forced refresh to look for URLs starting with ‘http;’ :

Mixed Content in HttpWatch

However, a customer contacted us recently because they were still getting the mixed content warning even though they had no HTTP URLs on their secure page. After some investigation it was found that this commonly used javascript technique was causing the problem:

// Causes mixed content message in IE on a secure page
document.write("<script id="__ie_onload" src="javascript:void(0)"></script>");
document.getElementById("__ie_onload").onreadystatechange = function()
{
     if (this.readyState == "complete") domReady();
};

It’s a trick used to emulate a DOMContentLoaded event in IE.  A security warning occurs because of the use the “javascript:” protocol even though no download occurs.

The fix is to use //: in the src attribute in the same way as popular javascript libraries such as jQuery and prototype. This does cause a harmless ERROR_INVALID_URL entry in HttpWatch, but it avoids the mixed content message:

// Does not cause a mixed content message in IE on a secure page
document.write("<script id="__ie_onload" src="//:"></script>");
document.getElementById("__ie_onload").onreadystatechange = function()
{
     if (this.readyState == "complete") domReady();
};

Ready to get started? TRY FOR FREE Buy Now