Bladeren bron

Add Proxy support (#395)

* Add Proxy support

* move proxy to a class, add socks support

* should use weakSelf

* Move ProxyConnect to Internal/Proxy/SRProxyConnect
oldshuren 9 jaren geleden
bovenliggende
commit
d8a3a39187

+ 16 - 0
SocketRocket.xcodeproj/project.pbxproj

@@ -17,6 +17,8 @@
 		3345DC871C52ACD70083CCB8 /* Security.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = F6A12CD3145122FC00C1D980 /* Security.framework */; };
 		3345DC871C52ACD70083CCB8 /* Security.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = F6A12CD3145122FC00C1D980 /* Security.framework */; };
 		3345DC881C52ACD70083CCB8 /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = F6B208301450F597009315AF /* Foundation.framework */; };
 		3345DC881C52ACD70083CCB8 /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = F6B208301450F597009315AF /* Foundation.framework */; };
 		3345DC8A1C52ACD70083CCB8 /* SRWebSocket.h in Headers */ = {isa = PBXBuildFile; fileRef = F6A12CCF145119B700C1D980 /* SRWebSocket.h */; settings = {ATTRIBUTES = (Public, ); }; };
 		3345DC8A1C52ACD70083CCB8 /* SRWebSocket.h in Headers */ = {isa = PBXBuildFile; fileRef = F6A12CCF145119B700C1D980 /* SRWebSocket.h */; settings = {ATTRIBUTES = (Public, ); }; };
+		4861E7751D022211002FAB1D /* SRProxyConnect.h in Headers */ = {isa = PBXBuildFile; fileRef = 4861E7731D022211002FAB1D /* SRProxyConnect.h */; };
+		4861E7761D022211002FAB1D /* SRProxyConnect.m in Sources */ = {isa = PBXBuildFile; fileRef = 4861E7741D022211002FAB1D /* SRProxyConnect.m */; };
 		555E0EB41C51E57A00E6BB92 /* SocketRocket.h in Headers */ = {isa = PBXBuildFile; fileRef = 555E0EB11C51E56D00E6BB92 /* SocketRocket.h */; settings = {ATTRIBUTES = (Public, ); }; };
 		555E0EB41C51E57A00E6BB92 /* SocketRocket.h in Headers */ = {isa = PBXBuildFile; fileRef = 555E0EB11C51E56D00E6BB92 /* SocketRocket.h */; settings = {ATTRIBUTES = (Public, ); }; };
 		8105E4801CDD67B400AA12DB /* SRAutobahnTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 8105E47A1CDD679A00AA12DB /* SRAutobahnTests.m */; };
 		8105E4801CDD67B400AA12DB /* SRAutobahnTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 8105E47A1CDD679A00AA12DB /* SRAutobahnTests.m */; };
 		8105E4821CDD67BD00AA12DB /* SRTWebSocketOperation.m in Sources */ = {isa = PBXBuildFile; fileRef = 8105E4771CDD679A00AA12DB /* SRTWebSocketOperation.m */; };
 		8105E4821CDD67BD00AA12DB /* SRTWebSocketOperation.m in Sources */ = {isa = PBXBuildFile; fileRef = 8105E4771CDD679A00AA12DB /* SRTWebSocketOperation.m */; };
@@ -142,6 +144,8 @@
 /* Begin PBXFileReference section */
 /* Begin PBXFileReference section */
 		2D4227621BB4358C000C1A6C /* SocketRocket.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = SocketRocket.framework; sourceTree = BUILT_PRODUCTS_DIR; };
 		2D4227621BB4358C000C1A6C /* SocketRocket.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = SocketRocket.framework; sourceTree = BUILT_PRODUCTS_DIR; };
 		3345DC901C52ACD70083CCB8 /* SocketRocket.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = SocketRocket.framework; sourceTree = BUILT_PRODUCTS_DIR; };
 		3345DC901C52ACD70083CCB8 /* SocketRocket.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = SocketRocket.framework; sourceTree = BUILT_PRODUCTS_DIR; };
+		4861E7731D022211002FAB1D /* SRProxyConnect.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SRProxyConnect.h; sourceTree = "<group>"; };
+		4861E7741D022211002FAB1D /* SRProxyConnect.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SRProxyConnect.m; sourceTree = "<group>"; };
 		555E0EB11C51E56D00E6BB92 /* SocketRocket.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SocketRocket.h; sourceTree = "<group>"; };
 		555E0EB11C51E56D00E6BB92 /* SocketRocket.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SocketRocket.h; sourceTree = "<group>"; };
 		8105E4761CDD679A00AA12DB /* SRTWebSocketOperation.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SRTWebSocketOperation.h; sourceTree = "<group>"; };
 		8105E4761CDD679A00AA12DB /* SRTWebSocketOperation.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SRTWebSocketOperation.h; sourceTree = "<group>"; };
 		8105E4771CDD679A00AA12DB /* SRTWebSocketOperation.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SRTWebSocketOperation.m; sourceTree = "<group>"; };
 		8105E4771CDD679A00AA12DB /* SRTWebSocketOperation.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SRTWebSocketOperation.m; sourceTree = "<group>"; };
@@ -272,6 +276,15 @@
 /* End PBXFrameworksBuildPhase section */
 /* End PBXFrameworksBuildPhase section */
 
 
 /* Begin PBXGroup section */
 /* Begin PBXGroup section */
