Ben Copsey

Add gzip response handling, contributed by Enormego http://developers.enormego.c…

…om/view/asihttprequest_gzip
File downloads are not playing nice with gzip yet
Added compressed data tests
... ... @@ -32,7 +32,7 @@
[request setFile:path forKey:@"file"];
[request start];
BOOL success = ([[request dataString] isEqualToString:[NSString stringWithFormat:@"post_var: %@\r\npost_var2: %@\r\npost_var3: %@\r\nfile_name: %@\r\nfile_size: %hu",@"foo",d,v,@"bigfile",size]]);
BOOL success = ([[request responseString] isEqualToString:[NSString stringWithFormat:@"post_var: %@\r\npost_var2: %@\r\npost_var3: %@\r\nfile_name: %@\r\nfile_size: %hu",@"foo",d,v,@"bigfile",size]]);
STAssertTrue(success,@"Failed to upload the correct data (using local file)");
//Try the same with the raw data
... ... @@ -43,7 +43,7 @@
[request setData:data forKey:@"file"];
[request start];
success = ([[request dataString] isEqualToString:[NSString stringWithFormat:@"post_var: %@\r\npost_var2: %@\r\npost_var3: %@\r\nfile_name: %@\r\nfile_size: %hu",@"foo",d,v,@"file",size]]);
success = ([[request responseString] isEqualToString:[NSString stringWithFormat:@"post_var: %@\r\npost_var2: %@\r\npost_var3: %@\r\nfile_name: %@\r\nfile_size: %hu",@"foo",d,v,@"file",size]]);
STAssertTrue(success,@"Failed to upload the correct data (using NSData)");
}
... ...
... ... @@ -15,6 +15,7 @@
#if TARGET_OS_IPHONE
#import <CFNetwork/CFNetwork.h>
#endif
#import <zlib.h>
typedef enum _ASINetworkErrorType {
ASIConnectionFailureErrorType = 1,
... ... @@ -62,6 +63,9 @@ typedef enum _ASINetworkErrorType {
// If useSessionPersistance is true, network requests will save credentials and reuse for the duration of the session (until clearSession is called)
BOOL useSessionPersistance;
// If allowCompressedResponse is true, requests will inform the server they can accept compressed data, and will automatically decompress gzipped responses. Default is true.
BOOL allowCompressedResponse;
// When downloadDestinationPath is set, the result of this request will be downloaded to the file at this location
// If downloadDestinationPath is not set, download data will be stored in memory
NSString *downloadDestinationPath;
... ... @@ -95,8 +99,8 @@ typedef enum _ASINetworkErrorType {
// Whether we've seen the headers of the response yet
BOOL haveExaminedHeaders;
// Data we receive will be stored here
NSMutableData *receivedData;
// Data we receive will be stored here. Data may be compressed unless allowCompressedResponse is false - you should use [request responseData] instead in most cases
NSMutableData *rawResponseData;
// Used for sending and receiving data
CFHTTPMessageRef request;
... ... @@ -154,7 +158,7 @@ typedef enum _ASINetworkErrorType {
ASIHTTPRequest *mainRequest;
// When NO, this request will only update the progress indicator when it completes
// When YES, this request will update the progress indicator according to how much data it has recieved so far
// When YES, this request will update the progress indicator according to how much data it has received so far
// The default for requests is YES
// Also see the comments in ASINetworkQueue.h
BOOL showAccurateProgress;
... ... @@ -185,8 +189,11 @@ typedef enum _ASINetworkErrorType {
#pragma mark get information about this request
// Returns the contents of the result as an NSString (not appropriate for binary data - used receivedData instead)
- (NSString *)dataString;
// Returns the contents of the result as an NSString (not appropriate for binary data - used responseData instead)
- (NSString *)responseString;
// Response data, automatically uncompressed where appropriate
- (NSData *)responseData;
#pragma mark request logic
... ... @@ -272,6 +279,11 @@ typedef enum _ASINetworkErrorType {
// Dump all session data (authentication and cookies)
+ (void)clearSession;
#pragma mark gzip compression
// Uncompress gzipped data with zlib
+ (NSData *)uncompressZippedData:(NSData*)compressedData;
@property (retain) NSString *username;
@property (retain) NSString *password;
... ... @@ -296,7 +308,7 @@ typedef enum _ASINetworkErrorType {
@property (assign) BOOL useCookiePersistance;
@property (retain) NSDictionary *requestCredentials;
@property (assign) int responseStatusCode;
@property (retain) NSMutableData *receivedData;
@property (retain) NSMutableData *rawResponseData;
@property (retain) NSDate *lastActivityTime;
@property (assign) NSTimeInterval timeOutSeconds;
@property (retain) NSString *requestMethod;
... ... @@ -310,4 +322,5 @@ typedef enum _ASINetworkErrorType {
@property (assign) unsigned long long uploadBufferSize;
@property (assign) NSStringEncoding defaultResponseEncoding;
@property (assign) NSStringEncoding responseEncoding;
@property (assign) BOOL allowCompressedResponse;
@end
... ...
... ... @@ -72,6 +72,7 @@ static NSError *ASIUnableToCreateRequestError;
requestAuthentication = NULL;
haveBuiltPostBody = NO;
request = NULL;
[self setAllowCompressedResponse:YES];
[self setDefaultResponseEncoding:NSISOLatin1StringEncoding];
[self setUploadBufferSize:0];
[self setResponseHeaders:nil];
... ... @@ -113,7 +114,7 @@ static NSError *ASIUnableToCreateRequestError;
[authenticationLock release];
[lastActivityTime release];
[responseCookies release];
[receivedData release];
[rawResponseData release];
[responseHeaders release];
[requestMethod release];
[cancelledLock release];
... ... @@ -166,17 +167,28 @@ static NSError *ASIUnableToCreateRequestError;
}
// Call this method to get the recieved data as an NSString. Don't use for Binary data!
- (NSString *)dataString
// Call this method to get the received data as an NSString. Don't use for Binary data!
- (NSString *)responseString
{
if (!receivedData) {
NSData *data = [self responseData];
if (!data) {
return nil;
}
return [[[NSString alloc] initWithBytes:[receivedData bytes] length:[receivedData length] encoding:[self responseEncoding]] autorelease];
return [[[NSString alloc] initWithBytes:[data bytes] length:[data length] encoding:[self responseEncoding]] autorelease];
}
- (NSData *)responseData
{
NSString *encoding = [[self responseHeaders] objectForKey:@"Content-Encoding"];
if(encoding && [encoding rangeOfString:@"gzip"].location != NSNotFound) {
return [ASIHTTPRequest uncompressZippedData:[self rawResponseData]];
} else {
return [self rawResponseData];
}
}
#pragma mark request logic
// Create the request
... ... @@ -243,6 +255,12 @@ static NSError *ASIUnableToCreateRequestError;
[self buildPostBody];
}
// Accept a compressed response
if ([self allowCompressedResponse]) {
[self addRequestHeader:@"Accept-Encoding" value:@"gzip"];
}
// Add custom headers
NSDictionary *headers;
... ... @@ -296,7 +314,7 @@ static NSError *ASIUnableToCreateRequestError;
contentLength = 0;
}
[self setResponseHeaders:nil];
[self setReceivedData:[[[NSMutableData alloc] init] autorelease]];
[self setRawResponseData:[[[NSMutableData alloc] init] autorelease]];
// Create the stream for the request.
readStream = CFReadStreamCreateForStreamedHTTPRequest(kCFAllocatorDefault, request,readStream);
... ... @@ -395,8 +413,8 @@ static NSError *ASIUnableToCreateRequestError;
readStream = NULL;
}
if (receivedData) {
[self setReceivedData:nil];
if (rawResponseData) {
[self setRawResponseData:nil];
// If we were downloading to a file, let's remove it
} else if (downloadDestinationPath) {
... ... @@ -1025,7 +1043,7 @@ static NSError *ASIUnableToCreateRequestError;
//Otherwise, let's add the data to our in-memory store
} else {
[receivedData appendBytes:buffer length:bytesRead];
[rawResponseData appendBytes:buffer length:bytesRead];
}
}
... ... @@ -1164,6 +1182,59 @@ static NSError *ASIUnableToCreateRequestError;
}
#pragma mark gzip data handling
//
// Contributed by Shaun Harrison of Enormego, see: http://developers.enormego.com/view/asihttprequest_gzip
// Based on this: http://deusty.blogspot.com/2007/07/gzip-compressiondecompression.html
//
+ (NSData *)uncompressZippedData:(NSData*)compressedData
{
if ([compressedData length] == 0) return compressedData;
unsigned full_length = [compressedData length];
unsigned half_length = [compressedData length] / 2;
NSMutableData *decompressed = [NSMutableData dataWithLength: full_length + half_length];
BOOL done = NO;
int status;
z_stream strm;
strm.next_in = (Bytef *)[compressedData bytes];
strm.avail_in = [compressedData length];
strm.total_out = 0;
strm.zalloc = Z_NULL;
strm.zfree = Z_NULL;
if (inflateInit2(&strm, (15+32)) != Z_OK) return nil;
while (!done) {
// Make sure we have enough room and reset the lengths.
if (strm.total_out >= [decompressed length]) {
[decompressed increaseLengthBy: half_length];
}
strm.next_out = [decompressed mutableBytes] + strm.total_out;
strm.avail_out = [decompressed length] - strm.total_out;
// Inflate another chunk.
status = inflate (&strm, Z_SYNC_FLUSH);
if (status == Z_STREAM_END) {
done = YES;
} else if (status != Z_OK) {
break;
}
}
if (inflateEnd (&strm) != Z_OK) return nil;
// Set real length.
if (done) {
[decompressed setLength: strm.total_out];
return [NSData dataWithData: decompressed];
} else {
return nil;
}
}
@synthesize username;
@synthesize password;
@synthesize domain;
... ... @@ -1186,7 +1257,7 @@ static NSError *ASIUnableToCreateRequestError;
@synthesize requestCookies;
@synthesize requestCredentials;
@synthesize responseStatusCode;
@synthesize receivedData;
@synthesize rawResponseData;
@synthesize lastActivityTime;
@synthesize timeOutSeconds;
@synthesize requestMethod;
... ... @@ -1201,4 +1272,5 @@ static NSError *ASIUnableToCreateRequestError;
@synthesize uploadBufferSize;
@synthesize defaultResponseEncoding;
@synthesize responseEncoding;
@synthesize allowCompressedResponse;
@end
... ...
... ... @@ -25,5 +25,6 @@
- (void)testBasicAuthentication;
- (void)testDigestAuthentication;
- (void)testCharacterEncoding;
- (void)testCompressedResponse;
@end
... ...
... ... @@ -20,7 +20,7 @@
NSURL *url = [[[NSURL alloc] initWithString:@"http://allseeing-i.com"] autorelease];
ASIHTTPRequest *request = [[[ASIHTTPRequest alloc] initWithURL:url] autorelease];
[request start];
NSString *html = [request dataString];
NSString *html = [request responseString];
STAssertNotNil(html,@"Basic synchronous request failed");
// Check we're getting the correct response headers
... ... @@ -97,7 +97,7 @@
ASIHTTPRequest *request = [[[ASIHTTPRequest alloc] initWithURL:url] autorelease];
[request setRequestMethod:method];
[request start];
BOOL success = [[request dataString] isEqualToString:method];
BOOL success = [[request responseString] isEqualToString:method];
STAssertTrue(success,@"Failed to set the request method correctly");
}
}
... ... @@ -110,7 +110,7 @@
[request setPostBody:[NSMutableData dataWithLength:1024*32]];
[request start];
BOOL success = ([[request dataString] isEqualToString:[NSString stringWithFormat:@"%hu",(1024*32)]]);
BOOL success = ([[request responseString] isEqualToString:[NSString stringWithFormat:@"%hu",(1024*32)]]);
STAssertTrue(success,@"Sent wrong content length");
}
... ... @@ -133,6 +133,7 @@
[request1 setDownloadDestinationPath:path];
[request1 start];
NSString *s = [NSString stringWithContentsOfURL:[NSURL fileURLWithPath:path]];
BOOL success = [[NSString stringWithContentsOfURL:[NSURL fileURLWithPath:path]] isEqualToString:@"This is the expected content for the first string"];
STAssertTrue(success,@"Failed to download data to a file");
}
... ... @@ -180,7 +181,7 @@
ASIHTTPRequest *request = [[[ASIHTTPRequest alloc] initWithURL:url] autorelease];
[request setUseCookiePersistance:YES];
[request start];
NSString *html = [request dataString];
NSString *html = [request responseString];
success = [html isEqualToString:@"I have set a cookie"];
STAssertTrue(success,@"Failed to set a cookie");
... ... @@ -216,7 +217,7 @@
[request setUseCookiePersistance:NO];
[request setRequestCookies:[NSMutableArray arrayWithObject:cookie]];
[request start];
html = [request dataString];
html = [request responseString];
success = [html isEqualToString:@"I have 'This is the value' as the value of 'ASIHTTPRequestTestCookie'"];
STAssertTrue(success,@"Cookie not presented to the server with cookie persistance OFF");
... ... @@ -225,7 +226,7 @@
request = [[[ASIHTTPRequest alloc] initWithURL:url] autorelease];
[request setUseCookiePersistance:YES];
[request start];
html = [request dataString];
html = [request responseString];
success = [html isEqualToString:@"I have 'This is the value' as the value of 'ASIHTTPRequestTestCookie'"];
STAssertTrue(success,@"Cookie not presented to the server with cookie persistance ON");
... ... @@ -233,7 +234,7 @@
url = [[[NSURL alloc] initWithString:@"http://allseeing-i.com/ASIHTTPRequest/tests/remove_cookie"] autorelease];
request = [[[ASIHTTPRequest alloc] initWithURL:url] autorelease];
[request start];
html = [request dataString];
html = [request responseString];
success = [html isEqualToString:@"I have removed a cookie"];
STAssertTrue(success,@"Failed to remove a cookie");
... ... @@ -241,7 +242,7 @@
url = [[[NSURL alloc] initWithString:@"http://allseeing-i.com/ASIHTTPRequest/tests/read_cookie"] autorelease];
request = [[[ASIHTTPRequest alloc] initWithURL:url] autorelease];
[request start];
html = [request dataString];
html = [request responseString];
success = [html isEqualToString:@"No cookie exists"];
STAssertTrue(success,@"Cookie presented to the server when it should have been removed");
... ... @@ -259,7 +260,7 @@
[request setUseCookiePersistance:NO];
[request setRequestCookies:[NSMutableArray arrayWithObject:cookie]];
[request start];
html = [request dataString];
html = [request responseString];
success = [html isEqualToString:@"I have 'Test Value' as the value of 'ASIHTTPRequestTestCookie'"];
STAssertTrue(success,@"Custom cookie not presented to the server with cookie persistance OFF");
... ... @@ -275,7 +276,7 @@
request = [[[ASIHTTPRequest alloc] initWithURL:url] autorelease];
[request setUseCookiePersistance:YES];
[request start];
html = [request dataString];
html = [request responseString];
success = [html isEqualToString:@"No cookie exists"];
STAssertTrue(success,@"Cookie presented to the server when it should have been removed");
}
... ... @@ -398,7 +399,29 @@
[request start];
success = [[request error] code] == ASIAuthenticationErrorType;
STAssertTrue(success,@"Failed to clear credentials");
}
- (void)testCompressedResponse
{
// allseeing-i.com does not gzip png images
NSURL *url = [[[NSURL alloc] initWithString:@"http://allseeing-i.com/i/logo.png"] autorelease];
ASIHTTPRequest *request = [[[ASIHTTPRequest alloc] initWithURL:url] autorelease];
[request start];
NSString *encoding = [[request responseHeaders] objectForKey:@"Content-Encoding"];
BOOL success = (!encoding || [encoding rangeOfString:@"gzip"].location != NSNotFound);
STAssertTrue(success,@"Got incorrect request headers from server");
success = ([request rawResponseData] == [request responseData]);
STAssertTrue(success,@"Attempted to uncompress data that was not compressed");
url = [[[NSURL alloc] initWithString:@"http://allseeing-i.com/ASIHTTPRequest/tests/first"] autorelease];
request = [[[ASIHTTPRequest alloc] initWithURL:url] autorelease];
[request start];
success = ([request rawResponseData] != [request responseData]);
STAssertTrue(success,@"Uncompressed data is the same as compressed data");
success = [[request responseString] isEqualToString:@"This is the expected content for the first string"];
STAssertTrue(success,@"Failed to decompress data correctly?");
}
... ...
... ... @@ -126,19 +126,19 @@
success = ([request1 error] == nil);
STAssertTrue(success,@"Request 1 failed");
success = [[request1 dataString] isEqualToString:@"This is the expected content for the first string"];
success = [[request1 responseString] isEqualToString:@"This is the expected content for the first string"];
STAssertTrue(success,@"Failed to download the correct data for request 1");
success = ([request2 error] == nil);
STAssertTrue(success,@"Request 2 failed");
success = [[request2 dataString] isEqualToString:@"This is the expected content for the second string"];
success = [[request2 responseString] isEqualToString:@"This is the expected content for the second string"];
STAssertTrue(success,@"Failed to download the correct data for request 2");
success = ([request3 error] == nil);
STAssertTrue(success,@"Request 3 failed");
success = [[request3 dataString] isEqualToString:@"This is the expected content for the third string"];
success = [[request3 responseString] isEqualToString:@"This is the expected content for the third string"];
STAssertTrue(success,@"Failed to download the correct data for request 3");
success = ([requestThatShouldFail error] != nil);
... ...
... ... @@ -34,8 +34,8 @@
[request addRequestHeader:@"User-Agent" value:@"ASIHTTPRequest"];
[request start];
if ([request dataString]) {
[htmlSource setString:[request dataString]];
if ([request responseString]) {
[htmlSource setString:[request responseString]];
}
}
... ... @@ -131,7 +131,7 @@
- (IBAction)topSecretFetchComplete:(ASIHTTPRequest *)request
{
if (![request error]) {
[topSecretInfo setStringValue:[request dataString]];
[topSecretInfo setStringValue:[request responseString]];
[topSecretInfo setFont:[NSFont boldSystemFontOfSize:13]];
}
}
... ...
This diff was suppressed by a .gitattributes entry.