从Samples中入门IOS开发(五)------ 基于HTTP的网络编程

本文介绍了如何使用HTTP协议通过GET、PUT和POST方法进行文件的下载和上传操作,并详细讲解了客户端与服务器之间的交互过程。



上一篇讲的是如何通过socket进行网络传输,实际上对于互联网上的资源,我们更多的是基于http来开发,SimpleURLConnections展示了如何基于http来进行数据传输,这里主要是讲client如何向http服务器请求和传输数据,http服务器端的实现不在此例子范围之内,实际上就是普通的http服务器。

从本例中主要能学到三点:

  • 基于Get下载文件
  • 基于Put上传文件
  • 基于Post上传文件
基于Get下载文件

首先通过URL打开Connection:

[plain]  view plain  copy
  1. request = [NSURLRequest requestWithURL:url];  
  2. assert(request != nil);  
  3.   
  4. self.connection = [NSURLConnection connectionWithRequest:request delegate:self];  
  5. assert(self.connection != nil);  

然后实现NSURLConnectionDelegate来处理数据传输,其中实现下载图片到本地文件的方法如下:

[plain]  view plain  copy
  1. - (void)connection:(NSURLConnection *)theConnection didReceiveData:(NSData *)data  
  2.     // A delegate method called by the NSURLConnection as data arrives.  We just   
  3.     // write the data to the file.  
  4. {  
  5.     #pragma unused(theConnection)  
  6.     NSInteger       dataLength;  
  7.     const uint8_t * dataBytes;  
  8.     NSInteger       bytesWritten;  
  9.     NSInteger       bytesWrittenSoFar;  
  10.   
  11.     assert(theConnection == self.connection);  
  12.       
  13.     dataLength = [data length];  
  14.     dataBytes  = [data bytes];  
  15.   
  16.     bytesWrittenSoFar = 0;  
  17.     do {  
  18.         bytesWritten = [self.fileStream write:&dataBytes[bytesWrittenSoFar] maxLength:dataLength - bytesWrittenSoFar];  
  19.         assert(bytesWritten != 0);  
  20.         if (bytesWritten == -1) {  
  21.             [self stopReceiveWithStatus:@"File write error"];  
  22.             break;  
  23.         } else {  
  24.             bytesWrittenSoFar += bytesWritten;  
  25.         }  
  26.     } while (bytesWrittenSoFar != dataLength);  
  27. }  

基于Put上传文件

Put和Get类似,只不过文件上传是通过设置HTTP header来完成的:

[plain]  view plain  copy
  1. self.fileStream = [NSInputStream inputStreamWithFileAtPath:filePath];  
  2. assert(self.fileStream != nil);  
  3.   
  4. // Open a connection for the URL, configured to PUT the file.  
  5.   
  6. request = [NSMutableURLRequest requestWithURL:url];  
  7. assert(request != nil);  
  8.   
  9. [request setHTTPMethod:@"PUT"];  
  10. [request setHTTPBodyStream:self.fileStream];  
  11.   
  12. if ( [filePath.pathExtension isEqual:@"png"] ) {  
  13.     [request setValue:@"image/png" forHTTPHeaderField:@"Content-Type"];  
  14. } else if ( [filePath.pathExtension isEqual:@"jpg"] ) {  
  15.     [request setValue:@"image/jpeg" forHTTPHeaderField:@"Content-Type"];  
  16. } else if ( [filePath.pathExtension isEqual:@"gif"] ) {  
  17.     [request setValue:@"image/gif" forHTTPHeaderField:@"Content-Type"];  
  18. } else {  
  19.     assert(NO);  
  20. }  
  21.   
  22. contentLength = (NSNumber *) [[[NSFileManager defaultManager] attributesOfItemAtPath:filePath error:NULL] objectForKey:NSFileSize];  
  23. assert( [contentLength isKindOfClass:[NSNumber class]] );  
  24. [request setValue:[contentLength description] forHTTPHeaderField:@"Content-Length"];  
  25.   
  26. 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里除了放入文件本身的数据流之外,还得在文件数据流前后放入结构性的描述信息,比如之前:

