Caught With My Bytes Unzipped

February 12th, 2007

I’m working on an application that has some simple networking needs, among them fetching small bits of data off of a variety of user-configured web sites. Since the needs are so straight-forward I thought using NSURL and its “resourceDataUsingCache:” method would be the way to go. It so happens that this application already has a separate thread running when the data is needed, so the synchronous call is fine:


NSData* myWebData = [myURL resourceDataUsingCache:NO];

This works amazingly well and reliably returns the contents of the specified URL, exactly as you’d expect. Unless the server decided to zip encode the data! In this case, you’ll get back data that for all intents and purposes looks completely wacked.

The problem stems from the fact that NSURL’s data loading mechanism passes the following header to the server when requesting the URL:

Accept-Encoding: gzip, deflate

But when the server complies and returns the data, NSURL shrugs and passes it straight back to the caller, without doing us the favor of unzipping it.

Becky Willrich acknowledged the bug in NSURL a few years ago, and suggested using a lower level API to work around the problem. She also identified an insidious aspect to the bug, which is that a server won’t necessarily give you zipped data just because you offered to handle it. In fact, a server that is in the habit of giving zipped data may choose not to if there doesn’t seem to be a benefit to doing so. In particular, if the cargo is so small that it would be more trouble to zip it than to just send it.

So why am I writing about this? Mainly to publish a simple workaround to the problem, using a slightly more complicated set of classes and parameter values, but ultimately being almost as simple as the original:


NSError* theError;
NSURLResponse* theResponse;
NSURLRequest* urlRequest =
	[NSURLRequest requestWithURL:myURL
		cachePolicy:NSURLRequestReloadIgnoringCacheData
		timeoutInterval:60.0];
NSData* myWebData =
	[NSURLConnection sendSynchronousRequest:urlRequest
		returningResponse:&theResponse
		error:&theError];

Monitoring the situation with tcpdump from the command line, I see that the same zipped content is being requested and received, but evidently NSURLConnection is kind enough to perform the extra step of unzipping it for me. Whoo!

Note that the methods used require 10.2 with Safari 1.0 installed, or 10.2.7 or later.