@@ -157,19 +157,53 @@ public function connect($uri)
157157 $ socksUri .= '# ' . $ parts ['fragment ' ];
158158 }
159159
160- $ that = $ this ;
160+ // start TCP/IP connection to SOCKS server
161+ $ connecting = $ this ->connector ->connect ($ socksUri );
162+
163+ $ deferred = new Deferred (function ($ _ , $ reject ) use ($ connecting ) {
164+ $ reject (new RuntimeException (
165+ 'Connection cancelled while waiting for proxy (ECONNABORTED) ' ,
166+ defined ('SOCKET_ECONNABORTED ' ) ? SOCKET_ECONNABORTED : 103
167+ ));
168+
169+ // either close active connection or cancel pending connection attempt
170+ $ connecting ->then (function (ConnectionInterface $ stream ) {
171+ $ stream ->close ();
172+ });
173+ $ connecting ->cancel ();
174+ });
161175
162- // start TCP/IP connection to SOCKS server and then
163176 // handle SOCKS protocol once connection is ready
164177 // resolve plain connection once SOCKS protocol is completed
165- return $ this ->connector ->connect ($ socksUri )->then (
166- function (ConnectionInterface $ stream ) use ($ that , $ host , $ port ) {
167- return $ that ->handleConnectedSocks ($ stream , $ host , $ port );
178+ $ that = $ this ;
179+ $ connecting ->then (
180+ function (ConnectionInterface $ stream ) use ($ that , $ host , $ port , $ deferred ) {
181+ $ that ->handleConnectedSocks ($ stream , $ host , $ port , $ deferred );
168182 },
169- function (Exception $ e ) {
170- throw new RuntimeException ('Unable to connect to proxy (ECONNREFUSED) ' , defined ('SOCKET_ECONNREFUSED ' ) ? SOCKET_ECONNREFUSED : 111 , $ e );
183+ function (Exception $ e ) use ($ deferred ) {
184+ $ deferred ->reject ($ e = new RuntimeException (
185+ 'Connection failed because connection to proxy failed (ECONNREFUSED) ' ,
186+ defined ('SOCKET_ECONNREFUSED ' ) ? SOCKET_ECONNREFUSED : 111 ,
187+ $ e
188+ ));
189+
190+ // avoid garbage references by replacing all closures in call stack.
191+ // what a lovely piece of code!
192+ $ r = new \ReflectionProperty ('Exception ' , 'trace ' );
193+ $ r ->setAccessible (true );
194+ $ trace = $ r ->getValue ($ e );
195+ foreach ($ trace as &$ one ) {
196+ foreach ($ one ['args ' ] as &$ arg ) {
197+ if ($ arg instanceof \Closure) {
198+ $ arg = 'Object( ' . get_class ($ arg ) . ') ' ;
199+ }
200+ }
201+ }
202+ $ r ->setValue ($ e , $ trace );
171203 }
172204 );
205+
206+ return $ deferred ->promise ();
173207 }
174208
175209 /**
@@ -178,15 +212,12 @@ function (Exception $e) {
178212 * @param ConnectionInterface $stream
179213 * @param string $host
180214 * @param int $port
181- * @return Promise Promise<ConnectionInterface, Exception>
215+ * @param Deferred $deferred
216+ * @return void
182217 * @internal
183218 */
184- public function handleConnectedSocks (ConnectionInterface $ stream , $ host , $ port )
219+ public function handleConnectedSocks (ConnectionInterface $ stream , $ host , $ port, Deferred $ deferred )
185220 {
186- $ deferred = new Deferred (function ($ _ , $ reject ) {
187- $ reject (new RuntimeException ('Connection canceled while establishing SOCKS session (ECONNABORTED) ' , defined ('SOCKET_ECONNABORTED ' ) ? SOCKET_ECONNABORTED : 103 ));
188- });
189-
190221 $ reader = new StreamReader ();
191222 $ stream ->on ('data ' , array ($ reader , 'write ' ));
192223
@@ -203,32 +234,22 @@ public function handleConnectedSocks(ConnectionInterface $stream, $host, $port)
203234 } else {
204235 $ promise = $ this ->handleSocks4 ($ stream , $ host , $ port , $ reader );
205236 }
206- $ promise ->then (function () use ($ deferred , $ stream ) {
237+
238+ $ promise ->then (function () use ($ deferred , $ stream , $ reader , $ onError , $ onClose ) {
239+ $ stream ->removeListener ('data ' , array ($ reader , 'write ' ));
240+ $ stream ->removeListener ('error ' , $ onError );
241+ $ stream ->removeListener ('close ' , $ onClose );
242+
207243 $ deferred ->resolve ($ stream );
208- }, function (Exception $ error ) use ($ deferred ) {
244+ }, function (Exception $ error ) use ($ deferred, $ stream ) {
209245 // pass custom RuntimeException through as-is, otherwise wrap in protocol error
210246 if (!$ error instanceof RuntimeException) {
211247 $ error = new RuntimeException ('Invalid response received from proxy (EBADMSG) ' , defined ('SOCKET_EBADMSG ' ) ? SOCKET_EBADMSG : 71 , $ error );
212248 }
213249
214250 $ deferred ->reject ($ error );
251+ $ stream ->close ();
215252 });
216-
217- return $ deferred ->promise ()->then (
218- function (ConnectionInterface $ stream ) use ($ reader , $ onError , $ onClose ) {
219- $ stream ->removeListener ('data ' , array ($ reader , 'write ' ));
220- $ stream ->removeListener ('error ' , $ onError );
221- $ stream ->removeListener ('close ' , $ onClose );
222-
223- return $ stream ;
224- },
225- function ($ error ) use ($ stream , $ onClose ) {
226- $ stream ->removeListener ('close ' , $ onClose );
227- $ stream ->close ();
228-
229- throw $ error ;
230- }
231- );
232253 }
233254
234255 private function handleSocks4 (ConnectionInterface $ stream , $ host , $ port , StreamReader $ reader )
0 commit comments