环境: 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
我已经创建了带有NSURLSessionConfiguration
的NSURLSession
并设置了标题,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 通过CFHTTPMessageRef
和CFHTTPMessageSetHeaderFieldValue
创建和自定义请求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"