上一篇講的是如何通過socket進行網路傳輸,實際上對於互連網上的資源,我們更多的是基於http來開發,SimpleURLConnections展示了如何基於http來進行資料轉送,這裡主要是講client如何向http伺服器請求和傳輸資料,http伺服器端的實現不在此例子範圍之內,實際上就是普通的http伺服器。
從本例中主要能學到三點:
- 基於Get下載檔案
- 基於Put上傳檔案
- 基於Post上傳檔案
基於Get下載檔案
首先通過URL開啟Connection:
request = [NSURLRequest requestWithURL:url]; assert(request != nil); self.connection = [NSURLConnection connectionWithRequest:request delegate:self]; assert(self.connection != nil);
然後實現NSURLConnectionDelegate來處理資料轉送,其中實現下載圖片到本地檔案的方法如下:
- (void)connection:(NSURLConnection *)theConnection didReceiveData:(NSData *)data // A delegate method called by the NSURLConnection as data arrives. We just // write the data to the file.{ #pragma unused(theConnection) NSInteger dataLength; const uint8_t * dataBytes; NSInteger bytesWritten; NSInteger bytesWrittenSoFar; assert(theConnection == self.connection); dataLength = [data length]; dataBytes = [data bytes]; bytesWrittenSoFar = 0; do { bytesWritten = [self.fileStream write:&dataBytes[bytesWrittenSoFar] maxLength:dataLength - bytesWrittenSoFar]; assert(bytesWritten != 0); if (bytesWritten == -1) { [self stopReceiveWithStatus:@"File write error"]; break; } else { bytesWrittenSoFar += bytesWritten; } } while (bytesWrittenSoFar != dataLength);}
基於Put上傳檔案
Put和Get類似,只不過檔案上傳是通過設定HTTP header來完成的:
self.fileStream = [NSInputStream inputStreamWithFileAtPath:filePath];assert(self.fileStream != nil);// Open a connection for the URL, configured to PUT the file.request = [NSMutableURLRequest requestWithURL:url];assert(request != nil);[request setHTTPMethod:@"PUT"];[request setHTTPBodyStream:self.fileStream];if ( [filePath.pathExtension isEqual:@"png"] ) { [request setValue:@"image/png" forHTTPHeaderField:@"Content-Type"];} else if ( [filePath.pathExtension isEqual:@"jpg"] ) { [request setValue:@"image/jpeg" forHTTPHeaderField:@"Content-Type"];} else if ( [filePath.pathExtension isEqual:@"gif"] ) { [request setValue:@"image/gif" forHTTPHeaderField:@"Content-Type"];} else { assert(NO);}contentLength = (NSNumber *) [[[NSFileManager defaultManager] attributesOfItemAtPath:filePath error:NULL] objectForKey:NSFileSize];assert( [contentLength isKindOfClass:[NSNumber class]] );[request setValue:[contentLength description] forHTTPHeaderField:@"Content-Length"];self.connection = [NSURLConnection connectionWithRequest:request delegate:self];
其中最主要的是設定三個header屬性:method, body和contentLength
[request setHTTPMethod:@"PUT"];
[request setHTTPBodyStream:self.fileStream];
[request setValue:[contentLength description] forHTTPHeaderField:@"Content-Length"];
基於Post上傳檔案
基於Post上傳檔案與Put最大不同點是,http body裡除了放入檔案本身的資料流之外,還得在檔案資料流前後放入結構性的描述資訊,比如之前:
bodyPrefixStr = [NSString stringWithFormat: @ // empty preamble "\r\n" "--%@\r\n" "Content-Disposition: form-data; name=\"fileContents\"; filename=\"%@\"\r\n" "Content-Type: %@\r\n" "\r\n"
之後:
bodySuffixStr = [NSString stringWithFormat: @ "\r\n" "--%@\r\n" "Content-Disposition: form-data; name=\"uploadButton\"\r\n" "\r\n" "Upload File\r\n" "--%@--\r\n" "\r\n" //empty epilogue , boundaryStr, boundaryStr ];
因此,這裡就需要能夠把這些資料加入到檔案流中,本例採用的方案是生產者和消費者的模式,把這兩段資訊拼接到檔案流中:
[NSStream createBoundInputStream:&consStream outputStream:&prodStream bufferSize:32768];assert(consStream != nil);assert(prodStream != nil);self.consumerStream = consStream;self.producerStream = prodStream;self.producerStream.delegate = self;[self.producerStream scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];[self.producerStream open];// Set up our state to send the body prefix first.self.buffer = [self.bodyPrefixData bytes];self.bufferLimit = [self.bodyPrefixData length];// Open a connection for the URL, configured to POST the file.request = [NSMutableURLRequest requestWithURL:url];assert(request != nil);[request setHTTPMethod:@"POST"];[request setHTTPBodyStream:self.consumerStream];[request setValue:[NSString stringWithFormat:@"multipart/form-data; boundary=\"%@\"", boundaryStr] forHTTPHeaderField:@"Content-Type"];[request setValue:[NSString stringWithFormat:@"%llu", bodyLength] forHTTPHeaderField:@"Content-Length"];self.connection = [NSURLConnection connectionWithRequest:request delegate:self];
然後在handleevent中處理資料流的拼接:
- (void)stream:(NSStream *)aStream handleEvent:(NSStreamEvent)eventCode // An NSStream delegate callback that's called when events happen on our // network stream.{ #pragma unused(aStream) assert(aStream == self.producerStream); switch (eventCode) { case NSStreamEventOpenCompleted: { // NSLog(@"producer stream opened"); } break; case NSStreamEventHasBytesAvailable: { assert(NO); // should never happen for the output stream } break; case NSStreamEventHasSpaceAvailable: { // Check to see if we've run off the end of our buffer. If we have, // work out the next buffer of data to send. if (self.bufferOffset == self.bufferLimit) { // See if we're transitioning from the prefix to the file data. // If so, allocate a file buffer. if (self.bodyPrefixData != nil) { self.bodyPrefixData = nil; assert(self.bufferOnHeap == NULL); self.bufferOnHeap = malloc(kPostBufferSize); assert(self.bufferOnHeap != NULL); self.buffer = self.bufferOnHeap; self.bufferOffset = 0; self.bufferLimit = 0; } // If we still have file data to send, read the next chunk. if (self.fileStream != nil) { NSInteger bytesRead; bytesRead = [self.fileStream read:self.bufferOnHeap maxLength:kPostBufferSize]; if (bytesRead == -1) { [self stopSendWithStatus:@"File read error"]; } else if (bytesRead != 0) { self.bufferOffset = 0; self.bufferLimit = bytesRead; } else { // If we hit the end of the file, transition to sending the // suffix. [self.fileStream close]; self.fileStream = nil; assert(self.bufferOnHeap != NULL); free(self.bufferOnHeap); self.bufferOnHeap = NULL; self.buffer = [self.bodySuffixData bytes]; self.bufferOffset = 0; self.bufferLimit = [self.bodySuffixData length]; } } if ( (self.bufferOffset == self.bufferLimit) && (self.producerStream != nil) ) { self.producerStream.delegate = nil; [self.producerStream close]; } } // Send the next chunk of data in our buffer. if (self.bufferOffset != self.bufferLimit) { NSInteger bytesWritten; bytesWritten = [self.producerStream write:&self.buffer[self.bufferOffset] maxLength:self.bufferLimit - self.bufferOffset]; if (bytesWritten <= 0) { [self stopSendWithStatus:@"Network write error"]; } else { self.bufferOffset += bytesWritten; } } } break; case NSStreamEventErrorOccurred: { NSLog(@"producer stream error %@", [aStream streamError]); [self stopSendWithStatus:@"Stream open error"]; } break; case NSStreamEventEndEncountered: { assert(NO); // should never happen for the output stream } break; default: { assert(NO); } break; }}
基於Post的資料轉送看上去很複雜,實際上道理還是很簡單,重點就在於資料流的拼接上。