[plain]  view plain  copy
  1. bodyPrefixStr = [NSString stringWithFormat:  
  2.             @  
  3.             // empty preamble  
  4.             "\r\n"  
  5.             "--%@\r\n"  
  6.             "Content-Disposition: form-data; name=\"fileContents\"; filename=\"%@\"\r\n"  
  7.             "Content-Type: %@\r\n"  
  8.             "\r\n"  

之后:

[plain]  view plain  copy
  1. bodySuffixStr = [NSString stringWithFormat:  
  2.             @  
  3.             "\r\n"  
  4.             "--%@\r\n"  
  5.             "Content-Disposition: form-data; name=\"uploadButton\"\r\n"  
  6.             "\r\n"  
  7.             "Upload File\r\n"  
  8.             "--%@--\r\n"   
  9.             "\r\n"  
  10.             //empty epilogue  
  11.             ,  
  12.             boundaryStr,   
  13.             boundaryStr  
  14.         ];  

因此,这里就需要能够把这些数据加入到文件流中,本例采用的方案是生产者和消费者的模式,把这两段信息拼接到文件流中:

[plain]  view plain  copy
  1. [NSStream createBoundInputStream:&consStream outputStream:&prodStream bufferSize:32768];  
  2. assert(consStream != nil);  
  3. assert(prodStream != nil);  
  4. self.consumerStream = consStream;  
  5. self.producerStream = prodStream;  
  6.   
  7. self.producerStream.delegate = self;  
  8. [self.producerStream scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];  
  9. [self.producerStream open];  
  10.   
  11. // Set up our state to send the body prefix first.  
  12.   
  13. self.buffer      = [self.bodyPrefixData bytes];  
  14. self.bufferLimit = [self.bodyPrefixData length];  
  15.   
  16. // Open a connection for the URL, configured to POST the file.  
  17.   
  18. request = [NSMutableURLRequest requestWithURL:url];  
  19. assert(request != nil);  
  20.   
  21. [request setHTTPMethod:@"POST"];  
  22. [request setHTTPBodyStream:self.consumerStream];  
  23.   
  24. [request setValue:[NSString stringWithFormat:@"multipart/form-data; boundary=\"%@\"", boundaryStr] forHTTPHeaderField:@"Content-Type"];  
  25. [request setValue:[NSString stringWithFormat:@"%llu", bodyLength] forHTTPHeaderField:@"Content-Length"];  
  26.   
  27. self.connection = [NSURLConnection connectionWithRequest:request delegate:self];  

然后在handleevent中处理数据流的拼接:

[plain]  view plain  copy
  1. - (void)stream:(NSStream *)aStream handleEvent:(NSStreamEvent)eventCode  
  2.     // An NSStream delegate callback that's called when events happen on our   
  3.     // network stream.  
  4. {  
  5.     #pragma unused(aStream)  
  6.     assert(aStream == self.producerStream);  
  7.   
  8.     switch (eventCode) {  
  9.         case NSStreamEventOpenCompleted: {  
  10.             // NSLog(@"producer stream opened");  
  11.         } break;  
  12.         case NSStreamEventHasBytesAvailable: {  
  13.             assert(NO);     // should never happen for the output stream  
  14.         } break;  
  15.         case NSStreamEventHasSpaceAvailable: {  
  16.             // Check to see if we've run off the end of our buffer.  If we have,   
  17.             // work out the next buffer of data to send.  
  18.               
  19.             if (self.bufferOffset == self.bufferLimit) {  
  20.   
  21.                 // See if we're transitioning from the prefix to the file data.  
  22.                 // If so, allocate a file buffer.  
  23.                   
  24.                 if (self.bodyPrefixData != nil) {  
  25.                     self.bodyPrefixData = nil;  
  26.   
  27.                     assert(self.bufferOnHeap == NULL);  
  28.                     self.bufferOnHeap = malloc(kPostBufferSize);  
  29.                     assert(self.bufferOnHeap != NULL);  
  30.                     self.buffer = self.bufferOnHeap;  
  31.                       
  32.                     self.bufferOffset = 0;  
  33.                     self.bufferLimit  = 0;  
  34.                 }  
  35.                   
  36.                 // If we still have file data to send, read the next chunk.   
  37.                   
  38.                 if (self.fileStream != nil) {  
  39.                     NSInteger   bytesRead;  
  40.                       
  41.                     bytesRead = [self.fileStream read:self.bufferOnHeap maxLength:kPostBufferSize];  
  42.                       
  43.                     if (bytesRead == -1) {  
  44.                         [self stopSendWithStatus:@"File read error"];  
  45.                     } else if (bytesRead != 0) {  
  46.                         self.bufferOffset = 0;  
  47.                         self.bufferLimit  = bytesRead;  
  48.                     } else {  
  49.                         // If we hit the end of the file, transition to sending the   
  50.                         // suffix.  
  51.   
  52.                         [self.fileStream close];  
  53.                         self.fileStream = nil;  
  54.                           
  55.                         assert(self.bufferOnHeap != NULL);  
  56.                         free(self.bufferOnHeap);  
  57.                         self.bufferOnHeap = NULL;  
  58.                         self.buffer       = [self.bodySuffixData bytes];  
  59.   
  60.                         self.bufferOffset = 0;  
  61.                         self.bufferLimit  = [self.bodySuffixData length];  
  62.                     }  
  63.                 }  
  64.                   
  65.                 if ( (self.bufferOffset == self.bufferLimit) && (self.producerStream != nil) ) {  
  66.                     self.producerStream.delegate = nil;  
  67.                     [self.producerStream close];  
  68.                 }  
  69.             }  
  70.               
  71.             // Send the next chunk of data in our buffer.  
  72.               
  73.             if (self.bufferOffset != self.bufferLimit) {  
  74.                 NSInteger   bytesWritten;  
  75.                 bytesWritten = [self.producerStream write:&self.buffer[self.bufferOffset] maxLength:self.bufferLimit - self.bufferOffset];  
  76.                 if (bytesWritten <= 0) {  
  77.                     [self stopSendWithStatus:@"Network write error"];  
  78.                 } else {  
  79.                     self.bufferOffset += bytesWritten;  
  80.                 }  
  81.             }  
  82.         } break;  
  83.         case NSStreamEventErrorOccurred: {  
  84.             NSLog(@"producer stream error %@", [aStream streamError]);  
  85.             [self stopSendWithStatus:@"Stream open error"];  
  86.         } break;  
  87.         case NSStreamEventEndEncountered: {  
  88.             assert(NO);     // should never happen for the output stream  
  89.         } break;  
  90.         default: {  
  91.             assert(NO);  
  92.         } break;  
  93.     }  
  94. }  

基于Post的数据传输看上去很复杂,实际上道理还是很简单,重点就在于数据流的拼接上。

上一篇讲的是如何通过socket进行网络传输,实际上对于互联网上的资源,我们更多的是基于http来开发, SimpleURLConnections展示了如何基于http来进行数据传输,这里主要是讲client如何向http服务器请求和传输数据,http服务器端的实现不在此例子范围之内,实际上就是普通的http服务器。

从本例中主要能学到三点:

  • 基于Get下载文件
  • 基于Put上传文件
  • 基于Post上传文件
基于Get下载文件

首先通过URL打开Connection:

[plain]  view plain  copy
  1. request = [NSURLRequest requestWithURL:url];  
  2. assert(request != nil);  
  3.   
  4. self.connection = [NSURLConnection connectionWithRequest:request delegate:self];  
  5. assert(self.connection != nil);  

然后实现NSURLConnectionDelegate来处理数据传输,其中实现下载图片到本地文件的方法如下:

[plain]  view plain  copy
  1. - (void)connection:(NSURLConnection *)theConnection didReceiveData:(NSData *)data  
  2.     // A delegate method called by the NSURLConnection as data arrives.  We just   
  3.     // write the data to the file.  
  4. {  
  5.     #pragma unused(theConnection)  
  6.     NSInteger       dataLength;  
  7.     const uint8_t * dataBytes;  
  8.     NSInteger       bytesWritten;  
  9.     NSInteger       bytesWrittenSoFar;  
  10.   
  11.     assert(theConnection == self.connection);  
  12.       
  13.     dataLength = [data length];  
  14.     dataBytes  = [data bytes];  
  15.   
  16.     bytesWrittenSoFar = 0;  
  17.     do {  
  18.         bytesWritten = [self.fileStream write:&dataBytes[bytesWrittenSoFar] maxLength:dataLength - bytesWrittenSoFar];  
  19.         assert(bytesWritten != 0);  
  20.         if (bytesWritten == -1) {  
  21.             [self stopReceiveWithStatus:@"File write error"];  
  22.             break;  
  23.         } else {  
  24.             bytesWrittenSoFar += bytesWritten;  
  25.         }  
  26.     } while (bytesWrittenSoFar != dataLength);  
  27. }  

基于Put上传文件

Put和Get类似,只不过文件上传是通过设置HTTP header来完成的:

[plain]  view plain  copy
  1. self.fileStream = [NSInputStream inputStreamWithFileAtPath:filePath];  
  2. assert(self.fileStream != nil);  
  3.   
  4. // Open a connection for the URL, configured to PUT the file.  
  5.   
  6. request = [NSMutableURLRequest requestWithURL:url];  
  7. assert(request != nil);  
  8.   
  9. [request setHTTPMethod:@"PUT"];  
  10. [request setHTTPBodyStream:self.fileStream];  
  11.   
  12. if ( [filePath.pathExtension isEqual:@"png"] ) {  
  13.     [request setValue:@"image/png" forHTTPHeaderField:@"Content-Type"];  
  14. } else if ( [filePath.pathExtension isEqual:@"jpg"] ) {  
  15.     [request setValue:@"image/jpeg" forHTTPHeaderField:@"Content-Type"];  
  16. } else if ( [filePath.pathExtension isEqual:@"gif"] ) {  
  17.     [request setValue:@"image/gif" forHTTPHeaderField:@"Content-Type"];  
  18. } else {  
  19.     assert(NO);  
  20. }  
  21.   
  22. contentLength = (NSNumber *) [[[NSFileManager defaultManager] attributesOfItemAtPath:filePath error:NULL] objectForKey:NSFileSize];  
  23. assert( [contentLength isKindOfClass:[NSNumber class]] );  
  24. [request setValue:[contentLength description] forHTTPHeaderField:@"Content-Length"];  
  25.   
  26. 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里除了放入文件本身的数据流之外,还得在文件数据流前后放入结构性的描述信息,比如之前:

[plain]  view plain  copy
  1. bodyPrefixStr = [NSString stringWithFormat:  
  2.             @  
  3.             // empty preamble  
  4.             "\r\n"  
  5.             "--%@\r\n"  
  6.             "Content-Disposition: form-data; name=\"fileContents\"; filename=\"%@\"\r\n"  
  7.             "Content-Type: %@\r\n"  
  8.             "\r\n"  

之后:

[plain]  view plain  copy
  1. bodySuffixStr = [NSString stringWithFormat:  
  2.             @  
  3.             "\r\n"  
  4.             "--%@\r\n"  
  5.             "Content-Disposition: form-data; name=\"uploadButton\"\r\n"  
  6.             "\r\n"  
  7.             "Upload File\r\n"  
  8.             "--%@--\r\n"   
  9.             "\r\n"  
  10.             //empty epilogue  
  11.             ,  
  12.             boundaryStr,   
  13.             boundaryStr  
  14.         ];  

因此,这里就需要能够把这些数据加入到文件流中,本例采用的方案是生产者和消费者的模式,把这两段信息拼接到文件流中:

[plain]  view plain  copy
  1. [NSStream createBoundInputStream:&consStream outputStream:&prodStream bufferSize:32768];  
  2. assert(consStream != nil);  
  3. assert(prodStream != nil);  
  4. self.consumerStream = consStream;  
  5. self.producerStream = prodStream;  
  6.   
  7. self.producerStream.delegate = self;  
  8. [self.producerStream scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];  
  9. [self.producerStream open];  
  10.   
  11. // Set up our state to send the body prefix first.  
  12.   
  13. self.buffer      = [self.bodyPrefixData bytes];  
  14. self.bufferLimit = [self.bodyPrefixData length];  
  15.   
  16. // Open a connection for the URL, configured to POST the file.  
  17.   
  18. request = [NSMutableURLRequest requestWithURL:url];  
  19. assert(request != nil);  
  20.   
  21. [request setHTTPMethod:@"POST"];  
  22. [request setHTTPBodyStream:self.consumerStream];  
  23.   
  24. [request setValue:[NSString stringWithFormat:@"multipart/form-data; boundary=\"%@\"", boundaryStr] forHTTPHeaderField:@"Content-Type"];  
  25. [request setValue:[NSString stringWithFormat:@"%llu", bodyLength] forHTTPHeaderField:@"Content-Length"];  
  26.   
  27. self.connection = [NSURLConnection connectionWithRequest:request delegate:self];  

然后在handleevent中处理数据流的拼接:

[plain]  view plain  copy
  1. - (void)stream:(NSStream *)aStream handleEvent:(NSStreamEvent)eventCode  
  2.     // An NSStream delegate callback that's called when events happen on our   
  3.     // network stream.  
  4. {  
  5.     #pragma unused(aStream)  
  6.     assert(aStream == self.producerStream);  
  7.   
  8.     switch (eventCode) {  
  9.         case NSStreamEventOpenCompleted: {  
  10.             // NSLog(@"producer stream opened");  
  11.         } break;  
  12.         case NSStreamEventHasBytesAvailable: {  
  13.             assert(NO);     // should never happen for the output stream  
  14.         } break;  
  15.         case NSStreamEventHasSpaceAvailable: {  
  16.             // Check to see if we've run off the end of our buffer.  If we have,   
  17.             // work out the next buffer of data to send.  
  18.               
  19.             if (self.bufferOffset == self.bufferLimit) {  
  20.   
  21.                 // See if we're transitioning from the prefix to the file data.  
  22.                 // If so, allocate a file buffer.  
  23.                   
  24.                 if (self.bodyPrefixData != nil) {  
  25.                     self.bodyPrefixData = nil;  
  26.   
  27.                     assert(self.bufferOnHeap == NULL);  
  28.                     self.bufferOnHeap = malloc(kPostBufferSize);  
  29.                     assert(self.bufferOnHeap != NULL);  
  30.                     self.buffer = self.bufferOnHeap;  
  31.                       
  32.                     self.bufferOffset = 0;  
  33.                     self.bufferLimit  = 0;  
  34.                 }  
  35.                   
  36.                 // If we still have file data to send, read the next chunk.   
  37.                   
  38.                 if (self.fileStream != nil) {  
  39.                     NSInteger   bytesRead;  
  40.                       
  41.                     bytesRead = [self.fileStream read:self.bufferOnHeap maxLength:kPostBufferSize];  
  42.                       
  43.                     if (bytesRead == -1) {  
  44.                         [self stopSendWithStatus:@"File read error"];  
  45.                     } else if (bytesRead != 0) {  
  46.                         self.bufferOffset = 0;  
  47.                         self.bufferLimit  = bytesRead;  
  48.                     } else {  
  49.                         // If we hit the end of the file, transition to sending the   
  50.                         // suffix.  
  51.   
  52.                         [self.fileStream close];  
  53.                         self.fileStream = nil;  
  54.                           
  55.                         assert(self.bufferOnHeap != NULL);  
  56.                         free(self.bufferOnHeap);  
  57.                         self.bufferOnHeap = NULL;  
  58.                         self.buffer       = [self.bodySuffixData bytes];  
  59.   
  60.                         self.bufferOffset = 0;  
  61.                         self.bufferLimit  = [self.bodySuffixData length];  
  62.                     }  
  63.                 }  
  64.                   
  65.                 if ( (self.bufferOffset == self.bufferLimit) && (self.producerStream != nil) ) {  
  66.                     self.producerStream.delegate = nil;  
  67.                     [self.producerStream close];  
  68.                 }  
  69.             }  
  70.               
  71.             // Send the next chunk of data in our buffer.  
  72.               
  73.             if (self.bufferOffset != self.bufferLimit) {  
  74.                 NSInteger   bytesWritten;  
  75.                 bytesWritten = [self.producerStream write:&self.buffer[self.bufferOffset] maxLength:self.bufferLimit - self.bufferOffset];  
  76.                 if (bytesWritten <= 0) {  
  77.                     [self stopSendWithStatus:@"Network write error"];  
  78.                 } else {  
  79.                     self.bufferOffset += bytesWritten;  
  80.                 }  
  81.             }  
  82.         } break;  
  83.         case NSStreamEventErrorOccurred: {  
  84.             NSLog(@"producer stream error %@", [aStream streamError]);  
  85.             [self stopSendWithStatus:@"Stream open error"];  
  86.         } break;  
  87.         case NSStreamEventEndEncountered: {  
  88.             assert(NO);     // should never happen for the output stream  
  89.         } break;  
  90.         default: {  
  91.             assert(NO);  
  92.         } break;  
  93.     }  
  94. }  

基于Post的数据传输看上去很复杂,实际上道理还是很简单,重点就在于数据流的拼接上。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值