Showing
4 changed files
with
276 additions
and
22 deletions
| @@ -44,12 +44,15 @@ extern NSString* const NetworkRequestErrorDomain; | @@ -44,12 +44,15 @@ extern NSString* const NetworkRequestErrorDomain; | ||
| 44 | // A queue delegate that should *ALSO* be notified of delegate message (used by ASINetworkQueue) | 44 | // A queue delegate that should *ALSO* be notified of delegate message (used by ASINetworkQueue) |
| 45 | id queue; | 45 | id queue; |
| 46 | 46 | ||
| 47 | - // HTTP method to use (GET / POST / PUT / DELETE). Defaults to GET | 47 | + // HTTP method to use (GET / POST / PUT / DELETE / HEAD). Defaults to GET |
| 48 | NSString *requestMethod; | 48 | NSString *requestMethod; |
| 49 | 49 | ||
| 50 | // Request body - only used when the whole body is stored in memory (shouldStreamPostDataFromDisk is false) | 50 | // Request body - only used when the whole body is stored in memory (shouldStreamPostDataFromDisk is false) |
| 51 | NSMutableData *postBody; | 51 | NSMutableData *postBody; |
| 52 | 52 | ||
| 53 | + // gzipped request body used when shouldCompressRequestBody is YES | ||
| 54 | + NSData *compressedPostBody; | ||
| 55 | + | ||
| 53 | // When true, post body will be streamed from a file on disk, rather than loaded into memory at once (useful for large uploads) | 56 | // When true, post body will be streamed from a file on disk, rather than loaded into memory at once (useful for large uploads) |
| 54 | // Automatically set to true in ASIFormDataRequests when using setFile:forKey: | 57 | // Automatically set to true in ASIFormDataRequests when using setFile:forKey: |
| 55 | BOOL shouldStreamPostDataFromDisk; | 58 | BOOL shouldStreamPostDataFromDisk; |
| @@ -58,6 +61,9 @@ extern NSString* const NetworkRequestErrorDomain; | @@ -58,6 +61,9 @@ extern NSString* const NetworkRequestErrorDomain; | ||
| 58 | // You can set this yourself - useful if you want to PUT a file from local disk | 61 | // You can set this yourself - useful if you want to PUT a file from local disk |
| 59 | NSString *postBodyFilePath; | 62 | NSString *postBodyFilePath; |
| 60 | 63 | ||
| 64 | + // Path to a temporary file used to store a deflated post body (when shouldCompressPostBody is YES) | ||
| 65 | + NSString *compressedPostBodyFilePath; | ||
| 66 | + | ||
| 61 | // Set to true when ASIHTTPRequest automatically created a temporary file containing the request body (when true, the file at postBodyFilePath will be deleted at the end of the request) | 67 | // Set to true when ASIHTTPRequest automatically created a temporary file containing the request body (when true, the file at postBodyFilePath will be deleted at the end of the request) |
| 62 | BOOL didCreateTemporaryPostDataFile; | 68 | BOOL didCreateTemporaryPostDataFile; |
| 63 | 69 | ||
| @@ -91,6 +97,10 @@ extern NSString* const NetworkRequestErrorDomain; | @@ -91,6 +97,10 @@ extern NSString* const NetworkRequestErrorDomain; | ||
| 91 | // If allowCompressedResponse is true, requests will inform the server they can accept compressed data, and will automatically decompress gzipped responses. Default is true. | 97 | // If allowCompressedResponse is true, requests will inform the server they can accept compressed data, and will automatically decompress gzipped responses. Default is true. |
| 92 | BOOL allowCompressedResponse; | 98 | BOOL allowCompressedResponse; |
| 93 | 99 | ||
| 100 | + // If shouldCompressRequestBody is true, the request body will be gzipped. Default is false. | ||
| 101 | + // You will probably need to enable this feature on your webserver to make this work. Tested with apache only. | ||
| 102 | + BOOL shouldCompressRequestBody; | ||
| 103 | + | ||
| 94 | // When downloadDestinationPath is set, the result of this request will be downloaded to the file at this location | 104 | // When downloadDestinationPath is set, the result of this request will be downloaded to the file at this location |
| 95 | // If downloadDestinationPath is not set, download data will be stored in memory | 105 | // If downloadDestinationPath is not set, download data will be stored in memory |
| 96 | NSString *downloadDestinationPath; | 106 | NSString *downloadDestinationPath; |
| @@ -203,11 +213,18 @@ extern NSString* const NetworkRequestErrorDomain; | @@ -203,11 +213,18 @@ extern NSString* const NetworkRequestErrorDomain; | ||
| 203 | // Prevents the body of the post being built more than once (largely for subclasses) | 213 | // Prevents the body of the post being built more than once (largely for subclasses) |
| 204 | BOOL haveBuiltPostBody; | 214 | BOOL haveBuiltPostBody; |
| 205 | 215 | ||
| 216 | + // Used internally, may reflect the size of the internal used by CFNetwork | ||
| 217 | + // POST / PUT operations with body sizes greater than uploadBufferSize will not timeout unless more than uploadBufferSize bytes have been sent | ||
| 218 | + // Likely to be 32KB on iPhone 3.0, 128KB on Mac OS X Leopard and iPhone 2.2.x | ||
| 206 | unsigned long long uploadBufferSize; | 219 | unsigned long long uploadBufferSize; |
| 207 | 220 | ||
| 221 | + // Text encoding for responses that do not send a Content-Type with a charset value. Defaults to NSISOLatin1StringEncoding | ||
| 208 | NSStringEncoding defaultResponseEncoding; | 222 | NSStringEncoding defaultResponseEncoding; |
| 223 | + | ||
| 224 | + // The text encoding of the response, will be defaultResponseEncoding if the server didn't specify. Can't be set. | ||
| 209 | NSStringEncoding responseEncoding; | 225 | NSStringEncoding responseEncoding; |
| 210 | 226 | ||
| 227 | + // Tells ASIHTTPRequest not to delete partial downloads, and allows it to use an existing file to resume a download. Defaults to NO. | ||
| 211 | BOOL allowResumeForFileDownloads; | 228 | BOOL allowResumeForFileDownloads; |
| 212 | 229 | ||
| 213 | // Custom user information assosiated with the request | 230 | // Custom user information assosiated with the request |
| @@ -360,7 +377,7 @@ extern NSString* const NetworkRequestErrorDomain; | @@ -360,7 +377,7 @@ extern NSString* const NetworkRequestErrorDomain; | ||
| 360 | // Dump all session data (authentication and cookies) | 377 | // Dump all session data (authentication and cookies) |
| 361 | + (void)clearSession; | 378 | + (void)clearSession; |
| 362 | 379 | ||
| 363 | -#pragma mark gzip compression | 380 | +#pragma mark gzip decompression |
| 364 | 381 | ||
| 365 | // Uncompress gzipped data with zlib | 382 | // Uncompress gzipped data with zlib |
| 366 | + (NSData *)uncompressZippedData:(NSData*)compressedData; | 383 | + (NSData *)uncompressZippedData:(NSData*)compressedData; |
| @@ -369,6 +386,15 @@ extern NSString* const NetworkRequestErrorDomain; | @@ -369,6 +386,15 @@ extern NSString* const NetworkRequestErrorDomain; | ||
| 369 | + (int)uncompressZippedDataFromFile:(NSString *)sourcePath toFile:(NSString *)destinationPath; | 386 | + (int)uncompressZippedDataFromFile:(NSString *)sourcePath toFile:(NSString *)destinationPath; |
| 370 | + (int)uncompressZippedDataFromSource:(FILE *)source toDestination:(FILE *)dest; | 387 | + (int)uncompressZippedDataFromSource:(FILE *)source toDestination:(FILE *)dest; |
| 371 | 388 | ||
| 389 | +#pragma mark gzip compression | ||
| 390 | + | ||
| 391 | +// Compress data with gzip using zlib | ||
| 392 | ++ (NSData *)compressData:(NSData*)uncompressedData; | ||
| 393 | + | ||
| 394 | +// gzip compress data from a file, saving to another file, used for uploading when shouldCompressRequestBody is true | ||
| 395 | ++ (int)compressDataFromFile:(NSString *)sourcePath toFile:(NSString *)destinationPath; | ||
| 396 | ++ (int)compressDataFromSource:(FILE *)source toDestination:(FILE *)dest; | ||
| 397 | + | ||
| 372 | @property (retain) NSString *username; | 398 | @property (retain) NSString *username; |
| 373 | @property (retain) NSString *password; | 399 | @property (retain) NSString *password; |
| 374 | @property (retain) NSString *domain; | 400 | @property (retain) NSString *domain; |
| @@ -417,4 +443,5 @@ extern NSString* const NetworkRequestErrorDomain; | @@ -417,4 +443,5 @@ extern NSString* const NetworkRequestErrorDomain; | ||
| 417 | @property (assign, readonly) unsigned long long partialDownloadSize; | 443 | @property (assign, readonly) unsigned long long partialDownloadSize; |
| 418 | @property (assign) BOOL shouldRedirect; | 444 | @property (assign) BOOL shouldRedirect; |
| 419 | @property (assign) BOOL validatesSecureCertificate; | 445 | @property (assign) BOOL validatesSecureCertificate; |
| 446 | +@property (assign) BOOL shouldCompressRequestBody; | ||
| 420 | @end | 447 | @end |
| @@ -70,6 +70,8 @@ static NSError *ASITooMuchRedirectionError; | @@ -70,6 +70,8 @@ static NSError *ASITooMuchRedirectionError; | ||
| 70 | @property (assign, nonatomic) BOOL updatedProgress; | 70 | @property (assign, nonatomic) BOOL updatedProgress; |
| 71 | @property (assign, nonatomic) BOOL needsRedirect; | 71 | @property (assign, nonatomic) BOOL needsRedirect; |
| 72 | @property (assign, nonatomic) int redirectCount; | 72 | @property (assign, nonatomic) int redirectCount; |
| 73 | + @property (retain, nonatomic) NSData *compressedPostBody; | ||
| 74 | + @property (retain, nonatomic) NSString *compressedPostBodyFilePath; | ||
| 73 | @end | 75 | @end |
| 74 | 76 | ||
| 75 | @implementation ASIHTTPRequest | 77 | @implementation ASIHTTPRequest |
| @@ -133,6 +135,7 @@ static NSError *ASITooMuchRedirectionError; | @@ -133,6 +135,7 @@ static NSError *ASITooMuchRedirectionError; | ||
| 133 | [userInfo release]; | 135 | [userInfo release]; |
| 134 | [mainRequest release]; | 136 | [mainRequest release]; |
| 135 | [postBody release]; | 137 | [postBody release]; |
| 138 | + [compressedPostBody release]; | ||
| 136 | [requestCredentials release]; | 139 | [requestCredentials release]; |
| 137 | [error release]; | 140 | [error release]; |
| 138 | [requestHeaders release]; | 141 | [requestHeaders release]; |
| @@ -154,6 +157,7 @@ static NSError *ASITooMuchRedirectionError; | @@ -154,6 +157,7 @@ static NSError *ASITooMuchRedirectionError; | ||
| 154 | [cancelledLock release]; | 157 | [cancelledLock release]; |
| 155 | [authenticationMethod release]; | 158 | [authenticationMethod release]; |
| 156 | [postBodyFilePath release]; | 159 | [postBodyFilePath release]; |
| 160 | + [compressedPostBodyFilePath release]; | ||
| 157 | [postBodyWriteStream release]; | 161 | [postBodyWriteStream release]; |
| 158 | [postBodyReadStream release]; | 162 | [postBodyReadStream release]; |
| 159 | [super dealloc]; | 163 | [super dealloc]; |
| @@ -177,16 +181,28 @@ static NSError *ASITooMuchRedirectionError; | @@ -177,16 +181,28 @@ static NSError *ASITooMuchRedirectionError; | ||
| 177 | // Are we submitting the request body from a file on disk | 181 | // Are we submitting the request body from a file on disk |
| 178 | if ([self postBodyFilePath]) { | 182 | if ([self postBodyFilePath]) { |
| 179 | 183 | ||
| 180 | - // If we were writing to the post body via appendPostData or appendPostDataFromFile, close the write tream | 184 | + // If we were writing to the post body via appendPostData or appendPostDataFromFile, close the write stream |
| 181 | if ([self postBodyWriteStream]) { | 185 | if ([self postBodyWriteStream]) { |
| 182 | [[self postBodyWriteStream] close]; | 186 | [[self postBodyWriteStream] close]; |
| 183 | [self setPostBodyWriteStream:nil]; | 187 | [self setPostBodyWriteStream:nil]; |
| 184 | } | 188 | } |
| 189 | + | ||
| 190 | + if ([self shouldCompressRequestBody]) { | ||
| 191 | + [self setCompressedPostBodyFilePath:[NSTemporaryDirectory() stringByAppendingPathComponent:[[NSProcessInfo processInfo] globallyUniqueString]]]; | ||
| 192 | + [ASIHTTPRequest compressDataFromFile:[self postBodyFilePath] toFile:[self compressedPostBodyFilePath]]; | ||
| 193 | + [self setPostLength:[[[NSFileManager defaultManager] fileAttributesAtPath:[self compressedPostBodyFilePath] traverseLink:NO] fileSize]]; | ||
| 194 | + } else { | ||
| 185 | [self setPostLength:[[[NSFileManager defaultManager] fileAttributesAtPath:[self postBodyFilePath] traverseLink:NO] fileSize]]; | 195 | [self setPostLength:[[[NSFileManager defaultManager] fileAttributesAtPath:[self postBodyFilePath] traverseLink:NO] fileSize]]; |
| 196 | + } | ||
| 186 | 197 | ||
| 187 | // Otherwise, we have an in-memory request body | 198 | // Otherwise, we have an in-memory request body |
| 188 | } else { | 199 | } else { |
| 189 | - [self setPostLength:[postBody length]]; | 200 | + if ([self shouldCompressRequestBody]) { |
| 201 | + [self setCompressedPostBody:[ASIHTTPRequest compressData:[self postBody]]]; | ||
| 202 | + [self setPostLength:[[self compressedPostBody] length]]; | ||
| 203 | + } else { | ||
| 204 | + [self setPostLength:[[self postBody] length]]; | ||
| 205 | + } | ||
| 190 | } | 206 | } |
| 191 | 207 | ||
| 192 | if ([self postLength] > 0) | 208 | if ([self postLength] > 0) |
| @@ -194,7 +210,7 @@ static NSError *ASITooMuchRedirectionError; | @@ -194,7 +210,7 @@ static NSError *ASITooMuchRedirectionError; | ||
| 194 | if (![requestMethod isEqualToString:@"POST"] && ![requestMethod isEqualToString:@"PUT"]) { | 210 | if (![requestMethod isEqualToString:@"POST"] && ![requestMethod isEqualToString:@"PUT"]) { |
| 195 | [self setRequestMethod:@"POST"]; | 211 | [self setRequestMethod:@"POST"]; |
| 196 | } | 212 | } |
| 197 | - [self addRequestHeader:@"Content-Length" value:[NSString stringWithFormat:@"%llu",postLength]]; | 213 | + [self addRequestHeader:@"Content-Length" value:[NSString stringWithFormat:@"%llu",[self postLength]]]; |
| 198 | } | 214 | } |
| 199 | [self setHaveBuiltPostBody:YES]; | 215 | [self setHaveBuiltPostBody:YES]; |
| 200 | } | 216 | } |
| @@ -374,6 +390,11 @@ static NSError *ASITooMuchRedirectionError; | @@ -374,6 +390,11 @@ static NSError *ASITooMuchRedirectionError; | ||
| 374 | [self addRequestHeader:@"Accept-Encoding" value:@"gzip"]; | 390 | [self addRequestHeader:@"Accept-Encoding" value:@"gzip"]; |
| 375 | } | 391 | } |
| 376 | 392 | ||
| 393 | + // Configure a compressed request body | ||
| 394 | + if ([self shouldCompressRequestBody]) { | ||
| 395 | + [self addRequestHeader:@"Content-Encoding" value:@"gzip"]; | ||
| 396 | + } | ||
| 397 | + | ||
| 377 | // Should this request resume an existing download? | 398 | // Should this request resume an existing download? |
| 378 | if ([self allowResumeForFileDownloads] && [self downloadDestinationPath] && [self temporaryFileDownloadPath] && [[NSFileManager defaultManager] fileExistsAtPath:[self temporaryFileDownloadPath]]) { | 399 | if ([self allowResumeForFileDownloads] && [self downloadDestinationPath] && [self temporaryFileDownloadPath] && [[NSFileManager defaultManager] fileExistsAtPath:[self temporaryFileDownloadPath]]) { |
| 379 | [self setPartialDownloadSize:[[[NSFileManager defaultManager] fileAttributesAtPath:[self temporaryFileDownloadPath] traverseLink:NO] fileSize]]; | 400 | [self setPartialDownloadSize:[[[NSFileManager defaultManager] fileAttributesAtPath:[self temporaryFileDownloadPath] traverseLink:NO] fileSize]]; |
| @@ -394,9 +415,11 @@ static NSError *ASITooMuchRedirectionError; | @@ -394,9 +415,11 @@ static NSError *ASITooMuchRedirectionError; | ||
| 394 | CFHTTPMessageSetHeaderFieldValue(request, (CFStringRef)header, (CFStringRef)[requestHeaders objectForKey:header]); | 415 | CFHTTPMessageSetHeaderFieldValue(request, (CFStringRef)header, (CFStringRef)[requestHeaders objectForKey:header]); |
| 395 | } | 416 | } |
| 396 | 417 | ||
| 397 | - // If this is a post request and we have data in memory send, add it to the request | 418 | + // If this is a post/put request and we store the request body in memory, add it to the request |
| 398 | - if ([self postBody]) { | 419 | + if ([self shouldCompressRequestBody] && [self compressedPostBody]) { |
| 399 | - CFHTTPMessageSetBody(request, (CFDataRef)postBody); | 420 | + CFHTTPMessageSetBody(request, (CFDataRef)[self compressedPostBody]); |
| 421 | + } else if ([self postBody]) { | ||
| 422 | + CFHTTPMessageSetBody(request, (CFDataRef)[self postBody]); | ||
| 400 | } | 423 | } |
| 401 | 424 | ||
| 402 | [self loadRequest]; | 425 | [self loadRequest]; |
| @@ -435,8 +458,13 @@ static NSError *ASITooMuchRedirectionError; | @@ -435,8 +458,13 @@ static NSError *ASITooMuchRedirectionError; | ||
| 435 | } | 458 | } |
| 436 | // Create the stream for the request. | 459 | // Create the stream for the request. |
| 437 | if ([self shouldStreamPostDataFromDisk] && [self postBodyFilePath] && [[NSFileManager defaultManager] fileExistsAtPath:[self postBodyFilePath]]) { | 460 | if ([self shouldStreamPostDataFromDisk] && [self postBodyFilePath] && [[NSFileManager defaultManager] fileExistsAtPath:[self postBodyFilePath]]) { |
| 461 | + | ||
| 462 | + // Are we gzipping the request body? | ||
| 463 | + if ([self compressedPostBodyFilePath] && [[NSFileManager defaultManager] fileExistsAtPath:[self compressedPostBodyFilePath]]) { | ||
| 464 | + [self setPostBodyReadStream:[[[NSInputStream alloc] initWithFileAtPath:[self compressedPostBodyFilePath]] autorelease]]; | ||
| 465 | + } else { | ||
| 438 | [self setPostBodyReadStream:[[[NSInputStream alloc] initWithFileAtPath:[self postBodyFilePath]] autorelease]]; | 466 | [self setPostBodyReadStream:[[[NSInputStream alloc] initWithFileAtPath:[self postBodyFilePath]] autorelease]]; |
| 439 | - [[self postBodyReadStream] open]; | 467 | + } |
| 440 | readStream = CFReadStreamCreateForStreamedHTTPRequest(kCFAllocatorDefault, request,(CFReadStreamRef)[self postBodyReadStream]); | 468 | readStream = CFReadStreamCreateForStreamedHTTPRequest(kCFAllocatorDefault, request,(CFReadStreamRef)[self postBodyReadStream]); |
| 441 | } else { | 469 | } else { |
| 442 | readStream = CFReadStreamCreateForHTTPRequest(kCFAllocatorDefault, request); | 470 | readStream = CFReadStreamCreateForHTTPRequest(kCFAllocatorDefault, request); |
| @@ -626,14 +654,22 @@ static NSError *ASITooMuchRedirectionError; | @@ -626,14 +654,22 @@ static NSError *ASITooMuchRedirectionError; | ||
| 626 | 654 | ||
| 627 | - (void)removePostDataFile | 655 | - (void)removePostDataFile |
| 628 | { | 656 | { |
| 629 | - if (postBodyFilePath) { | 657 | + if ([self postBodyFilePath]) { |
| 630 | NSError *removeError = nil; | 658 | NSError *removeError = nil; |
| 631 | - [[NSFileManager defaultManager] removeItemAtPath:postBodyFilePath error:&removeError]; | 659 | + [[NSFileManager defaultManager] removeItemAtPath:[self postBodyFilePath] error:&removeError]; |
| 632 | if (removeError) { | 660 | if (removeError) { |
| 633 | - [self failWithError:[NSError errorWithDomain:NetworkRequestErrorDomain code:ASIFileManagementError userInfo:[NSDictionary dictionaryWithObjectsAndKeys:[NSString stringWithFormat:@"Failed to delete file at %@ with error: %@",postBodyFilePath,removeError],NSLocalizedDescriptionKey,removeError,NSUnderlyingErrorKey,nil]]]; | 661 | + [self failWithError:[NSError errorWithDomain:NetworkRequestErrorDomain code:ASIFileManagementError userInfo:[NSDictionary dictionaryWithObjectsAndKeys:[NSString stringWithFormat:@"Failed to delete file at %@ with error: %@",[self postBodyFilePath],removeError],NSLocalizedDescriptionKey,removeError,NSUnderlyingErrorKey,nil]]]; |
| 634 | } | 662 | } |
| 635 | [self setPostBodyFilePath:nil]; | 663 | [self setPostBodyFilePath:nil]; |
| 636 | } | 664 | } |
| 665 | + if ([self compressedPostBodyFilePath]) { | ||
| 666 | + NSError *removeError = nil; | ||
| 667 | + [[NSFileManager defaultManager] removeItemAtPath:[self compressedPostBodyFilePath] error:&removeError]; | ||
| 668 | + if (removeError) { | ||
| 669 | + [self failWithError:[NSError errorWithDomain:NetworkRequestErrorDomain code:ASIFileManagementError userInfo:[NSDictionary dictionaryWithObjectsAndKeys:[NSString stringWithFormat:@"Failed to delete file at %@ with error: %@",[self compressedPostBodyFilePath],removeError],NSLocalizedDescriptionKey,removeError,NSUnderlyingErrorKey,nil]]]; | ||
| 670 | + } | ||
| 671 | + [self setCompressedPostBodyFilePath:nil]; | ||
| 672 | + } | ||
| 637 | } | 673 | } |
| 638 | 674 | ||
| 639 | 675 | ||
| @@ -708,7 +744,7 @@ static NSError *ASITooMuchRedirectionError; | @@ -708,7 +744,7 @@ static NSError *ASITooMuchRedirectionError; | ||
| 708 | return; | 744 | return; |
| 709 | } | 745 | } |
| 710 | 746 | ||
| 711 | - // If this is the first time we've written to the buffer, byteCount will be the size of the buffer (currently seems to be 128KB on both Mac and iPhone 2.2.1, 32KB on iPhone 3.0) | 747 | + // If this is the first time we've written to the buffer, byteCount will be the size of the buffer (currently seems to be 128KB on both Leopard and iPhone 2.2.1, 32KB on iPhone 3.0) |
| 712 | // If request body is less than the buffer size, byteCount will be the total size of the request body | 748 | // If request body is less than the buffer size, byteCount will be the total size of the request body |
| 713 | // We will remove this from any progress display, as kCFStreamPropertyHTTPRequestBytesWrittenCount does not tell us how much data has actually be written | 749 | // We will remove this from any progress display, as kCFStreamPropertyHTTPRequestBytesWrittenCount does not tell us how much data has actually be written |
| 714 | if (totalBytesSent > 0 && uploadBufferSize == 0 && totalBytesSent != postLength) { | 750 | if (totalBytesSent > 0 && uploadBufferSize == 0 && totalBytesSent != postLength) { |
| @@ -1258,6 +1294,7 @@ static NSError *ASITooMuchRedirectionError; | @@ -1258,6 +1294,7 @@ static NSError *ASITooMuchRedirectionError; | ||
| 1258 | 1294 | ||
| 1259 | - (void)handleNetworkEvent:(CFStreamEventType)type | 1295 | - (void)handleNetworkEvent:(CFStreamEventType)type |
| 1260 | { | 1296 | { |
| 1297 | + NSLog(@"%hi",type); | ||
| 1261 | // Dispatch the stream events. | 1298 | // Dispatch the stream events. |
| 1262 | switch (type) { | 1299 | switch (type) { |
| 1263 | case kCFStreamEventHasBytesAvailable: | 1300 | case kCFStreamEventHasBytesAvailable: |
| @@ -1334,7 +1371,7 @@ static NSError *ASITooMuchRedirectionError; | @@ -1334,7 +1371,7 @@ static NSError *ASITooMuchRedirectionError; | ||
| 1334 | 1371 | ||
| 1335 | - (void)handleStreamComplete | 1372 | - (void)handleStreamComplete |
| 1336 | { | 1373 | { |
| 1337 | - //Try to read the headers (if this is a HEAD request handleBytesAvailable available may not be called) | 1374 | + //Try to read the headers (if this is a HEAD request handleBytesAvailable may not be called) |
| 1338 | if (![self responseHeaders]) { | 1375 | if (![self responseHeaders]) { |
| 1339 | if ([self readResponseHeadersReturningAuthenticationFailure]) { | 1376 | if ([self readResponseHeadersReturningAuthenticationFailure]) { |
| 1340 | [self attemptToApplyCredentialsAndResume]; | 1377 | [self attemptToApplyCredentialsAndResume]; |
| @@ -1536,7 +1573,7 @@ static NSError *ASITooMuchRedirectionError; | @@ -1536,7 +1573,7 @@ static NSError *ASITooMuchRedirectionError; | ||
| 1536 | } | 1573 | } |
| 1537 | 1574 | ||
| 1538 | 1575 | ||
| 1539 | -#pragma mark gzip data handling | 1576 | +#pragma mark gzip decompression |
| 1540 | 1577 | ||
| 1541 | // | 1578 | // |
| 1542 | // Contributed by Shaun Harrison of Enormego, see: http://developers.enormego.com/view/asihttprequest_gzip | 1579 | // Contributed by Shaun Harrison of Enormego, see: http://developers.enormego.com/view/asihttprequest_gzip |
| @@ -1589,24 +1626,29 @@ static NSError *ASITooMuchRedirectionError; | @@ -1589,24 +1626,29 @@ static NSError *ASITooMuchRedirectionError; | ||
| 1589 | } | 1626 | } |
| 1590 | } | 1627 | } |
| 1591 | 1628 | ||
| 1592 | - | 1629 | +// NOTE: To debug this method, turn off Data Formatters in Xcode or you'll crash on closeFile |
| 1593 | + (int)uncompressZippedDataFromFile:(NSString *)sourcePath toFile:(NSString *)destinationPath | 1630 | + (int)uncompressZippedDataFromFile:(NSString *)sourcePath toFile:(NSString *)destinationPath |
| 1594 | { | 1631 | { |
| 1595 | - // Get a FILE struct for the source file | ||
| 1596 | - FILE *source = fdopen([[NSFileHandle fileHandleForReadingAtPath:sourcePath] fileDescriptor], "r"); | ||
| 1597 | - | ||
| 1598 | // Create an empty file at the destination path | 1632 | // Create an empty file at the destination path |
| 1599 | [[NSFileManager defaultManager] createFileAtPath:destinationPath contents:[NSData data] attributes:nil]; | 1633 | [[NSFileManager defaultManager] createFileAtPath:destinationPath contents:[NSData data] attributes:nil]; |
| 1600 | 1634 | ||
| 1635 | + // Get a FILE struct for the source file | ||
| 1636 | + NSFileHandle *inputFileHandle = [NSFileHandle fileHandleForReadingAtPath:sourcePath]; | ||
| 1637 | + FILE *source = fdopen([inputFileHandle fileDescriptor], "r"); | ||
| 1638 | + | ||
| 1601 | // Get a FILE struct for the destination path | 1639 | // Get a FILE struct for the destination path |
| 1602 | - FILE *dest = fdopen([[NSFileHandle fileHandleForWritingAtPath:destinationPath] fileDescriptor], "w"); | 1640 | + NSFileHandle *outputFileHandle = [NSFileHandle fileHandleForWritingAtPath:destinationPath]; |
| 1641 | + FILE *dest = fdopen([outputFileHandle fileDescriptor], "w"); | ||
| 1642 | + | ||
| 1603 | 1643 | ||
| 1604 | // Uncompress data in source and save in destination | 1644 | // Uncompress data in source and save in destination |
| 1605 | int status = [ASIHTTPRequest uncompressZippedDataFromSource:source toDestination:dest]; | 1645 | int status = [ASIHTTPRequest uncompressZippedDataFromSource:source toDestination:dest]; |
| 1606 | 1646 | ||
| 1607 | // Close the files | 1647 | // Close the files |
| 1608 | - fclose(source); | ||
| 1609 | fclose(dest); | 1648 | fclose(dest); |
| 1649 | + fclose(source); | ||
| 1650 | + [inputFileHandle closeFile]; | ||
| 1651 | + [outputFileHandle closeFile]; | ||
| 1610 | return status; | 1652 | return status; |
| 1611 | } | 1653 | } |
| 1612 | 1654 | ||
| @@ -1615,6 +1657,7 @@ static NSError *ASITooMuchRedirectionError; | @@ -1615,6 +1657,7 @@ static NSError *ASITooMuchRedirectionError; | ||
| 1615 | // http://www.zlib.net/zpipe.c | 1657 | // http://www.zlib.net/zpipe.c |
| 1616 | // | 1658 | // |
| 1617 | #define CHUNK 16384 | 1659 | #define CHUNK 16384 |
| 1660 | +#define SET_BINARY_MODE(file) | ||
| 1618 | + (int)uncompressZippedDataFromSource:(FILE *)source toDestination:(FILE *)dest | 1661 | + (int)uncompressZippedDataFromSource:(FILE *)source toDestination:(FILE *)dest |
| 1619 | { | 1662 | { |
| 1620 | int ret; | 1663 | int ret; |
| @@ -1673,6 +1716,134 @@ static NSError *ASITooMuchRedirectionError; | @@ -1673,6 +1716,134 @@ static NSError *ASITooMuchRedirectionError; | ||
| 1673 | return ret == Z_STREAM_END ? Z_OK : Z_DATA_ERROR; | 1716 | return ret == Z_STREAM_END ? Z_OK : Z_DATA_ERROR; |
| 1674 | } | 1717 | } |
| 1675 | 1718 | ||
| 1719 | + | ||
| 1720 | +#pragma mark gzip compression | ||
| 1721 | + | ||
| 1722 | +// Based on this from Robbie Hanson: http://deusty.blogspot.com/2007/07/gzip-compressiondecompression.html | ||
| 1723 | + | ||
| 1724 | ++ (NSData *)compressData:(NSData*)uncompressedData | ||
| 1725 | +{ | ||
| 1726 | + if ([uncompressedData length] == 0) return uncompressedData; | ||
| 1727 | + | ||
| 1728 | + z_stream strm; | ||
| 1729 | + | ||
| 1730 | + strm.zalloc = Z_NULL; | ||
| 1731 | + strm.zfree = Z_NULL; | ||
| 1732 | + strm.opaque = Z_NULL; | ||
| 1733 | + strm.total_out = 0; | ||
| 1734 | + strm.next_in=(Bytef *)[uncompressedData bytes]; | ||
| 1735 | + strm.avail_in = [uncompressedData length]; | ||
| 1736 | + | ||
| 1737 | + // Compresssion Levels: | ||
| 1738 | + // Z_NO_COMPRESSION | ||
| 1739 | + // Z_BEST_SPEED | ||
| 1740 | + // Z_BEST_COMPRESSION | ||
| 1741 | + // Z_DEFAULT_COMPRESSION | ||
| 1742 | + | ||
| 1743 | + if (deflateInit2(&strm, Z_DEFAULT_COMPRESSION, Z_DEFLATED, (15+16), 8, Z_DEFAULT_STRATEGY) != Z_OK) return nil; | ||
| 1744 | + | ||
| 1745 | + NSMutableData *compressed = [NSMutableData dataWithLength:16384]; // 16K chunks for expansion | ||
| 1746 | + | ||
| 1747 | + do { | ||
| 1748 | + | ||
| 1749 | + if (strm.total_out >= [compressed length]) | ||
| 1750 | + [compressed increaseLengthBy: 16384]; | ||
| 1751 | + | ||
| 1752 | + strm.next_out = [compressed mutableBytes] + strm.total_out; | ||
| 1753 | + strm.avail_out = [compressed length] - strm.total_out; | ||
| 1754 | + | ||
| 1755 | + deflate(&strm, Z_FINISH); | ||
| 1756 | + | ||
| 1757 | + } while (strm.avail_out == 0); | ||
| 1758 | + | ||
| 1759 | + deflateEnd(&strm); | ||
| 1760 | + | ||
| 1761 | + [compressed setLength: strm.total_out]; | ||
| 1762 | + return [NSData dataWithData:compressed]; | ||
| 1763 | +} | ||
| 1764 | + | ||
| 1765 | +// NOTE: To debug this method, turn off Data Formatters in Xcode or you'll crash on closeFile | ||
| 1766 | ++ (int)compressDataFromFile:(NSString *)sourcePath toFile:(NSString *)destinationPath | ||
| 1767 | +{ | ||
| 1768 | + // Create an empty file at the destination path | ||
| 1769 | + [[NSFileManager defaultManager] createFileAtPath:destinationPath contents:[NSData data] attributes:nil]; | ||
| 1770 | + | ||
| 1771 | + // Get a FILE struct for the source file | ||
| 1772 | + NSFileHandle *inputFileHandle = [NSFileHandle fileHandleForReadingAtPath:sourcePath]; | ||
| 1773 | + FILE *source = fdopen([inputFileHandle fileDescriptor], "r"); | ||
| 1774 | + | ||
| 1775 | + // Get a FILE struct for the destination path | ||
| 1776 | + NSFileHandle *outputFileHandle = [NSFileHandle fileHandleForWritingAtPath:destinationPath]; | ||
| 1777 | + FILE *dest = fdopen([outputFileHandle fileDescriptor], "w"); | ||
| 1778 | + | ||
| 1779 | + // compress data in source and save in destination | ||
| 1780 | + int status = [ASIHTTPRequest compressDataFromSource:source toDestination:dest]; | ||
| 1781 | + | ||
| 1782 | + // Close the files | ||
| 1783 | + fclose(dest); | ||
| 1784 | + fclose(source); | ||
| 1785 | + | ||
| 1786 | + // We have to close both of these explictly because CFReadStreamCreateForStreamedHTTPRequest() seems to go bonkers otherwise | ||
| 1787 | + [inputFileHandle closeFile]; | ||
| 1788 | + [outputFileHandle closeFile]; | ||
| 1789 | + | ||
| 1790 | + return status; | ||
| 1791 | +} | ||
| 1792 | + | ||
| 1793 | +// | ||
| 1794 | +// Also from the zlib sample code at http://www.zlib.net/zpipe.c | ||
| 1795 | +// | ||
| 1796 | ++ (int)compressDataFromSource:(FILE *)source toDestination:(FILE *)dest | ||
| 1797 | +{ | ||
| 1798 | + int ret, flush; | ||
| 1799 | + unsigned have; | ||
| 1800 | + z_stream strm; | ||
| 1801 | + unsigned char in[CHUNK]; | ||
| 1802 | + unsigned char out[CHUNK]; | ||
| 1803 | + | ||
| 1804 | + /* allocate deflate state */ | ||
| 1805 | + strm.zalloc = Z_NULL; | ||
| 1806 | + strm.zfree = Z_NULL; | ||
| 1807 | + strm.opaque = Z_NULL; | ||
| 1808 | + ret = deflateInit2(&strm, Z_DEFAULT_COMPRESSION, Z_DEFLATED, (15+16), 8, Z_DEFAULT_STRATEGY); | ||
| 1809 | + if (ret != Z_OK) | ||
| 1810 | + return ret; | ||
| 1811 | + | ||
| 1812 | + /* compress until end of file */ | ||
| 1813 | + do { | ||
| 1814 | + strm.avail_in = fread(in, 1, CHUNK, source); | ||
| 1815 | + if (ferror(source)) { | ||
| 1816 | + (void)deflateEnd(&strm); | ||
| 1817 | + return Z_ERRNO; | ||
| 1818 | + } | ||
| 1819 | + flush = feof(source) ? Z_FINISH : Z_NO_FLUSH; | ||
| 1820 | + strm.next_in = in; | ||
| 1821 | + | ||
| 1822 | + /* run deflate() on input until output buffer not full, finish | ||
| 1823 | + compression if all of source has been read in */ | ||
| 1824 | + do { | ||
| 1825 | + strm.avail_out = CHUNK; | ||
| 1826 | + strm.next_out = out; | ||
| 1827 | + ret = deflate(&strm, flush); /* no bad return value */ | ||
| 1828 | + assert(ret != Z_STREAM_ERROR); /* state not clobbered */ | ||
| 1829 | + have = CHUNK - strm.avail_out; | ||
| 1830 | + if (fwrite(out, 1, have, dest) != have || ferror(dest)) { | ||
| 1831 | + (void)deflateEnd(&strm); | ||
| 1832 | + return Z_ERRNO; | ||
| 1833 | + } | ||
| 1834 | + } while (strm.avail_out == 0); | ||
| 1835 | + assert(strm.avail_in == 0); /* all input will be used */ | ||
| 1836 | + | ||
| 1837 | + /* done when last data in file processed */ | ||
| 1838 | + } while (flush != Z_FINISH); | ||
| 1839 | + assert(ret == Z_STREAM_END); /* stream will be complete */ | ||
| 1840 | + | ||
| 1841 | + /* clean up and return */ | ||
| 1842 | + (void)deflateEnd(&strm); | ||
| 1843 | + return Z_OK; | ||
| 1844 | +} | ||
| 1845 | + | ||
| 1846 | + | ||
| 1676 | @synthesize username; | 1847 | @synthesize username; |
| 1677 | @synthesize password; | 1848 | @synthesize password; |
| 1678 | @synthesize domain; | 1849 | @synthesize domain; |
| @@ -1702,6 +1873,7 @@ static NSError *ASITooMuchRedirectionError; | @@ -1702,6 +1873,7 @@ static NSError *ASITooMuchRedirectionError; | ||
| 1702 | @synthesize timeOutSeconds; | 1873 | @synthesize timeOutSeconds; |
| 1703 | @synthesize requestMethod; | 1874 | @synthesize requestMethod; |
| 1704 | @synthesize postBody; | 1875 | @synthesize postBody; |
| 1876 | +@synthesize compressedPostBody; | ||
| 1705 | @synthesize contentLength; | 1877 | @synthesize contentLength; |
| 1706 | @synthesize partialDownloadSize; | 1878 | @synthesize partialDownloadSize; |
| 1707 | @synthesize postLength; | 1879 | @synthesize postLength; |
| @@ -1717,6 +1889,7 @@ static NSError *ASITooMuchRedirectionError; | @@ -1717,6 +1889,7 @@ static NSError *ASITooMuchRedirectionError; | ||
| 1717 | @synthesize allowResumeForFileDownloads; | 1889 | @synthesize allowResumeForFileDownloads; |
| 1718 | @synthesize userInfo; | 1890 | @synthesize userInfo; |
| 1719 | @synthesize postBodyFilePath; | 1891 | @synthesize postBodyFilePath; |
| 1892 | +@synthesize compressedPostBodyFilePath; | ||
| 1720 | @synthesize postBodyWriteStream; | 1893 | @synthesize postBodyWriteStream; |
| 1721 | @synthesize postBodyReadStream; | 1894 | @synthesize postBodyReadStream; |
| 1722 | @synthesize shouldStreamPostDataFromDisk; | 1895 | @synthesize shouldStreamPostDataFromDisk; |
| @@ -1733,4 +1906,5 @@ static NSError *ASITooMuchRedirectionError; | @@ -1733,4 +1906,5 @@ static NSError *ASITooMuchRedirectionError; | ||
| 1733 | @synthesize validatesSecureCertificate; | 1906 | @synthesize validatesSecureCertificate; |
| 1734 | @synthesize needsRedirect; | 1907 | @synthesize needsRedirect; |
| 1735 | @synthesize redirectCount; | 1908 | @synthesize redirectCount; |
| 1909 | +@synthesize shouldCompressRequestBody; | ||
| 1736 | @end | 1910 | @end |
| @@ -10,7 +10,7 @@ | @@ -10,7 +10,7 @@ | ||
| 10 | #import "ASIHTTPRequest.h" | 10 | #import "ASIHTTPRequest.h" |
| 11 | #import "ASINSStringAdditions.h" | 11 | #import "ASINSStringAdditions.h" |
| 12 | #import "ASINetworkQueue.h" | 12 | #import "ASINetworkQueue.h" |
| 13 | - | 13 | +#import "ASIFormDataRequest.h" |
| 14 | 14 | ||
| 15 | @implementation ASIHTTPRequestTests | 15 | @implementation ASIHTTPRequestTests |
| 16 | 16 | ||
| @@ -680,6 +680,58 @@ | @@ -680,6 +680,58 @@ | ||
| 680 | GHAssertTrue(success,@"Failed to use GET on new URL"); | 680 | GHAssertTrue(success,@"Failed to use GET on new URL"); |
| 681 | } | 681 | } |
| 682 | 682 | ||
| 683 | +- (void)testCompression | ||
| 684 | +{ | ||
| 685 | + NSString *content = @"This is the test content. This is the test content. This is the test content. This is the test content."; | ||
| 686 | + | ||
| 687 | + // Test in memory compression / decompression | ||
| 688 | + NSData *data = [content dataUsingEncoding:NSUTF8StringEncoding]; | ||
| 689 | + NSData *compressedData = [ASIHTTPRequest compressData:data]; | ||
| 690 | + NSData *uncompressedData = [ASIHTTPRequest uncompressZippedData:compressedData]; | ||
| 691 | + NSString *newContent = [[[NSString alloc] initWithBytes:[uncompressedData bytes] length:[uncompressedData length] encoding:NSUTF8StringEncoding] autorelease]; | ||
| 692 | + | ||
| 693 | + BOOL success = [newContent isEqualToString:content]; | ||
| 694 | + GHAssertTrue(success,@"Failed compress or decompress the correct data"); | ||
| 695 | + | ||
| 696 | + // Test file to file compression / decompression | ||
| 697 | + NSString *basePath = [[[NSBundle mainBundle] bundlePath] stringByDeletingLastPathComponent]; | ||
| 698 | + NSString *sourcePath = [basePath stringByAppendingPathComponent:@"text.txt"]; | ||
| 699 | + NSString *destPath = [basePath stringByAppendingPathComponent:@"text.txt.compressed"]; | ||
| 700 | + NSString *newPath = [basePath stringByAppendingPathComponent:@"text2.txt"]; | ||
| 701 | + | ||
| 702 | + [content writeToFile:sourcePath atomically:NO encoding:NSUTF8StringEncoding error:NULL]; | ||
| 703 | + [ASIHTTPRequest compressDataFromFile:sourcePath toFile:destPath]; | ||
| 704 | + [ASIHTTPRequest uncompressZippedDataFromFile:destPath toFile:newPath]; | ||
| 705 | + success = [[NSString stringWithContentsOfFile:newPath encoding:NSUTF8StringEncoding error:NULL] isEqualToString:content]; | ||
| 706 | + GHAssertTrue(success,@"Failed compress or decompress the correct data"); | ||
| 707 | + | ||
| 708 | + // Test compressed body | ||
| 709 | + // Body is deflated by ASIHTTPRequest, sent, inflated by the server, printed, deflated by mod_deflate, response is inflated by ASIHTTPRequest | ||
| 710 | + ASIHTTPRequest *request = [ASIHTTPRequest requestWithURL:[NSURL URLWithString:@"http://asi/ASIHTTPRequest/tests/compressed_post_body"]]; | ||
| 711 | + [request setRequestMethod:@"PUT"]; | ||
| 712 | + [request setShouldCompressRequestBody:YES]; | ||
| 713 | + [request appendPostData:data]; | ||
| 714 | + [request start]; | ||
| 715 | + | ||
| 716 | + success = [[request responseString] isEqualToString:content]; | ||
| 717 | + GHAssertTrue(success,@"Failed to compress the body, or server failed to decompress it"); | ||
| 718 | + | ||
| 719 | + | ||
| 720 | + request = [ASIHTTPRequest requestWithURL:[NSURL URLWithString:@"http://allseeing-i.com/ASIHTTPRequest/tests/compressed_post_body"]]; | ||
| 721 | + [request setRequestMethod:@"PUT"]; | ||
| 722 | + [request setShouldCompressRequestBody:YES]; | ||
| 723 | + [request setShouldStreamPostDataFromDisk:YES]; | ||
| 724 | + [request setUploadProgressDelegate:self]; | ||
| 725 | + [request setPostBodyFilePath:sourcePath]; | ||
| 726 | + | ||
| 727 | + [request start]; | ||
| 728 | + | ||
| 729 | + success = [[request responseString] isEqualToString:content]; | ||
| 730 | + GHAssertTrue(success,@"Failed to compress the body, or server failed to decompress it"); | ||
| 731 | + | ||
| 732 | + | ||
| 733 | + | ||
| 734 | +} | ||
| 683 | 735 | ||
| 684 | 736 | ||
| 685 | @end | 737 | @end |
-
Please register or login to post a comment