NSURLResponse object HTTP header values

I'm using NSURLSession et al to download files in my iPhone app. It works correctly except for large (>20MB) files. Then, without exception, the

NSURLSession:downloadTask:didWriteData:totalBytesWritten:totalBytesExpectedToWrite:

callback returns -1 for totalBytesExpectedToWrite.

Embedded in the NSURLSessionDownloadTask.response object there is an HTTP header dictionary with this key/value pair:

"Content-Length" =     (
    22268238
);

This is the correct value that I need so I can monitor the download progress, not the "expected value".

How can I obtain this header value from the NSURLResponse object? If I access response.expectedContentLength it always returns -1.

One would expect a -allHeaderFields method, or perhaps

[response headerValueForKey:@"Content-Length"] or some such.

Is there a way to obtain the NSURLResponse object's HTTP header values in a NSDictionary?

Accepted Reply

NSURLSession does not have any logic that changes based on the size of the incoming file. I suspect that this change in behaviour is driven by your server. That is, at a certain threshold the server switches to the chunked transfer encoding, at which point NSURLSession has no idea how much data is expected. That’s why expectedContentLength is returning NSURLResponseUnknownLength.

If you happen to know that the server sets Content-Length to a valid value — which it’s not required, or even encouraged to do when using the chunked encoding — feel free to get that header and rely on it.

How can I obtain this header value from the NSURLResponse object?

Is the -valueForHTTPHeaderField: method not working for you?

Share and Enjoy

Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"

Replies

NSURLSession does not have any logic that changes based on the size of the incoming file. I suspect that this change in behaviour is driven by your server. That is, at a certain threshold the server switches to the chunked transfer encoding, at which point NSURLSession has no idea how much data is expected. That’s why expectedContentLength is returning NSURLResponseUnknownLength.

If you happen to know that the server sets Content-Length to a valid value — which it’s not required, or even encouraged to do when using the chunked encoding — feel free to get that header and rely on it.

How can I obtain this header value from the NSURLResponse object?

Is the -valueForHTTPHeaderField: method not working for you?

Share and Enjoy

Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"

NSURLSession does not have any logic that changes based on the size of the incoming file. 

Does the cache?

Is the -valueForHTTPHeaderField: method not working for you?

I tried that but Xcode didn't allow it, as NSURLSessionDownloadTask.response is a NSURLResponse object not a NSHTTPURLResponse object (which responds to -valueForHTTPHeaderField:) .

However, I did cast the types and got it work work:

 NSDictionary *responseHeaders = ((NSHTTPURLResponse *)downloadTask.response).allHeaderFields;

So that does work, thank you!

But the file being downloaded is a .gz file. Is there a way in iOS to uncompress it programmatically?

as NSURLSessionDownloadTask.response is a NSURLResponse object not a NSHTTPURLResponse object

Right. For an http[s] request the response will be an NSHTTPURLResponse and standard practice for accessing HTTP-specific properties, like this header, is to do the cast. If you’re feeling paranoid, feel free to add this:

NSHTTPURLResponse * = task.response;
if ( ! [response isKindOfClass:[NSHTTPURLResponse class]] ) {
    … handle this failure …
}

Is there a way in iOS to uncompress it programmatically?

If the server uses a gzip content encoding (see here, and note that this different from the transfer encoding) NSURLSession will undo that transparently. If not, you’ll have to undo it yourself. To do this, check out the zlib man page.

Share and Enjoy

Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"