首先补充点东西。
main 方法中的[self buildRequestHeaders];
- (void)buildRequestHeaders
{
if ([self haveBuiltRequestHeaders]) {
return;
}
[self setHaveBuiltRequestHeaders:YES];
if ([self mainRequest]) {
for (NSString *header in [[self mainRequest] requestHeaders]) {
[self addRequestHeader:header value:[[[self mainRequest] requestHeaders] valueForKey:header]];
}
return;
}
[self applyCookieHeader];
// Build and set the user agent string if the request does not already have a custom user agent specified
if (![[self requestHeaders] objectForKey:@"User-Agent"]) {
NSString *userAgentString = [ASIHTTPRequest defaultUserAgentString];
if (userAgentString) {
[self addRequestHeader:@"User-Agent" value:userAgentString];
}
}
// Accept a compressed response
if ([self allowCompressedResponse]) {
[self addRequestHeader:@"Accept-Encoding" value:@"gzip"];
}
// Configure a compressed request body
if ([self shouldCompressRequestBody]) {
[self addRequestHeader:@"Content-Encoding" value:@"gzip"];
}
// Should this request resume an existing download?
[self updatePartialDownloadSize];
if ([self partialDownloadSize]) {
[self addRequestHeader:@"Range" value:[NSString stringWithFormat:@"bytes=%llu-",[self partialDownloadSize]]];
}
}
- (void)updatePartialDownloadSize
{
NSFileManager *fileManager = [[[NSFileManager alloc] init] autorelease];
if ([self allowResumeForFileDownloads] && [self downloadDestinationPath] && [self temporaryFileDownloadPath] && [fileManager fileExistsAtPath:[self temporaryFileDownloadPath]]) {
NSError *err = nil;
[self setPartialDownloadSize:[[fileManager attributesOfItemAtPath:[self temporaryFileDownloadPath] error:&err] fileSize]];
if (err) {
[self failWithError:[NSError errorWithDomain:NetworkRequestErrorDomain code:ASIFileManagementError userInfo:[NSDictionary dictionaryWithObjectsAndKeys:[NSString stringWithFormat:@"Failed to get attributes for file at path '%@'",[self temporaryFileDownloadPath]],NSLocalizedDescriptionKey,error,NSUnderlyingErrorKey,nil]]];
return;
}
}
}
buildRequestHeaders 主要是设置gzip,断点续传
断点续传需要设置的属性如下:
allowResumeForFileDownloads
downloadDestinationPath
temporaryFileDownloadPath
if ([self allowResumeForFileDownloads] && [self downloadDestinationPath] && [self temporaryFileDownloadPath] && [fileManager fileExistsAtPath:[self temporaryFileDownloadPath]]) {
NSError *err = nil;
[self setPartialDownloadSize:[[fileManager attributesOfItemAtPath:[self temporaryFileDownloadPath] error:&err] fileSize]];
if (err) {
[self failWithError:[NSError errorWithDomain:NetworkRequestErrorDomain code:ASIFileManagementError userInfo:[NSDictionary dictionaryWithObjectsAndKeys:[NSString stringWithFormat:@"Failed to get attributes for file at path '%@'",[self temporaryFileDownloadPath]],NSLocalizedDescriptionKey,error,NSUnderlyingErrorKey,nil]]];
return;
}
}
- (void)startRequest
{
if ([self isCancelled]) {
return;
}
[self performSelectorOnMainThread:@selector(requestStarted) withObject:nil waitUntilDone:[NSThread isMainThread]];
[self setDownloadComplete:NO];
[self setComplete:NO];
[self setTotalBytesRead:0];
[self setLastBytesRead:0];
if ([self redirectCount] == 0) {
[self setOriginalURL:[self url]];
}
// If we're retrying a request, let's remove any progress we made
if ([self lastBytesSent] > 0) {
[self removeUploadProgressSoFar];
}
[self setLastBytesSent:0];
[self setContentLength:0];
[self setResponseHeaders:nil];
if (![self downloadDestinationPath]) {
[self setRawResponseData:[[[NSMutableData alloc] init] autorelease]];
}
//
// Create the stream for the request
//
NSFileManager *fileManager = [[[NSFileManager alloc] init] autorelease];
[self setReadStreamIsScheduled:NO];
// Do we need to stream the request body from disk
if ([self shouldStreamPostDataFromDisk] && [self postBodyFilePath] && [fileManager fileExistsAtPath:[self postBodyFilePath]]) {
// Are we gzipping the request body?
if ([self compressedPostBodyFilePath] && [fileManager fileExistsAtPath:[self compressedPostBodyFilePath]]) {
[self setPostBodyReadStream:[ASIInputStream inputStreamWithFileAtPath:[self compressedPostBodyFilePath] request:self]];
} else {
[self setPostBodyReadStream:[ASIInputStream inputStreamWithFileAtPath:[self postBodyFilePath] request:self]];
}
[self setReadStream:[(NSInputStream *)CFReadStreamCreateForStreamedHTTPRequest(kCFAllocatorDefault, request,(CFReadStreamRef)[self postBodyReadStream]) autorelease]];
} else {
// If we have a request body, we'll stream it from memory using our custom stream, so that we can measure bandwidth use and it can be bandwidth-throttled if necessary
if ([self postBody] && [[self postBody] length] > 0) {
if ([self shouldCompressRequestBody] && [self compressedPostBody]) {
[self setPostBodyReadStream:[ASIInputStream inputStreamWithData:[self compressedPostBody] request:self]];
} else if ([self postBody]) {
[self setPostBodyReadStream:[ASIInputStream inputStreamWithData:[self postBody] request:self]];
}
[self setReadStream:[(NSInputStream *)CFReadStreamCreateForStreamedHTTPRequest(kCFAllocatorDefault, request,(CFReadStreamRef)[self postBodyReadStream]) autorelease]];
} else {
[self setReadStream:[(NSInputStream *)CFReadStreamCreateForHTTPRequest(kCFAllocatorDefault, request) autorelease]];
}
}
if (![self readStream]) {
[self failWithError:[NSError errorWithDomain:NetworkRequestErrorDomain code:ASIInternalErrorWhileBuildingRequestType userInfo:[NSDictionary dictionaryWithObjectsAndKeys:@"Unable to create read stream",NSLocalizedDescriptionKey,nil]]];
return;
}
//
// Handle SSL certificate settings
//
if([[[[self url] scheme] lowercaseString] isEqualToString:@"https"]) {
NSMutableDictionary *sslProperties = [NSMutableDictionary dictionaryWithCapacity:1];
// Tell CFNetwork not to validate SSL certificates
if (![self validatesSecureCertificate]) {
[sslProperties setObject:(NSString *)kCFBooleanFalse forKey:(NSString *)kCFStreamSSLValidatesCertificateChain];
}
// Tell CFNetwork to use a client certificate
if (clientCertificateIdentity) {
NSMutableArray *certificates = [NSMutableArray arrayWithCapacity:[clientCertificates count]+1];
// The first object in the array is our SecIdentityRef
[certificates addObject:(id)clientCertificateIdentity];
// If we've added any additional certificates, add them too
for (id cert in clientCertificates) {
[certificates addObject:cert];
}
[sslProperties setObject:certificates forKey:(NSString *)kCFStreamSSLCertificates];
}
CFReadStreamSetProperty((CFReadStreamRef)[self readStream], kCFStreamPropertySSLSettings, sslProperties);
}
//
// Handle proxy settings
//
if ([self proxyHost] && [self proxyPort]) {
NSString *hostKey;
NSString *portKey;
if (![self proxyType]) {
[self setProxyType:(NSString *)kCFProxyTypeHTTP];
}
if ([[self proxyType] isEqualToString:(NSString *)kCFProxyTypeSOCKS]) {
hostKey = (NSString *)kCFStreamPropertySOCKSProxyHost;
portKey = (NSString *)kCFStreamPropertySOCKSProxyPort;
} else {
hostKey = (NSString *)kCFStreamPropertyHTTPProxyHost;
portKey = (NSString *)kCFStreamPropertyHTTPProxyPort;
if ([[[[self url] scheme] lowercaseString] isEqualToString:@"https"]) {
hostKey = (NSString *)kCFStreamPropertyHTTPSProxyHost;
portKey = (NSString *)kCFStreamPropertyHTTPSProxyPort;
}
}
NSMutableDictionary *proxyToUse = [NSMutableDictionary dictionaryWithObjectsAndKeys:[self proxyHost],hostKey,[NSNumber numberWithInt:[self proxyPort]],portKey,nil];
if ([[self proxyType] isEqualToString:(NSString *)kCFProxyTypeSOCKS]) {
CFReadStreamSetProperty((CFReadStreamRef)[self readStream], kCFStreamPropertySOCKSProxy, proxyToUse);
} else {
CFReadStreamSetProperty((CFReadStreamRef)[self readStream], kCFStreamPropertyHTTPProxy, proxyToUse);
}
}
//
// Handle persistent connections
//
[ASIHTTPRequest expirePersistentConnections];
[connectionsLock lock];
if (![[self url] host] || ![[self url] scheme]) {
[self setConnectionInfo:nil];
[self setShouldAttemptPersistentConnection:NO];
}
// Will store the old stream that was using this connection (if there was one) so we can clean it up once we've opened our own stream
NSInputStream *oldStream = nil;
// Use a persistent connection if possible
if ([self shouldAttemptPersistentConnection]) {
// If we are redirecting, we will re-use the current connection only if we are connecting to the same server
if ([self connectionInfo]) {
if (![[[self connectionInfo] objectForKey:@"host"] isEqualToString:[[self url] host]] || ![[[self connectionInfo] objectForKey:@"scheme"] isEqualToString:[[self url] scheme]] || [(NSNumber *)[[self connectionInfo] objectForKey:@"port"] intValue] != [[[self url] port] intValue]) {
[self setConnectionInfo:nil];
// Check if we should have expired this connection
} else if ([[[self connectionInfo] objectForKey:@"expires"] timeIntervalSinceNow] < 0) {
#if DEBUG_PERSISTENT_CONNECTIONS
NSLog(@"Not re-using connection #%i because it has expired",[[[self connectionInfo] objectForKey:@"id"] intValue]);
#endif
[persistentConnectionsPool removeObject:[self connectionInfo]];
[self setConnectionInfo:nil];
}
}
if (![self connectionInfo] && [[self url] host] && [[self url] scheme]) { // We must have a proper url with a host and scheme, or this will explode
// Look for a connection to the same server in the pool
for (NSMutableDictionary *existingConnection in persistentConnectionsPool) {
if (![existingConnection objectForKey:@"request"] && [[existingConnection objectForKey:@"host"] isEqualToString:[[self url] host]] && [[existingConnection objectForKey:@"scheme"] isEqualToString:[[self url] scheme]] && [(NSNumber *)[existingConnection objectForKey:@"port"] intValue] == [[[self url] port] intValue]) {
[self setConnectionInfo:existingConnection];
}
}
}
if ([[self connectionInfo] objectForKey:@"stream"]) {
oldStream = [[[self connectionInfo] objectForKey:@"stream"] retain];
}
// No free connection was found in the pool matching the server/scheme/port we're connecting to, we'll need to create a new one
if (![self connectionInfo]) {
[self setConnectionInfo:[NSMutableDictionary dictionary]];
nextConnectionNumberToCreate++;
[[self connectionInfo] setObject:[NSNumber numberWithInt:nextConnectionNumberToCreate] forKey:@"id"];
[[self connectionInfo] setObject:[[self url] host] forKey:@"host"];
[[self connectionInfo] setObject:[NSNumber numberWithInt:[[[self url] port] intValue]] forKey:@"port"];
[[self connectionInfo] setObject:[[self url] scheme] forKey:@"scheme"];
[persistentConnectionsPool addObject:[self connectionInfo]];
}
// If we are retrying this request, it will already have a requestID
if (![self requestID]) {
nextRequestID++;
[self setRequestID:[NSNumber numberWithUnsignedInt:nextRequestID]];
}
[[self connectionInfo] setObject:[self requestID] forKey:@"request"];
[[self connectionInfo] setObject:[self readStream] forKey:@"stream"];
CFReadStreamSetProperty((CFReadStreamRef)[self readStream], kCFStreamPropertyHTTPAttemptPersistentConnection, kCFBooleanTrue);
#if DEBUG_PERSISTENT_CONNECTIONS
NSLog(@"Request #%@ will use connection #%i",[self requestID],[[[self connectionInfo] objectForKey:@"id"] intValue]);
#endif
// Tag the stream with an id that tells it which connection to use behind the scenes
// See http://lists.apple.com/archives/macnetworkprog/2008/Dec/msg00001.html for details on this approach
CFReadStreamSetProperty((CFReadStreamRef)[self readStream], CFSTR("ASIStreamID"), [[self connectionInfo] objectForKey:@"id"]);
}
[connectionsLock unlock];
// Schedule the stream
if (![self readStreamIsScheduled] && (!throttleWakeUpTime || [throttleWakeUpTime timeIntervalSinceDate:[NSDate date]] < 0)) {
[self scheduleReadStream];
}
BOOL streamSuccessfullyOpened = NO;
// Start the HTTP connection
CFStreamClientContext ctxt = {0, self, NULL, NULL, NULL};
if (CFReadStreamSetClient((CFReadStreamRef)[self readStream], kNetworkEvents, ReadStreamClientCallBack, &ctxt)) {
if (CFReadStreamOpen((CFReadStreamRef)[self readStream])) {
streamSuccessfullyOpened = YES;
}
}
// Here, we'll close the stream that was previously using this connection, if there was one
// We've kept it open until now (when we've just opened a new stream) so that the new stream can make use of the old connection
// http://lists.apple.com/archives/Macnetworkprog/2006/Mar/msg00119.html
if (oldStream) {
[oldStream close];
[oldStream release];
oldStream = nil;
}
if (!streamSuccessfullyOpened) {
[self setConnectionCanBeReused:NO];
[self destroyReadStream];
[self failWithError:[NSError errorWithDomain:NetworkRequestErrorDomain code:ASIInternalErrorWhileBuildingRequestType userInfo:[NSDictionary dictionaryWithObjectsAndKeys:@"Unable to start HTTP connection",NSLocalizedDescriptionKey,nil]]];
return;
}
if (![self mainRequest]) {
if ([self shouldResetUploadProgress]) {
if ([self showAccurateProgress]) {
[self incrementUploadSizeBy:[self postLength]];
} else {
[self incrementUploadSizeBy:1];
}
[ASIHTTPRequest updateProgressIndicator:&uploadProgressDelegate withProgress:0 ofTotal:1];
}
if ([self shouldResetDownloadProgress] && ![self partialDownloadSize]) {
[ASIHTTPRequest updateProgressIndicator:&downloadProgressDelegate withProgress:0 ofTotal:1];
}
}
// Record when the request started, so we can timeout if nothing happens
[self setLastActivityTime:[NSDate date]];
[self setStatusTimer:[NSTimer timerWithTimeInterval:0.25 target:self selector:@selector(updateStatus:) userInfo:nil repeats:YES]];
[[NSRunLoop currentRunLoop] addTimer:[self statusTimer] forMode:[self runLoopMode]];
}
更新进度条状态
if (![self mainRequest]) {
if ([self shouldResetUploadProgress]) {
if ([self showAccurateProgress]) {
[self incrementUploadSizeBy:[self postLength]];
} else {
[self incrementUploadSizeBy:1];
}
[ASIHTTPRequest updateProgressIndicator:&uploadProgressDelegate withProgress:0 ofTotal:1];
}
if ([self shouldResetDownloadProgress] && ![self partialDownloadSize]) {
[ASIHTTPRequest updateProgressIndicator:&downloadProgressDelegate withProgress:0 ofTotal:1];
}
}
都包含在if (![self mainRequest]) {}中,为什么呢??? 有个注释如下:
//Only update progress if this isn't a HEAD request used to preset the content-length
没有搞明白??????
if (![self downloadDestinationPath]) {
[self setRawResponseData:[[[NSMutableData alloc] init] autorelease]];
}
设置返回结果的存储变量 [self setRawResponseData:[[[NSMutableData alloc] init] autorelease]];
取得返回结果:
主要是从这个变量里rawResponseData
- (NSData *)responseData
{
if ([self isResponseCompressed] && [self shouldWaitToInflateCompressedResponses]) {
return [ASIDataDecompressor uncompressData:[self rawResponseData] error:NULL];
} else {
return [self rawResponseData];
}
return nil;
}