Question

I'm downloading some files with my own download manager. It worked well for almost half year (and even after publishing it to the App Store)
But yesterday I've got something interesting:

Error Domain=NSURLErrorDomain Code=-1015 "cannot decode raw data"
UserInfo=0x4c12e0
{
    NSErrorFailingURLStringKey=http://***/file.json.gz,
    NSErrorFailingURLKey=http://***/file.json.gz,
    NSLocalizedDescription=cannot decode raw data
    NSUnderlyingError=0x4dcec0 "cannot decode raw data"
}

A little background: I have a web server which gives me JSONs and gzipped JSONs.

So the problem is happening when I'm trying to download a gzipped file and ONLY on iPod Touch 4G (5.1.1)!

What is happening? How can I handle it? Is it a web server problem?

Was it helpful?

Solution

The problem was next.
When iPhone receives gzipped data it automatically unpack it. And Content-Length in this case is equals to -1. So if you want to continue downloading of a gzipped data it's not a good idea to make the Range header: you don't know the size of a gzipped data.
In our case we'd making start of the Range equal to already downloaded data and in some cases it exceeded the size of a gzipped data (and I'm don't even saying it was wrong, the file was corrupted!). So web server returned 416 Requested Range not satisfiable and that's why NSURLConnection delegate's didFailWithError method was called with an NSURLErrorCannotDecodeRawData error.

Detail explanation and our solution

In the download manager we had code

NSMutableURLRequest *req = [NSMutableURLRequest requestWithURL:url];
[req setRange:NSMakeRange(progress, NSNotFound)];

Where progress is the amount of downloaded data. It stored in the DB to allow pause and continue downloading of a single file (e. g. large file between application relaunches). When we want to continue, we set the Range header with the [progress; ∞) interval (to receive the data from the offset we've already downloaded).

Servers (Apache, nginx, whatever) apply gzip encoding to the stream on-the-fly. That's good for reducing output file size, but in result you don't know the size of the whole gzipped file. So it basically means that you can't pause and continue download of gzipped stream. And in addition the downloaded gzipped chunk is being unzipped on receive (NSURLConnection delegate method connection:didReceiveData:), so you won't know how much gzipped data passed. Thus you won't create the correct offset and the server will return data from offset you didn't meant and your resulting file, at first, will be corrupted and, at second, once you exceed the content length and receive 416.

So no one browser or whatever will allow you to continue downloading of the dynamic (generated on request) or gzipped content. If you want to pause and continue large compressed files (as 20 Mb JSONs in our case), either make them static and archived or continue gziping them and just hope that user will wait until the file downloaded.

So we've chosen the 2nd path and now don't set range if Content-Lenght is unknown (-1).

NSMutableURLRequest *req = [NSMutableURLRequest requestWithURL:url];

if (urlResponse.expectedContentLength != NSURLResponseUnknownLength) {
    [req setRange:NSMakeRange(progress, NSNotFound)];
}
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top