Reputation: 3725
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?
Upvotes: 3
Views: 7280
Reputation: 3725
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.
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)];
}
Upvotes: 3