上一篇讲的是如何通过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的数据传输看上去很复杂,实际上道理还是很简单,重点就在于数据流的拼接上。
从本例中主要能学到三点:
- 基于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的数据传输看上去很复杂,实际上道理还是很简单,重点就在于数据流的拼接上。