+		4861E7721D022211002FAB1D /* Proxy */ = {
+			isa = PBXGroup;
+			children = (
+				4861E7731D022211002FAB1D /* SRProxyConnect.h */,
+				4861E7741D022211002FAB1D /* SRProxyConnect.m */,
+			);
+			path = Proxy;
+			sourceTree = "<group>";
+		};
 		8105E4741CDD679A00AA12DB /* Tests */ = {
 		8105E4741CDD679A00AA12DB /* Tests */ = {
 			isa = PBXGroup;
 			isa = PBXGroup;
 			children = (
 			children = (
@@ -332,6 +345,7 @@
 		81B31C0D1CDC404100D86D43 /* Internal */ = {
 		81B31C0D1CDC404100D86D43 /* Internal */ = {
 			isa = PBXGroup;
 			isa = PBXGroup;
 			children = (
 			children = (
+				4861E7721D022211002FAB1D /* Proxy */,
 				817995831CE139540084DA37 /* Delegate */,
 				817995831CE139540084DA37 /* Delegate */,
 				81B31C0E1CDC404100D86D43 /* IOConsumer */,
 				81B31C0E1CDC404100D86D43 /* IOConsumer */,
 				81B31C5C1CDC443A00D86D43 /* RunLoop */,
 				81B31C5C1CDC443A00D86D43 /* RunLoop */,
@@ -545,6 +559,7 @@
 				81B31C1C1CDC404100D86D43 /* SRIOConsumerPool.h in Headers */,
 				81B31C1C1CDC404100D86D43 /* SRIOConsumerPool.h in Headers */,
 				F6A12CD1145119B700C1D980 /* SRWebSocket.h in Headers */,
 				F6A12CD1145119B700C1D980 /* SRWebSocket.h in Headers */,
 				81B31C2D1CDC406B00D86D43 /* SRHash.h in Headers */,
 				81B31C2D1CDC406B00D86D43 /* SRHash.h in Headers */,
+				4861E7751D022211002FAB1D /* SRProxyConnect.h in Headers */,
 				555E0EB41C51E57A00E6BB92 /* SocketRocket.h in Headers */,
 				555E0EB41C51E57A00E6BB92 /* SocketRocket.h in Headers */,
 				817995861CE139700084DA37 /* SRDelegateController.h in Headers */,
 				817995861CE139700084DA37 /* SRDelegateController.h in Headers */,
 				81B22EC51CE42D7E0073C636 /* SRError.h in Headers */,
 				81B22EC51CE42D7E0073C636 /* SRError.h in Headers */,
@@ -801,6 +816,7 @@
 			isa = PBXSourcesBuildPhase;
 			isa = PBXSourcesBuildPhase;
 			buildActionMask = 2147483647;
 			buildActionMask = 2147483647;
 			files = (
 			files = (
+				4861E7761D022211002FAB1D /* SRProxyConnect.m in Sources */,
 				81CD05DB1CEEC47300497F47 /* NSURLRequest+SRWebSocket.m in Sources */,
 				81CD05DB1CEEC47300497F47 /* NSURLRequest+SRWebSocket.m in Sources */,
 				81B22EC91CE42D7E0073C636 /* SRError.m in Sources */,
 				81B22EC91CE42D7E0073C636 /* SRError.m in Sources */,
 				81B31C181CDC404100D86D43 /* SRIOConsumer.m in Sources */,
 				81B31C181CDC404100D86D43 /* SRIOConsumer.m in Sources */,

+ 18 - 0
SocketRocket/Internal/Proxy/SRProxyConnect.h

@@ -0,0 +1,18 @@
+//
+// Copyright (c) 2016-present, Facebook, Inc.
+// All rights reserved.
+//
+// This source code is licensed under the BSD-style license found in the
+// LICENSE file in the root directory of this source tree. An additional grant
+// of patent rights can be found in the PATENTS file in the same directory.
+//
+
+#import <Foundation/Foundation.h>
+
+@interface SRProxyConnect : NSObject
+
+-(instancetype)initWithURL:(NSURL *)url;
+
+-(void) openNetworkStreamWithCompletion:(void (^)(NSError *error, NSInputStream *readStream, NSOutputStream *writeStream ))completion;
+
+@end								  

+ 480 - 0
SocketRocket/Internal/Proxy/SRProxyConnect.m

@@ -0,0 +1,480 @@
+//
+// Copyright (c) 2016-present, Facebook, Inc.
+// All rights reserved.
+//
+// This source code is licensed under the BSD-style license found in the
+// LICENSE file in the root directory of this source tree. An additional grant
+// of patent rights can be found in the PATENTS file in the same directory.
+//
+
+#import "SRProxyConnect.h"
+#import "SRError.h"
+#import "NSRunLoop+SRWebSocket.h"
+
+static inline void SRProxyFastLog(NSString *format, ...);
+
+typedef void (^SRProxyConnectionCompletion)(NSError *error, NSInputStream *readStream, NSOutputStream *writeStream);
+
+@interface SRProxyConnect() <NSStreamDelegate>
+
+@property (nonatomic, strong) NSURL *url;
+@property (nonatomic, strong) NSInputStream * inputStream;
+@property (nonatomic, strong) NSOutputStream * outputStream;
+
+@end
+
+@implementation SRProxyConnect {
+    
+    SRProxyConnectionCompletion  _connectDoneHandler;
+    
+    NSString *_httpProxyHost;
+    uint32_t _httpProxyPort;
+    
+    CFHTTPMessageRef _receivedHTTPHeaders;
+
+    NSString *_socksProxyHost;
+    uint32_t _socksProxyPort;
+    NSString *_socksProxyUsername;
+    NSString *_socksProxyPassword;
+    
+    BOOL _secure;
+
+    NSMutableArray<NSData *> *_inputQueue;
+    NSOperationQueue * _writeQueue;
+    
+}
+
+-(instancetype)initWithURL:(NSURL *)url
+{
+    self = [super init];
+    if (!self) return self;
+    
+    _url = url;
+        
+    NSString *scheme = url.scheme.lowercaseString;
+        
+    if ([scheme isEqualToString:@"wss"] || [scheme isEqualToString:@"https"]) {
+        _secure = YES;
+    }
+    
+    _writeQueue =  [[NSOperationQueue alloc] init];
+    _inputQueue = [NSMutableArray arrayWithCapacity:2];
+
+    return self;
+}
+
+-(void) openNetworkStreamWithCompletion:(void (^)(NSError *error, NSInputStream *readStream, NSOutputStream *writeStream ))completion
+{
+    _connectDoneHandler = completion;
+    [self _configureProxy];
+}
+
+-(void) _didConnect
+{
+    SRProxyFastLog(@"_didConnect, return streams");
+    if (_secure) {
+        if (_httpProxyHost) {
+            // Must set the real peer name before turning on SSL
+            SRProxyFastLog(@"proxy set peer name to real host %@", self.url.host);
+            [self.outputStream setProperty:self.url.host forKey:@"_kCFStreamPropertySocketPeerName"];
+        }
+    }
+    if (_receivedHTTPHeaders) {
+        CFRelease(_receivedHTTPHeaders);
+        _receivedHTTPHeaders = NULL;
+    }
+    NSInputStream *inputStream = self.inputStream;
+    NSOutputStream *outputStream = self.outputStream;
+    self.inputStream = nil;
+    self.outputStream = nil;
+    [inputStream removeFromRunLoop:[NSRunLoop SR_networkRunLoop]
+                           forMode:NSDefaultRunLoopMode];
+    inputStream.delegate = nil;
+    outputStream.delegate = nil;
+    _connectDoneHandler(nil, inputStream, outputStream);
+}
+
+-(void) _failWithError:(NSError *)error
+{
+    SRProxyFastLog(@"_failWithError, return error");
+    if (!error) {
+        error = SRHTTPErrorWithCodeDescription(500, 2132,@"Proxy Error");
+    }
+    
+    if (_receivedHTTPHeaders) {
+        CFRelease(_receivedHTTPHeaders);
+        _receivedHTTPHeaders = NULL;
+    }
+    [self.inputStream removeFromRunLoop:[NSRunLoop SR_networkRunLoop]
+                                forMode:NSDefaultRunLoopMode];
+    [self.inputStream close];
+    [self.outputStream close];
+    self.inputStream = nil;
+    self.outputStream = nil;
+    _connectDoneHandler(error, nil, nil);
+}
+
+// get proxy setting from device setting
+-(void) _configureProxy
+{
+    SRProxyFastLog(@"configureProxy");
+    NSDictionary *proxySettings = CFBridgingRelease(CFNetworkCopySystemProxySettings());
+    // CFNetworkCopyProxiesForURL doesn't understand ws:// or wss://
+    NSURL *httpURL;
+    if (_secure)
+        httpURL = [NSURL URLWithString:[NSString stringWithFormat:@"https://%@", _url.host]];
+    else
+        httpURL = [NSURL URLWithString:[NSString stringWithFormat:@"http://%@", _url.host]];
+    
+    NSArray *proxies = CFBridgingRelease(CFNetworkCopyProxiesForURL((__bridge CFURLRef)httpURL, (__bridge CFDictionaryRef)proxySettings));
+    if (proxies.count == 0) {
+        SRProxyFastLog(@"configureProxy no proxies");
+        [self _openConnection];
+        return;                 // no proxy
+    }
+    NSDictionary *settings = [proxies objectAtIndex:0];
+    NSString *proxyType = settings[(NSString *)kCFProxyTypeKey];
+    if ([proxyType isEqualToString:(NSString *)kCFProxyTypeAutoConfigurationURL]) {
+        NSURL *pacURL = settings[(NSString *)kCFProxyAutoConfigurationURLKey];
+        if (pacURL) {
+            [self _fetchPAC:pacURL];
+            return;
+        }
+    }
+    if ([proxyType isEqualToString:(NSString *)kCFProxyTypeAutoConfigurationJavaScript]) {
+        NSString *script = settings[(NSString *)kCFProxyAutoConfigurationJavaScriptKey];
+        if (script) {
+            [self _runPACScript:script];
+            return;
+        }
+    }
+    [self _readProxySettingWithType:proxyType settings:settings];
+
+    [self _openConnection];
+}
+
+- (void) _readProxySettingWithType:(NSString *)proxyType settings:(NSDictionary *)settings
+{
+    if ([proxyType isEqualToString:(NSString *)kCFProxyTypeHTTP] || [proxyType isEqualToString:(NSString *)kCFProxyTypeHTTPS]) {
+        _httpProxyHost = settings[(NSString *)kCFProxyHostNameKey];
+        NSNumber *portValue = settings[(NSString *)kCFProxyPortNumberKey];
+        if (portValue)
+            _httpProxyPort = [portValue intValue];
+    }
+    if ([proxyType isEqualToString:(NSString *)kCFProxyTypeSOCKS]) {
+        _socksProxyHost = settings[(NSString *)kCFProxyHostNameKey];
+        NSNumber *portValue = settings[(NSString *)kCFProxyPortNumberKey];
+        if (portValue)
+            _socksProxyPort = [portValue intValue];
+        _socksProxyUsername = settings[(NSString *)kCFProxyUsernameKey];
+        _socksProxyPassword = settings[(NSString *)kCFProxyPasswordKey];
+    }
+    if (_httpProxyHost) {
+        SRProxyFastLog(@"Using http proxy %@:%u", _httpProxyHost, _httpProxyPort);
+    } else if (_socksProxyHost) {
+        SRProxyFastLog(@"Using socks proxy %@:%u", _socksProxyHost, _socksProxyPort);
+    } else {
+        SRProxyFastLog(@"configureProxy no proxies");
+    }
+}
+
+- (void)_fetchPAC:(NSURL *)PACurl
+{
+    SRProxyFastLog(@"SRWebSocket fetchPAC:%@", PACurl);
+    
+    if ([PACurl isFileURL]) {
+        NSError *nsError = nil;
+        NSString *script = [NSString stringWithContentsOfURL:PACurl
+                                                usedEncoding:NULL
+                                                       error:&nsError];
+        
+        if (nsError) {
+            [self _openConnection];
+            return;
+        }
+        [self _runPACScript:script];
+        return;
+    }
+    
+    NSString *scheme = [PACurl.scheme lowercaseString];
+    if (![scheme isEqualToString:@"http"] && ![scheme isEqualToString:@"https"]) {
+        // Don't know how to read data from this URL, we'll have to give up
+        // We'll simply assume no proxies, and start the request as normal
+        [self _openConnection];
+        return;
+    }
+    __weak typeof(self) weakSelf = self;
+    NSURLRequest *request = [NSURLRequest requestWithURL:PACurl];
+    NSURLSession *session = [NSURLSession sharedSession];
+    NSURLSessionDataTask *task = [session dataTaskWithRequest:request
+                                            completionHandler:
+                                  ^(NSData *data, NSURLResponse *response, NSError *error) {
+                                      if (!error) {
+                                          NSString* script = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
+                                          [weakSelf _runPACScript:script];
+                                      } else {
+                                          [weakSelf _openConnection];
+                                      }
+                                      
+                                  }];
+    [task resume];
+}
+
+- (void)_runPACScript:(NSString *)script
+{
+    if (!script) {
+        [self _openConnection];
+        return;
+    }
+    SRProxyFastLog(@"runPACScript");
+    // From: http://developer.apple.com/samplecode/CFProxySupportTool/listing1.html
+    // Work around <rdar://problem/5530166>.  This dummy call to
+    // CFNetworkCopyProxiesForURL initialise some state within CFNetwork
+    // that is required by CFNetworkCopyProxiesForAutoConfigurationScript.
+    NSDictionary *empty;
+    CFBridgingRelease(CFNetworkCopyProxiesForURL((__bridge CFURLRef)_url, (__bridge CFDictionaryRef)empty));
+    
+    // Obtain the list of proxies by running the autoconfiguration script
+    CFErrorRef err = NULL;
+    
+    // CFNetworkCopyProxiesForAutoConfigurationScript doesn't understand ws:// or wss://
+    NSURL *httpURL;
+    if (_secure)
+        httpURL = [NSURL URLWithString:[NSString stringWithFormat:@"https://%@", _url.host]];
+    else
+        httpURL = [NSURL URLWithString:[NSString stringWithFormat:@"http://%@", _url.host]];
+    
+    NSArray *proxies = CFBridgingRelease(CFNetworkCopyProxiesForAutoConfigurationScript((__bridge CFStringRef)script,(__bridge CFURLRef)httpURL, &err));
+    if (!err && [proxies count] > 0) {
+        NSDictionary *settings = [proxies objectAtIndex:0];
+        NSString *proxyType = settings[(NSString *)kCFProxyTypeKey];
+        [self _readProxySettingWithType:proxyType settings:settings];
+    }
+    [self _openConnection];
+}
+
+- (void)_openConnection;
+{
+    [self _initializeStreams];
+    
+    [self.inputStream scheduleInRunLoop:[NSRunLoop SR_networkRunLoop]
+                                forMode:NSDefaultRunLoopMode];
+    //[self.outputStream scheduleInRunLoop:[NSRunLoop SR_networkRunLoop]
+    //                           forMode:NSDefaultRunLoopMode];
+    [self.outputStream open];
+    [self.inputStream open];
+}
+
+- (void)_initializeStreams;
+{
+    assert(_url.port.unsignedIntValue <= UINT32_MAX);
+    uint32_t port = _url.port.unsignedIntValue;
+    if (port == 0) {
+        if (!_secure) {
+            port = 80;
+        } else {
+            port = 443;
+        }
+    }
+    NSString *host = _url.host;
+    
+    if (_httpProxyHost) {
+        host = _httpProxyHost;
+        if (_httpProxyPort)
+            port = _httpProxyPort;
+        else
+            port = 80;
+    }
+    
+    CFReadStreamRef readStream = NULL;
+    CFWriteStreamRef writeStream = NULL;
+    
+    SRProxyFastLog(@"ProxyConnect connect stream to %@:%u", host, port);
+    CFStreamCreatePairWithSocketToHost(NULL, (__bridge CFStringRef)host, port, &readStream, &writeStream);
+    
+    self.outputStream = CFBridgingRelease(writeStream);
+    self.inputStream = CFBridgingRelease(readStream);
+
+    if (_socksProxyHost) {
+        SRProxyFastLog(@"ProxyConnect set sock property stream to %@:%u user %@ password %@", _socksProxyHost, _socksProxyPort, _socksProxyUsername, _socksProxyPassword);
+        NSMutableDictionary *settings = [NSMutableDictionary dictionaryWithCapacity:4];
+        settings[NSStreamSOCKSProxyHostKey] = _socksProxyHost;
+        if (_socksProxyPort) {
+            settings[NSStreamSOCKSProxyPortKey] = @(_socksProxyPort);
+        }
+        if (_socksProxyUsername) {
+            settings[NSStreamSOCKSProxyUserKey] = _socksProxyUsername;
+        }
+        if (_socksProxyPassword) {
+            settings[NSStreamSOCKSProxyPasswordKey] = _socksProxyPassword;
+        }
+        [self.inputStream setProperty:settings forKey:NSStreamSOCKSProxyConfigurationKey];
+        [self.outputStream setProperty:settings forKey:NSStreamSOCKSProxyConfigurationKey];
+    }
+    self.inputStream.delegate = self;
+    self.outputStream.delegate = self;
+}
+
+- (void)stream:(NSStream *)aStream handleEvent:(NSStreamEvent)eventCode;
+{
+    SRProxyFastLog(@"stream handleEvent %u", eventCode);
+    switch (eventCode) {
+        case NSStreamEventOpenCompleted: {
+            if (aStream == self.inputStream) {
+                if (_httpProxyHost) {
+                    [self _proxyDidConnect];
+                } else {
+                    [self _didConnect];
+                }
+            }
+            break;
+        }
+            
+        case NSStreamEventErrorOccurred: {
+            [self _failWithError:aStream.streamError];
+            break;
+            
+        }
+            
+        case NSStreamEventEndEncountered: {
+            [self _failWithError:aStream.streamError];
+            
+            break;
+        }
+            
+        case NSStreamEventHasBytesAvailable: {
+            if (aStream == _inputStream) {
+                [self _processInputStream];
+            }
+            break;
+        }
+            
+        default:
+            SRProxyFastLog(@"(default)  %@", aStream);
+            break;
+    }
+}
+
+- (void)_proxyDidConnect
+{
+    SRProxyFastLog(@"Proxy Connected");
+    uint32_t port = _url.port.unsignedIntValue;
+    if (port == 0) {
+        if (!_secure) {
+            port = 80;
+        } else {
+            port = 443;
+        }
+    }
+    // Send HTTP CONNECT Request
+    NSString *connectRequestStr = [NSString stringWithFormat:@"CONNECT %@:%u HTTP/1.1\r\nHost: %@\r\nConnection: keep-alive\r\nProxy-Connection: keep-alive\r\n\r\n", _url.host, port, _url.host];
+    
+    NSData *message =  [connectRequestStr dataUsingEncoding:NSUTF8StringEncoding];
+    SRProxyFastLog(@"Proxy sending %@", connectRequestStr);
+    
+    [self _writeData:message];
+}
+
+static size_t const SRProxyConnectBufferMaxSize = 4096;
+
+///handles the incoming bytes and sending them to the proper processing method
+-(void) _processInputStream
+{
+    NSMutableData *buf = [NSMutableData dataWithCapacity:SRProxyConnectBufferMaxSize];
+    uint8_t *buffer = buf.mutableBytes;
+    NSInteger length = [_inputStream read:buffer maxLength: SRProxyConnectBufferMaxSize];
+    
+    if (length <= 0)
+        return;
+    BOOL process = NO;
+    if (_inputQueue.count == 0)
+        process = YES;
+    
+    [_inputQueue addObject:[NSData dataWithBytes:buffer length:length]];
+    
+    if (process)
+        [self _dequeueInput];
+    
+}
+
+///dequeue the incoming input so it is processed in order
+
+-(void) _dequeueInput
+{
+    while (_inputQueue.count > 0) {
+        NSData * data = _inputQueue[0];
+        [self _proxyProcessHTTPResponseWithData:data];
+        [_inputQueue removeObjectAtIndex:0];
+    }
+}
+//handle checking the proxy  connection status
+-(void)  _proxyProcessHTTPResponseWithData:(NSData *)data
+{
+    if (_receivedHTTPHeaders == NULL) {
+        _receivedHTTPHeaders = CFHTTPMessageCreateEmpty(NULL, NO);
+    }
+    
+    CFHTTPMessageAppendBytes(_receivedHTTPHeaders, (const UInt8 *)data.bytes, data.length);
+    if (CFHTTPMessageIsHeaderComplete(_receivedHTTPHeaders)) {
+        SRProxyFastLog(@"Finished reading headers %@", CFBridgingRelease(CFHTTPMessageCopyAllHeaderFields(_receivedHTTPHeaders)));
+        [self _proxyHTTPHeadersDidFinish];
+    }
+}
+
+- (void)_proxyHTTPHeadersDidFinish;
+{
+    NSInteger responseCode = CFHTTPMessageGetResponseStatusCode(_receivedHTTPHeaders);
+    
+    if (responseCode >= 299) {
+        SRProxyFastLog(@"Connect to Proxy Request failed with response code %d", responseCode);
+        NSError *error = SRHTTPErrorWithCodeDescription(responseCode, 2132,
+                                                        [NSString stringWithFormat:@"Received bad response code from proxy server: %d.",
+                                                         (int)responseCode]);
+        [self _failWithError:error];
+        return;
+    }
+    SRProxyFastLog(@"proxy connect return %d, call socket connect", responseCode);
+    [self _didConnect];
+}
+
+#define timeout 5
+-(void)_writeData:(NSData *)data;
+{
+    const uint8_t * bytes = data.bytes;
+    __block NSInteger out = timeout * 1000000; //wait 5 seconds before giving up
+    __weak typeof(self) weakSelf = self;
+    [_writeQueue addOperationWithBlock:^() {
+        if (!weakSelf)
+            return;
+        NSOutputStream *outStream = weakSelf.outputStream;
+        if (!outStream)
+            return;
+        while ( ![outStream hasSpaceAvailable]) {
+            usleep(100); //wait until the socket is ready
+            out -= 100;
+            if (out < 0) {
+                NSError *error = SRHTTPErrorWithCodeDescription(408, 2132,@"Proxy timeout");
+                [self _failWithError:error];
+            } else if (outStream.streamError != nil) {
+                [self _failWithError:outStream.streamError];
+            }
+        }
+        [outStream write:bytes maxLength:data.length];
+    }];
+}
+@end
+
+//#define SRPROXY_ENABLE_LOG
+
+static inline void SRProxyFastLog(NSString *format, ...)  {
+#ifdef SRPROXY_ENABLE_LOG
+    __block va_list arg_list;
+    va_start (arg_list, format);
+    
+    NSString *formattedString = [[NSString alloc] initWithFormat:format arguments:arg_list];
+    
+    va_end(arg_list);
+    
+    NSLog(@"[Proxy] %@", formattedString);
+#endif
+}

+ 33 - 36
SocketRocket/SRWebSocket.m

@@ -35,6 +35,7 @@
 #import "SRError.h"
 #import "SRError.h"
 #import "NSURLRequest+SRWebSocket.h"
 #import "NSURLRequest+SRWebSocket.h"
 #import "NSRunLoop+SRWebSocket.h"
 #import "NSRunLoop+SRWebSocket.h"
+#import "SRProxyConnect.h"
 
 
 #if !__has_feature(objc_arc) 
 #if !__has_feature(objc_arc) 
 #error SocketRocket must be compiled with ARC enabled
 #error SocketRocket must be compiled with ARC enabled
@@ -145,6 +146,9 @@ NSString *const SRHTTPResponseErrorKey = @"HTTPResponseStatusCode";
     
     
     NSArray<NSString *> *_requestedProtocols;
     NSArray<NSString *> *_requestedProtocols;
     SRIOConsumerPool *_consumerPool;
     SRIOConsumerPool *_consumerPool;
+
+    // proxy support
+    SRProxyConnect *_proxyConnect;
 }
 }
 
 
 - (instancetype)initWithURLRequest:(NSURLRequest *)request protocols:(NSArray<NSString *> *)protocols allowsUntrustedSSLCertificates:(BOOL)allowsUntrustedSSLCertificates
 - (instancetype)initWithURLRequest:(NSURLRequest *)request protocols:(NSArray<NSString *> *)protocols allowsUntrustedSSLCertificates:(BOOL)allowsUntrustedSSLCertificates
@@ -186,8 +190,6 @@ NSString *const SRHTTPResponseErrorKey = @"HTTPResponseStatusCode";
 
 
     _scheduledRunloops = [[NSMutableSet alloc] init];
     _scheduledRunloops = [[NSMutableSet alloc] init];
 
 
-    [self _initializeStreams];
-
     return self;
     return self;
 }
 }
 
 
@@ -402,31 +404,6 @@ NSString *const SRHTTPResponseErrorKey = @"HTTPResponseStatusCode";
     [self _readHTTPHeader];
     [self _readHTTPHeader];
 }
 }
 
 
-- (void)_initializeStreams;
-{
-    assert(_url.port.unsignedIntValue <= UINT32_MAX);
-    uint32_t port = _url.port.unsignedIntValue;
-    if (port == 0) {
-        if (!_secure) {
-            port = 80;
-        } else {
-            port = 443;
-        }
-    }
-    NSString *host = _url.host;
-    
-    CFReadStreamRef readStream = NULL;
-    CFWriteStreamRef writeStream = NULL;
-    
-    CFStreamCreatePairWithSocketToHost(NULL, (__bridge CFStringRef)host, port, &readStream, &writeStream);
-    
-    _outputStream = CFBridgingRelease(writeStream);
-    _inputStream = CFBridgingRelease(readStream);
-    
-    _inputStream.delegate = self;
-    _outputStream.delegate = self;
-}
-
 - (void)_updateSecureStreamOptions;
 - (void)_updateSecureStreamOptions;
 {
 {
     if (_secure) {
     if (_secure) {
@@ -495,15 +472,35 @@ NSString *const SRHTTPResponseErrorKey = @"HTTPResponseStatusCode";
 
 
 - (void)openConnection;
 - (void)openConnection;
 {
 {
-    [self _updateSecureStreamOptions];
-    
-    if (!_scheduledRunloops.count) {
-        [self scheduleInRunLoop:[NSRunLoop SR_networkRunLoop] forMode:NSDefaultRunLoopMode];
+    __weak typeof(self) weakSelf = self;
+
+    _proxyConnect = [[SRProxyConnect alloc] initWithURL:_url];
+    [_proxyConnect openNetworkStreamWithCompletion:^(NSError *error, NSInputStream *readStream, NSOutputStream *writeStream) {
+            [weakSelf _connectionDoneWithError:error readStream:readStream writeStream:writeStream];
+        }];
+}
+
+- (void) _connectionDoneWithError:(NSError *)error readStream:(NSInputStream *)readStream writeStream:(NSOutputStream *)writeStream
+{
+    _proxyConnect = nil;        // don't need it anymore
+    if (error != nil) {
+        [self _failWithError:error];
+    } else {
+        _outputStream = writeStream;
+        _inputStream = readStream;
+                
+        _inputStream.delegate = self;
+        _outputStream.delegate = self;
+        [self _updateSecureStreamOptions];
+                
+        if (!_scheduledRunloops.count) {
+            [self scheduleInRunLoop:[NSRunLoop SR_networkRunLoop] forMode:NSDefaultRunLoopMode];
+        }
+        
+        dispatch_async(_workQueue, ^{
+                [self didConnect];
+            });
     }
     }
-    
-    
-    [_outputStream open];
-    [_inputStream open];
 }
 }
 
 
 - (void)scheduleInRunLoop:(NSRunLoop *)aRunLoop forMode:(NSString *)mode;
 - (void)scheduleInRunLoop:(NSRunLoop *)aRunLoop forMode:(NSString *)mode;
@@ -1570,7 +1567,7 @@ static const size_t SRFrameHeaderOverhead = 32;
 
 
 @end
 @end
 
 
-//#define SR_ENABLE_LOG
+#define SR_ENABLE_LOG
 
 
 static inline void SRFastLog(NSString *format, ...)  {
 static inline void SRFastLog(NSString *format, ...)  {
 #ifdef SR_ENABLE_LOG
 #ifdef SR_ENABLE_LOG