NSURLSession设置标题'连接'到'升级'不行

时间:2014-09-05 14:05:01

标签: ios objective-c cocoa websocket http-headers

环境: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

我已使用NSURLSession创建了NSURLSessionConfiguration并设置了标题,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头。

5月,还有其他类与网络一起工作,而不是 NSURLSession 吗?在 NSURLRequest 同样的问题。

2 个答案:

答案 0 :(得分:3)

要以低级别最佳方式管理HTTP标头,请使用 CFNetwork 级别。但是不要使用CFHTTPStream来发送/接收数据,因为CFHTTPStream只支持两个“连接”标题状态:“保持活跃”和“关闭”。 />请参阅文件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

答案 1 :(得分:0)

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

应该是

@"Connection": @"Upgrade"