Subversion Repositories php-qbpwcf

Rev

Details | Last modification | View Log | RSS feed

Rev Author Line No. Line
3 liveuser 1
<?php
2
 
3
namespace React\Socket;
4
 
5
use React\EventLoop\LoopInterface;
6
use React\Promise;
7
use InvalidArgumentException;
8
use RuntimeException;
9
 
10
final class TcpConnector implements ConnectorInterface
11
{
12
    private $loop;
13
    private $context;
14
 
15
    public function __construct(LoopInterface $loop, array $context = array())
16
    {
17
        $this->loop = $loop;
18
        $this->context = $context;
19
    }
20
 
21
    public function connect($uri)
22
    {
23
        if (\strpos($uri, '://') === false) {
24
            $uri = 'tcp://' . $uri;
25
        }
26
 
27
        $parts = \parse_url($uri);
28
        if (!$parts || !isset($parts['scheme'], $parts['host'], $parts['port']) || $parts['scheme'] !== 'tcp') {
29
            return Promise\reject(new \InvalidArgumentException('Given URI "' . $uri . '" is invalid'));
30
        }
31
 
32
        $ip = \trim($parts['host'], '[]');
33
        if (false === \filter_var($ip, \FILTER_VALIDATE_IP)) {
34
            return Promise\reject(new \InvalidArgumentException('Given URI "' . $ip . '" does not contain a valid host IP'));
35
        }
36
 
37
        // use context given in constructor
38
        $context = array(
39
            'socket' => $this->context
40
        );
41
 
42
        // parse arguments from query component of URI
43
        $args = array();
44
        if (isset($parts['query'])) {
45
            \parse_str($parts['query'], $args);
46
        }
47
 
48
        // If an original hostname has been given, use this for TLS setup.
49
        // This can happen due to layers of nested connectors, such as a
50
        // DnsConnector reporting its original hostname.
51
        // These context options are here in case TLS is enabled later on this stream.
52
        // If TLS is not enabled later, this doesn't hurt either.
53
        if (isset($args['hostname'])) {
54
            $context['ssl'] = array(
55
                'SNI_enabled' => true,
56
                'peer_name' => $args['hostname']
57
            );
58
 
59
            // Legacy PHP < 5.6 ignores peer_name and requires legacy context options instead.
60
            // The SNI_server_name context option has to be set here during construction,
61
            // as legacy PHP ignores any values set later.
62
            // @codeCoverageIgnoreStart
63
            if (\PHP_VERSION_ID < 50600) {
64
                $context['ssl'] += array(
65
                    'SNI_server_name' => $args['hostname'],
66
                    'CN_match' => $args['hostname']
67
                );
68
            }
69
            // @codeCoverageIgnoreEnd
70
        }
71
 
72
        // latest versions of PHP no longer accept any other URI components and
73
        // HHVM fails to parse URIs with a query but no path, so let's simplify our URI here
74
        $remote = 'tcp://' . $parts['host'] . ':' . $parts['port'];
75
 
76
        $stream = @\stream_socket_client(
77
            $remote,
78
            $errno,
79
            $errstr,
80
            0,
81
            \STREAM_CLIENT_CONNECT | \STREAM_CLIENT_ASYNC_CONNECT,
82
            \stream_context_create($context)
83
        );
84
 
85
        if (false === $stream) {
86
            return Promise\reject(new \RuntimeException(
87
                \sprintf("Connection to %s failed: %s", $uri, $errstr),
88
                $errno
89
            ));
90
        }
91
 
92
        // wait for connection
93
        $loop = $this->loop;
94
        return new Promise\Promise(function ($resolve, $reject) use ($loop, $stream, $uri) {
95
            $loop->addWriteStream($stream, function ($stream) use ($loop, $resolve, $reject, $uri) {
96
                $loop->removeWriteStream($stream);
97
 
98
                // The following hack looks like the only way to
99
                // detect connection refused errors with PHP's stream sockets.
100
                if (false === \stream_socket_get_name($stream, true)) {
101
                    \fclose($stream);
102
 
103
                    $reject(new \RuntimeException('Connection to ' . $uri . ' failed: Connection refused'));
104
                } else {
105
                    $resolve(new Connection($stream, $loop));
106
                }
107
            });
108
        }, function () use ($loop, $stream, $uri) {
109
            $loop->removeWriteStream($stream);
110
            \fclose($stream);
111
 
112
            // @codeCoverageIgnoreStart
113
            // legacy PHP 5.3 sometimes requires a second close call (see tests)
114
            if (\PHP_VERSION_ID < 50400 && \is_resource($stream)) {
115
                \fclose($stream);
116
            }
117
            // @codeCoverageIgnoreEnd
118
 
119
            throw new \RuntimeException('Connection to ' . $uri . ' cancelled during TCP/IP handshake');
120
        });
121
    }
122
}