Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
tree: c13bf8c14b
Fetching contributors…

Cannot retrieve contributors at this time

253 lines (203 sloc) 8.332 kb
//
// PKWebSocketHandler.m
// PKWebSocket
//
// Created by Pavel Kunc on 16/04/2012.
// Copyright (c) 2012 Pavel Kunc. All rights reserved.
//
#import "PKWebSocketHandler.h"
#import "AsyncSocket.h"
#import "PKWebSocketURL.h"
#import "PKHTTPResponse.h"
#import "PKHTTPRequest.h"
#import "PKWebSocketFrameParser.h"
#import "PKWebSocketMessage.h"
NSString * const HTTP_GET_METHOD = @"GET";
NSString * const HTTP_RESPONSE_BOUNDARY = @"\r\n\r\n";
NSString * const HTTP_UPGRADE_HEADER = @"Upgrade";
NSString * const HTTP_CONNECTION_HEADER = @"Connection";
const NSUInteger HTTP_UPGRADE_STATUS_CODE = 101;
@interface PKWebSocketHandler ()
@property (nonatomic, strong, readonly) AsyncSocket *socket;
@property (nonatomic, strong, readonly) NSArray *runLoopModes;
@end
@implementation PKWebSocketHandler
// Public
@synthesize parser = _parser;
@synthesize timeout = _timeout;
@synthesize tlsSettings = _tlsSettings;
@synthesize status = _status;
@synthesize url = _url;
@synthesize delegate = _websocket;
// Private
@synthesize socket = _socket;
@synthesize runLoopModes = _runLoopModes;
- (id)init {
self = [super init];
if (self != nil) {
self.parser = nil;
self->_status = PKWebSocketDisconnectedStatus;
self->_runLoopModes = [NSArray arrayWithObject:NSRunLoopCommonModes];
self.timeout = 5;
self.tlsSettings = nil;
self->_socket = [[AsyncSocket alloc] initWithDelegate:self];
}
return self;
}
- (void)setStatus:(PKWebSocketStatus)aStatus {
if (self->_status == aStatus) return;
self->_status = aStatus;
[self.delegate handler:self didChangeStatus:self->_status];
}
#pragma mark - Socket management
- (BOOL)connectWithURL:(PKWebSocketURL *)anURL error:(NSError **)outError {
if (self.status != PKWebSocketDisconnectedStatus) return YES;
self.status = PKWebSocketConnectingStatus;
if ([anURL isSecure] && self.tlsSettings != nil) {
[self.socket startTLS:self.tlsSettings];
}
BOOL status = [self.socket connectToHost:anURL.host
onPort:[anURL.port intValue]
withTimeout:self.timeout
error:outError];
if (status) {
self->_url = anURL;
if (self.runLoopModes) {
[self.socket setRunLoopModes:self.runLoopModes];
}
return YES;
} else {
self.status = PKWebSocketDisconnectedStatus;
return NO;
}
}
- (void)disconnect {
self.status = PKWebSocketClosingStatus;
[self parserNeedsInitiateClosingHandshakeWithData:self.parser.closingPayload];
[self.socket disconnectAfterReadingAndWriting];
}
- (void)sendBinaryFrameWithData:(NSData *)aData {
NSData *payload = [self.parser binaryFramePayloadWithData:aData];
[self sendData:payload tag:PKWebSocketTagMessage];
}
- (void)sendTextFrameWithString:(NSString *)aString {
NSData *payload = [self.parser textFramePayloadWithString:aString];
[self sendData:payload tag:PKWebSocketTagMessage];
}
- (void)sendData:(NSData *)aData tag:(long)aTag {
[self.socket writeData:aData withTimeout:self.timeout tag:aTag];
}
#pragma mark - Handshake frames
- (BOOL)validateHandshakeResponse:(PKHTTPResponse *)aResponse
error:(NSError **)outError {
BOOL valid = aResponse.statusCode == HTTP_UPGRADE_STATUS_CODE;
if (!valid) return NO;
valid = [aResponse valueForHTTPHeaderField:HTTP_UPGRADE_HEADER] != nil;
if (!valid) return NO;
valid = [aResponse valueForHTTPHeaderField:HTTP_CONNECTION_HEADER] != nil;
return valid;
}
- (PKHTTPRequest *)handshakeRequestWithURL:(PKWebSocketURL *)anURL
error:(NSError **)outError {
PKHTTPRequest *request = [[PKHTTPRequest alloc] initWithMethod:HTTP_GET_METHOD
URL:anURL];
[request addValue:@"WebSocket" forHTTPHeaderField:HTTP_UPGRADE_HEADER];
[request addValue:@"Upgrade" forHTTPHeaderField:HTTP_CONNECTION_HEADER];
NSString *host = [anURL hostForHTTPHeader];
[request addValue:host forHTTPHeaderField:@"Host"];
NSString *origin = [NSString stringWithFormat:@"http://%@", host];
[request addValue:origin forHTTPHeaderField:@"Origin"];
return request;
}
- (NSData *)handshakeResponseFrameBoundary {
return [HTTP_RESPONSE_BOUNDARY dataUsingEncoding:NSASCIIStringEncoding];
}
#pragma mark AsyncSocket delegate methods
- (BOOL)onSocketWillConnect:(AsyncSocket *)sock {
if (self.url.isSecure && self.tlsSettings != nil) {
CFReadStreamSetProperty([sock getCFReadStream],
kCFStreamPropertySSLSettings, (__bridge CFDictionaryRef)self.tlsSettings);
CFWriteStreamSetProperty([sock getCFWriteStream],
kCFStreamPropertySSLSettings, (__bridge CFDictionaryRef)self.tlsSettings);
}
return YES;
}
- (void)onSocket:(AsyncSocket *)sock didConnectToHost:(NSString *)host port:(UInt16)port {
self.status = PKWebSocketOpeningStatus;
PKHTTPRequest *request = [self handshakeRequestWithURL:self.url error:NULL];
[self sendData:[request asData] tag:PKWebSocketTagOpeningHandshake];
}
- (void)onSocket:(AsyncSocket *)sock willDisconnectWithError:(NSError *)outError {
if (self.status == PKWebSocketOpenedStatus) {
self.status = PKWebSocketClosedStatus;
} else {
NSLog(@"");
//[self.delegate _dispatchFailure:[NSNumber numberWithInt:1]];
}
}
- (void)onSocketDidDisconnect:(AsyncSocket *)sock {
self.status = PKWebSocketDisconnectedStatus;
}
- (void)onSocket:(AsyncSocket *)sock didWriteDataWithTag:(long)tag {
if (tag == PKWebSocketTagOpeningHandshake) {
[sock readDataToData:[self handshakeResponseFrameBoundary]
withTimeout:self.timeout
tag:PKWebSocketTagOpeningHandshake];
} else if (tag == PKWebSocketTagMessage) {
[self.delegate handlerDidSendMessage];
} else {
// Closing handshake probably
}
}
- (void)onSocket:(AsyncSocket *)sock didReadData:(NSData *)data withTag:(long)tag {
if (tag == PKWebSocketTagOpeningHandshake) {
PKHTTPResponse *response = [[PKHTTPResponse alloc] initWithData:data error:NULL];
if ([self validateHandshakeResponse:response error:NULL]) {
self.status = PKWebSocketOpenedStatus;
[sock readDataWithTimeout:-1 tag:PKWebSocketTagMessage];
} else {
// Handshake error
[self.delegate handler:self didFailWithError:nil];
[self disconnect];
}
} else if (tag == PKWebSocketTagMessage) {
[self.parser processData:data];
[sock readDataWithTimeout:-1 tag:PKWebSocketTagMessage];
} else if (tag == PKWebSocketTagClosingHandshake) {
// Closing handshake
} else {
NSAssert(nil, @"Unknown tag (%d) received!", tag);
}
}
#pragma mark - Frame delegate
- (void)parserNeedsReadDataToData:(NSData *)boundary {
[self.socket readDataToData:boundary
withTimeout:-1
tag:PKWebSocketTagMessage];
}
- (void)parserNeedsReadDataOfLength:(NSUInteger)length {
[self.socket readDataToLength:length
withTimeout:-1
tag:PKWebSocketTagMessage];
}
- (void)parserNeedsInitiateClosingHandshakeWithData:(NSData *)data {
if (data == nil && [data length] == 0) return;
[self sendData:data tag:PKWebSocketTagClosingHandshake];
}
- (void)parserDidProcessData:(NSData *)data error:(NSError *)error {
if (data == nil) {
[self.delegate handler:self didFailWithError:error];
return;
}
PKWebSocketMessage *message = [PKWebSocketMessage messageWithData:data];
[self.delegate handler:self didProcessMessage:message error:error];
}
- (void)parserDidProcessString:(NSString *)string error:(NSError *)error {
if (string == nil) {
[self.delegate handler:self didFailWithError:error];
return;
}
PKWebSocketMessage *message = [PKWebSocketMessage messageWithString:string];
[self.delegate handler:self didProcessMessage:message error:error];
}
@end
Jump to Line
Something went wrong with that request. Please try again.