NSURLSession 将标头'Connection'设置为'Upgrade'不起作用



环境: Mac OS X 10.9, Xcode 5.0.2

我需要为我的迷你 WebSocket 客户端创建一个 HTTP 请求,示例请求:

GET / HTTP/1.1
Host: serverwebsocket.com:10080
Upgrade: websocket
Connection: Upgrade
Origin: http://from.com

我已经创建了带有NSURLSessionConfigurationNSURLSession并设置了标题,Wireshark 显示除Connection之外的所有标头集,它保持keep-alive,但不应该。

// Create request based on Sessions
// Create sesson configuretion
NSURLSessionConfiguration* sessionConf = [NSURLSessionConfiguration defaultSessionConfiguration];
// Configure session config
// set header value, detail header websocket on http://learn.javascript.ru/websockets
sessionConf.HTTPAdditionalHeaders = @{@"Upgrade": @"websocket",
                                      @"Connection": @"Upgrage",
                                      @"Origin": @"http://from.com",
                                      @"User-Agent": @"Chrome/36.0.198.5.143"};
// Declare handler block of response
__block void (^handler)(NSData* data, NSURLResponse* response, NSError* error);
handler = ^(NSData* data, NSURLResponse* response, NSError* error)
{
    // If receive response from server
    if(data)
    {
        NSString* result = [[[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding] autorelease];
        NSLog(@"response data: %@", result);
    }
    else // something wrong
    {
        NSString* errorText;
        if(error)
        {
            errorText = [error localizedDescription];
        }
        else // Generic description
        {
            errorText = @"Error Interner connection";
        }
        NSLog(@"Request error: %@",errorText );
    }
};

// Create Session
NSURLSession* session = [NSURLSession sessionWithConfiguration:sessionConf];
NSURL* url = [NSURL URLWithString: [_textUrl stringValue]];
[[session dataTaskWithURL:url completionHandler:handler] resume];

如何更改Connection的标题?而且我不想使用另一个 websocket 库,我想在低级别处理 HTTP 标头。

可能,除了 NSURLSession,还有其他类与网络一起工作吗?在NSURLRequest中,同样的问题。

对于以低级别管理HTTP标头的最佳方法,请使用CFNetwork级别。但不要使用CFHTTPStream发送/接收数据,因为CFHTTPStream仅支持"连接"标头的两种状态:">keep-alive"和">close"。
请参阅文件中 https://opensource.apple.com/source/CFNetwork/CFNetwork-128/HTTP/CFHTTPStream.c
函数" extern void cleanUpRequest() "。

溶液:
1 通过CFHTTPMessageRefCFHTTPMessageSetHeaderFieldValue
创建和自定义请求2 将请求转换为原始数据
3 发送/接收原始数据使用 NSOutputStream 和 NSInputStream

这是在CFNetwork级别发送/接收HTTP WebSocket消息的最小示例:

#import "AppDelegate.h"
@implementation AppDelegate
NSInputStream* inputStream;
NSOutputStream* outputStream;
NSMutableData* inputBuffer;     // This data receive from server
NSMutableData* outputBuffer;    // This data send to server
- (IBAction)btnSend:(id)sender
{
    NSURL* url = [NSURL URLWithString: [_textUrl stringValue]];
    if( !outputBuffer)
    {
        outputBuffer = [[NSMutableData alloc] init];
    }
    ///////////////////////////////////////////////////////////////
    /////////////// Create GET request uses CFNetwork level
    CFHTTPMessageRef request = CFHTTPMessageCreateRequest(kCFAllocatorDefault, CFSTR("GET"), (__bridge CFURLRef)url, kCFHTTPVersion1_1);
    // Set Host header
    CFHTTPMessageSetHeaderFieldValue(request, CFSTR("Host"), (__bridge CFStringRef)(url.port ? [NSString stringWithFormat:@"%@:%@", url.host, url.port] : url.host));
    CFHTTPMessageSetHeaderFieldValue(request, CFSTR("User-Agent"), CFSTR("Chrome/36.0.198.5.143"));
    CFHTTPMessageSetHeaderFieldValue(request, CFSTR("Pragma"), CFSTR("no-cache"));
    CFHTTPMessageSetHeaderFieldValue(request, CFSTR("Cache-Control"), CFSTR("no-cache"));
    // Set special headers for websocket, detail on http://learn.javascript.ru/websockets
    CFHTTPMessageSetHeaderFieldValue(request, CFSTR("Upgrade"), CFSTR("websocket"));
    CFHTTPMessageSetHeaderFieldValue(request, CFSTR("Connection"), CFSTR("Upgrade"));
    NSString* origin = @"http://from.com";
    CFHTTPMessageSetHeaderFieldValue(request, CFSTR("Origin"), (__bridge CFStringRef)origin);
    CFHTTPMessageSetHeaderFieldValue(request, CFSTR("Sec-WebSocket-Protocol"), CFSTR("SIP"));
    CFHTTPMessageSetHeaderFieldValue(request, CFSTR("Sec-WebSocket-Key"), CFSTR("yuPCDHanXBphfIH83e4JVw=="));
    CFHTTPMessageSetHeaderFieldValue(request, CFSTR("Sec-WebSocket-Version"), CFSTR("13"));
    CFHTTPMessageSetHeaderFieldValue(request, CFSTR("Sec-WebSocket-Extensions"), CFSTR("permessage-deflate; client_max_window_bits, x-webkit-deflate-frame"));
    // Convert request to raw data
    NSData* rawHttpMessage = CFBridgingRelease(CFHTTPMessageCopySerializedMessage(request));
    [outputBuffer appendData:rawHttpMessage];
    CFRelease(request);
    /////////////////////////////////////////////////////////////////
    /////////////// Customize stream for sending/receive data
    CFReadStreamRef readStream;
    CFWriteStreamRef writeStream;
    CFStreamCreatePairWithSocketToHost(NULL, (__bridge CFStringRef)url.host, [url.port intValue],
                                       &readStream, &writeStream);
    inputStream = (__bridge_transfer NSInputStream*)readStream;
    outputStream = (__bridge_transfer NSOutputStream*)writeStream;
    [inputStream setDelegate:self];     // Activate stream event handler
    [outputStream setDelegate:self];    // Activate stream event handler
    [inputStream scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
    [outputStream scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
    [inputStream open];
    [outputStream open];
    [inputStream retain];
    [outputStream retain];
}
// Handler of event for NSInputStream and NSOutputStream
// Detail see: https://developer.apple.com/library/ios/documentation/Cocoa/Conceptual/Streams/Articles/NetworkStreams.html
-(void)stream:(NSStream*)stream handleEvent:(NSStreamEvent)eventCode
{
    switch (eventCode)
    {
        case NSStreamEventHasSpaceAvailable:    // when outputstream can send data
            if( stream == outputStream)
            {
                //NSLog(@"Send data [%lu]", [outputBuffer length]);
                [outputStream write:[outputBuffer bytes] maxLength:[outputBuffer length]]; // Send data
                // Close output stream when all data sent
                [outputStream close];
                [outputStream removeFromRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
                [outputStream release];
                outputStream = nil;
                [outputBuffer release];
                outputBuffer = nil;
            }
        break;
        case NSStreamEventHasBytesAvailable:    // when inputstream received data
        {
            const int bufSize = 2048;
            if(!inputBuffer)
            {
                inputBuffer = [[NSMutableData data] retain];
            }
            uint8_t buf[bufSize];
            long len = 0;
            len = [inputStream read:buf maxLength:bufSize]; // get data
            if(len)
            {
                [inputBuffer appendBytes:(const void*)buf length:len];
                //NSLog(@"Received data from server [%lu]: %@", [inputBuffer length], inputBuffer); // Show in raw format
                NSString* rs = [NSString stringWithUTF8String:[inputBuffer bytes]];
                NSLog(@"Received data from server [%lu]:n%@", [inputBuffer length], rs); // Show in string format
            }
            else
            {
                NSLog(@"Received data Error[%li]: %@",(long)[inputStream.streamError code], [inputStream.streamError localizedDescription]);
            }
            // Close inputStream stream when all data receive
            [inputStream close];
            [inputStream removeFromRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
            [inputStream release];
            inputStream = nil;
            [inputBuffer release];
            inputBuffer = nil;
        }
        break;
        case NSStreamEventErrorOccurred:    // when error of transmited data
            if( stream == outputStream)
            {
                NSError* error = [stream streamError];
                NSLog(@"Error sending data [%li]: %@",(long)[error code], [error localizedDescription]);
            }
            else if( stream == inputStream)
            {
                NSError* error = [stream streamError];
                NSLog(@"Error receive data [%li]: %@",(long)[error code], [error localizedDescription]);
            }
        break;
        case NSStreamEventEndEncountered:   // this is not work ;)
            if( stream == outputStream)
            {
                NSLog(@"outputStream End");
            }
            else if( stream == inputStream)
            {
                NSLog(@"inputputStream End");
            }
        break;
    }
}
@end

此示例发送 WebSocket 请求并接收 WeSocket 响应:

对服务器的 WebSocket 请求:

GET / HTTP/1.1
Host: serverwebsocket.com:10080
Upgrade: websocket
Connection: Upgrade
Origin: http://from.com
Sec-WebSocket-Protocol:SIP
Sec-WebSocket-Key: yuPCDHanXBphfIH83e4JVw==
Sec-WebSocket-Version: 13

来自服务器的 WebSocket 响应:

HTTP/1.1 101 Switching Protocols
Content-Length: 0
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: mEgcu0WkPuU6yMRtyUl/C+X8zJE=
Sec-WebSocket-Protocol: sip
Sec-WebSocket-Version: 13

一方面 @"Connection": @"Upgrage"

应该是

@"Connection": @"Upgrade"

相关内容

  • 没有找到相关文章

最新更新