從Samples中入門IOS開發(五)------ 基於HTTP的網絡編程

上一篇講的是如何通過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的數據傳輸看上去很複雜,實際上道理還是很簡單,重點就在於數據流的拼接上。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章