2

I wonder under what conditions an NSHTTPURLResponse object will not have key @"Content-Length" ? Is it usual/normal not to have that key?

I am trying something with the dropbox SDK and I've realized that

[[response allHeaderFields] objectForKey:@"Content-Length"]

returns nil and is causing downloadProgress to be infinite:

 NSInteger contentLength = [[[response allHeaderFields] objectForKey:@"Content-Length"] intValue];
    downloadProgress = (CGFloat)bytesDownloaded / (CGFloat)contentLength;

Is there any way I can make the response have that key?

BTW: This is the response I am getting

(gdb) po [response allHeaderFields]
{
    "Cache-Control" = "max-age=0";
    Connection = "keep-alive";
    "Content-Encoding" = gzip;
    "Content-Type" = "text/plain; charset=UTF-16LE";
    Date = "Wed, 10 Aug 2011 06:21:43 GMT";
    Etag = 228n;
    Pragma = public;
    Server = dbws;
    "Transfer-Encoding" = Identity;
}

EDIT (Work-arounded):

As @Mitchell said. Servers not always return such a key. (Weird DropBox servers, for png yes, for txt files sometimes no :/ ) So, in order to calculate the downloadProgress of a file I've modified (work-arounded) the source:

//In DBRequest.m 
- (void)connection:(NSURLConnection*)connection didReceiveData:(NSData*)data {
    ...
    bytesDownloaded += [data length];
    //start of modification
    //Server might not contain @"Content-Length" key, 
    //in that case use the downloadedBytes. Is better 
    //than having an infinite value because it could
    //be handled by DBRestClient's delegate. (If not 
    //it will have the same effect as infinite)
    if ([response expectedContentLength] == NSURLResponseUnknownLength ) {
        downloadProgress = (CGFloat)bytesDownloaded;
    }else{
        NSInteger contentLength = [[[response allHeaderFields] objectForKey:@"Content-Length"] intValue];
        downloadProgress = (CGFloat)bytesDownloaded / (CGFloat)contentLength; 
    }
    //end of modification
    if (downloadProgressSelector) {
        [target performSelector:downloadProgressSelector withObject:self];
    }
}

And since I have the size of a file from the metadata I can do:

- (void)restClient:(DBRestClient *)client loadProgress:(CGFloat)progress forFile:(NSString *)destPath {
    if (progress > 1) {//Work-around: This means the progress is not a 
        progress = progress/((CGFloat)(metadataOfTheFile.totalBytes));
    }
    ... update the progress bar here
}
nacho4d
  • 43,720
  • 45
  • 157
  • 240
  • 3
    From documentation we can see there is a method expectedContentLength which may return NSURLResponseUnknownLength if length cannot be determined. Did you try that? – Praveen S Aug 10 '11 at 06:53
  • IT also looks like you're assigning the NSInteger contentLength from the pointer value of NSNumber, since you can't store primitive types in NSDictionary. Change this to NSNumber*. – Mitchell Currie Aug 10 '11 at 07:05

2 Answers2

3

If you will recall that sometimes downloads in safari or your browser of choice don't have a progress bar either, the length isn't determined. In this particular case a loading spinner is the best thing to show. It entirely depends on the server returning the content-length, which is optional.

I honestly don't know why it isn't returning that info, perhaps it's due to the content-range or some other variable that it decides not to reveal it.

You may have to look elsewhere to obtain that property, but the best recommendation is to be safe, rather than sorry and avoid all NaN errors.

Mitchell Currie
  • 2,769
  • 3
  • 20
  • 26
  • OR less ideally force the content-length, as the dropBox directory index should show file size. – Mitchell Currie Aug 10 '11 at 06:43
  • Indeed the server might not return that information. I've realized dropbox servers return "Content-Length" for images but not for text file (*.txt) not at least with the files Im trying. I've implemented a work-around to get the downloaded percentage :) – nacho4d Aug 11 '11 at 02:54
1

It's allowed for HTTP to not have a Content-Length field, it's optional. So you need to handle it:

NSNumber *lengthNumber = [[response allHeaderFields] objectForKey:@"Content-Length"];
NSUInteger contentLength = [lengthNumber unsignedIntegerValue];
if (contentLength == 0) {
    // Calculate progress.
} else {
    // Can't calculate progress.
}
DarkDust
  • 90,870
  • 19
  • 190
  • 224
  • Are you sure that's correct? Contentlength should be non-zero to calculate progress. – Mitchell Currie Aug 10 '11 at 07:02
  • @Mitchell Currie: See [RFC 2616](http://www.ietf.org/rfc/rfc2616.txt), sections 4.4 for the gory details, or section 14.13 just for the `Content-Length` field. The vital part is: *"In HTTP, it SHOULD be sent whenever the message's length can be determined prior to being transferred"*: sometimes the application cannot know how much data it will send, and that's when the Content-Length field is left out. This is used for example for streaming media. – DarkDust Aug 10 '11 at 07:16