从Samples中入门IOS开发(5)- 基于HTTP的网络编程
从Samples中入门IOS开发(五)------ 基于HTTP的网络编程
然后实现NSURLConnectionDelegate来处理数据传输,其中实现下载图片到本地文件的方法如下:
基于Put上传文件
其中最主要的是设置三个header属性:method, body和contentLength
[request setHTTPMethod:@"PUT"];
[request setHTTPBodyStream:self.fileStream];
[request setValue:[contentLength description] forHTTPHeaderField:@"Content-Length"];
之后:
因此,这里就需要能够把这些数据加入到文件流中,本例采用的方案是生产者和消费者的模式,把这两段信息拼接到文件流中:
然后在handleevent中处理数据流的拼接:
基于Post的数据传输看上去很复杂,实际上道理还是很简单,重点就在于数据流的拼接上。
上一篇讲的是如何通过socket进行网络传输,实际上对于互联网上的资源,我们更多的是基于http来开发,SimpleURLConnections展示了如何基于http来进行数据传输,这里主要是讲client如何向http服务器请求和传输数据,http服务器端的实现不在此例子范围之内,实际上就是普通的http服务器。
从本例中主要能学到三点:
- 基于Get下载文件
- 基于Put上传文件
- 基于Post上传文件
首先通过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的数据传输看上去很复杂,实际上道理还是很简单,重点就在于数据流的拼接上。