Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
tree: 6d1f99227c
Fetching contributors…

Cannot retrieve contributors at this time

287 lines (235 sloc) 9.837 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 {
BOOL _didSecure;
}
// 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->_didSecure = NO;
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 {
NSParameterAssert(anURL);
if (self.status != PKWebSocketDisconnectedStatus) return YES;
self->_url = anURL;
self.status = PKWebSocketConnectingStatus;
if ([anURL isSecure]) {
[self.socket startTLS:self.tlsSettings];
}
BOOL status = [self.socket connectToHost:anURL.host
onPort:[anURL.port intValue]
withTimeout:self.timeout
error:outError];
if (status) {
if (self.runLoopModes) {
[self.socket setRunLoopModes:self.runLoopModes];
}
return YES;
} else {
self.status = PKWebSocketDisconnectedStatus;
return NO;
}
}
- (void)disconnect {
if (self.status == PKWebSocketOpenedStatus) {
[self parserNeedsInitiateClosingHandshakeWithData:self.parser.closingPayload];
}
self.status = PKWebSocketClosingStatus;
[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];
if (request == nil) {
if (outError != NULL) {
NSString *reason =
[NSString stringWithFormat:@"Unable to create handshake request for (%@) method and URL (%@)",
HTTP_GET_METHOD,
[anURL absoluteString]];
NSDictionary *ui = [NSDictionary dictionaryWithObjectsAndKeys:reason, NSLocalizedDescriptionKey, nil];
*outError = [NSError errorWithDomain:PKWebSocketErrorDomain
code:PKWebSocketInvalidURL
userInfo:ui];
}
return nil;
}
[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) {
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 {
if ([self.url isSecure] && !self->_didSecure) {
NSString *reason = @"Secure URL used, connection established but connection failed to secure! Disconnecting...";
NSDictionary *ui = [NSDictionary dictionaryWithObjectsAndKeys:reason, NSLocalizedDescriptionKey, nil];
NSError *error = [NSError errorWithDomain:PKWebSocketErrorDomain
code:PKWebSocketTLSError
userInfo:ui];
[self.delegate handler:self didFailWithError:error];
[self disconnect];
return;
}
self.status = PKWebSocketOpeningStatus;
NSError *error;
PKHTTPRequest *request = [self handshakeRequestWithURL:self.url error:&error];
if (request != nil) {
[self sendData:[request asData] tag:PKWebSocketTagOpeningHandshake];
} else {
[self.delegate handler:self didFailWithError:error];
[self disconnect];
}
}
- (void)onSocket:(AsyncSocket *)sock willDisconnectWithError:(NSError *)outError {
if (self.status != PKWebSocketOpenedStatus) {
[self.delegate handler:self didFailWithError:outError];
}
self.status = PKWebSocketClosedStatus;
}
- (void)onSocketDidDisconnect:(AsyncSocket *)sock {
self->_didSecure = NO;
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 if (tag == PKWebSocketTagClosingHandshake) {
// Closing handshake
} else {
}
}
- (void)onSocket:(AsyncSocket *)sock didReadData:(NSData *)data withTag:(long)tag {
if (tag == PKWebSocketTagOpeningHandshake) {
NSError *error;
PKHTTPResponse *response = [[PKHTTPResponse alloc] initWithData:data error:&error];
if (response != nil && [self validateHandshakeResponse:response error:&error]) {
self.status = PKWebSocketOpenedStatus;
[sock readDataWithTimeout:-1 tag:PKWebSocketTagMessage];
} else {
[self.delegate handler:self didFailWithError:error];
[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);
}
}
- (void)onSocketDidSecure:(AsyncSocket *)sock {
self->_didSecure = YES;
}
#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 {
PKWebSocketMessage *message = nil;
if (data != nil) message = [PKWebSocketMessage messageWithData:data];
[self.delegate handler:self didProcessMessage:message error:error];
}
- (void)parserDidProcessString:(NSString *)string error:(NSError *)error {
PKWebSocketMessage *message = nil;
if (string != nil) 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.