环境: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 同样的问题。
答案 0 :(得分:3)
要以低级别最佳方式管理HTTP标头,请使用 CFNetwork 级别。但是不要使用CFHTTPStream来发送/接收数据,因为CFHTTPStream只支持两个“连接”标题状态:“保持活跃”和“关闭”。 />请参阅文件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
答案 1 :(得分:0)
一方面
@"Connection": @"Upgrage"
应该是
@"Connection": @"Upgrade"