`

asihttp 源码分析 之三 startRequest

阅读更多

首先补充点东西。

 

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;
}

 

分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics