friendship ended with social-app. php is my new best friend
1<?php
2
3namespace React\Socket;
4
5use React\Dns\Config\Config as DnsConfig;
6use React\Dns\Resolver\Factory as DnsFactory;
7use React\Dns\Resolver\ResolverInterface;
8use React\EventLoop\LoopInterface;
9
10/**
11 * The `Connector` class is the main class in this package that implements the
12 * `ConnectorInterface` and allows you to create streaming connections.
13 *
14 * You can use this connector to create any kind of streaming connections, such
15 * as plaintext TCP/IP, secure TLS or local Unix connection streams.
16 *
17 * Under the hood, the `Connector` is implemented as a *higher-level facade*
18 * for the lower-level connectors implemented in this package. This means it
19 * also shares all of their features and implementation details.
20 * If you want to typehint in your higher-level protocol implementation, you SHOULD
21 * use the generic [`ConnectorInterface`](#connectorinterface) instead.
22 *
23 * @see ConnectorInterface for the base interface
24 */
25final class Connector implements ConnectorInterface
26{
27 private $connectors = array();
28
29 /**
30 * Instantiate new `Connector`
31 *
32 * ```php
33 * $connector = new React\Socket\Connector();
34 * ```
35 *
36 * This class takes two optional arguments for more advanced usage:
37 *
38 * ```php
39 * // constructor signature as of v1.9.0
40 * $connector = new React\Socket\Connector(array $context = [], ?LoopInterface $loop = null);
41 *
42 * // legacy constructor signature before v1.9.0
43 * $connector = new React\Socket\Connector(?LoopInterface $loop = null, array $context = []);
44 * ```
45 *
46 * This class takes an optional `LoopInterface|null $loop` parameter that can be used to
47 * pass the event loop instance to use for this object. You can use a `null` value
48 * here in order to use the [default loop](https://github.com/reactphp/event-loop#loop).
49 * This value SHOULD NOT be given unless you're sure you want to explicitly use a
50 * given event loop instance.
51 *
52 * @param array|LoopInterface|null $context
53 * @param null|LoopInterface|array $loop
54 * @throws \InvalidArgumentException for invalid arguments
55 */
56 public function __construct($context = array(), $loop = null)
57 {
58 // swap arguments for legacy constructor signature
59 if (($context instanceof LoopInterface || $context === null) && (\func_num_args() <= 1 || \is_array($loop))) {
60 $swap = $loop === null ? array(): $loop;
61 $loop = $context;
62 $context = $swap;
63 }
64
65 if (!\is_array($context) || ($loop !== null && !$loop instanceof LoopInterface)) {
66 throw new \InvalidArgumentException('Expected "array $context" and "?LoopInterface $loop" arguments');
67 }
68
69 // apply default options if not explicitly given
70 $context += array(
71 'tcp' => true,
72 'tls' => true,
73 'unix' => true,
74
75 'dns' => true,
76 'timeout' => true,
77 'happy_eyeballs' => true,
78 );
79
80 if ($context['timeout'] === true) {
81 $context['timeout'] = (float)\ini_get("default_socket_timeout");
82 }
83
84 if ($context['tcp'] instanceof ConnectorInterface) {
85 $tcp = $context['tcp'];
86 } else {
87 $tcp = new TcpConnector(
88 $loop,
89 \is_array($context['tcp']) ? $context['tcp'] : array()
90 );
91 }
92
93 if ($context['dns'] !== false) {
94 if ($context['dns'] instanceof ResolverInterface) {
95 $resolver = $context['dns'];
96 } else {
97 if ($context['dns'] !== true) {
98 $config = $context['dns'];
99 } else {
100 // try to load nameservers from system config or default to Google's public DNS
101 $config = DnsConfig::loadSystemConfigBlocking();
102 if (!$config->nameservers) {
103 $config->nameservers[] = '8.8.8.8'; // @codeCoverageIgnore
104 }
105 }
106
107 $factory = new DnsFactory();
108 $resolver = $factory->createCached(
109 $config,
110 $loop
111 );
112 }
113
114 if ($context['happy_eyeballs'] === true) {
115 $tcp = new HappyEyeBallsConnector($loop, $tcp, $resolver);
116 } else {
117 $tcp = new DnsConnector($tcp, $resolver);
118 }
119 }
120
121 if ($context['tcp'] !== false) {
122 $context['tcp'] = $tcp;
123
124 if ($context['timeout'] !== false) {
125 $context['tcp'] = new TimeoutConnector(
126 $context['tcp'],
127 $context['timeout'],
128 $loop
129 );
130 }
131
132 $this->connectors['tcp'] = $context['tcp'];
133 }
134
135 if ($context['tls'] !== false) {
136 if (!$context['tls'] instanceof ConnectorInterface) {
137 $context['tls'] = new SecureConnector(
138 $tcp,
139 $loop,
140 \is_array($context['tls']) ? $context['tls'] : array()
141 );
142 }
143
144 if ($context['timeout'] !== false) {
145 $context['tls'] = new TimeoutConnector(
146 $context['tls'],
147 $context['timeout'],
148 $loop
149 );
150 }
151
152 $this->connectors['tls'] = $context['tls'];
153 }
154
155 if ($context['unix'] !== false) {
156 if (!$context['unix'] instanceof ConnectorInterface) {
157 $context['unix'] = new UnixConnector($loop);
158 }
159 $this->connectors['unix'] = $context['unix'];
160 }
161 }
162
163 public function connect($uri)
164 {
165 $scheme = 'tcp';
166 if (\strpos($uri, '://') !== false) {
167 $scheme = (string)\substr($uri, 0, \strpos($uri, '://'));
168 }
169
170 if (!isset($this->connectors[$scheme])) {
171 return \React\Promise\reject(new \RuntimeException(
172 'No connector available for URI scheme "' . $scheme . '" (EINVAL)',
173 \defined('SOCKET_EINVAL') ? \SOCKET_EINVAL : (\defined('PCNTL_EINVAL') ? \PCNTL_EINVAL : 22)
174 ));
175 }
176
177 return $this->connectors[$scheme]->connect($uri);
178 }
179
180
181 /**
182 * [internal] Builds on URI from the given URI parts and ip address with original hostname as query
183 *
184 * @param array $parts
185 * @param string $host
186 * @param string $ip
187 * @return string
188 * @internal
189 */
190 public static function uri(array $parts, $host, $ip)
191 {
192 $uri = '';
193
194 // prepend original scheme if known
195 if (isset($parts['scheme'])) {
196 $uri .= $parts['scheme'] . '://';
197 }
198
199 if (\strpos($ip, ':') !== false) {
200 // enclose IPv6 addresses in square brackets before appending port
201 $uri .= '[' . $ip . ']';
202 } else {
203 $uri .= $ip;
204 }
205
206 // append original port if known
207 if (isset($parts['port'])) {
208 $uri .= ':' . $parts['port'];
209 }
210
211 // append orignal path if known
212 if (isset($parts['path'])) {
213 $uri .= $parts['path'];
214 }
215
216 // append original query if known
217 if (isset($parts['query'])) {
218 $uri .= '?' . $parts['query'];
219 }
220
221 // append original hostname as query if resolved via DNS and if
222 // destination URI does not contain "hostname" query param already
223 $args = array();
224 \parse_str(isset($parts['query']) ? $parts['query'] : '', $args);
225 if ($host !== $ip && !isset($args['hostname'])) {
226 $uri .= (isset($parts['query']) ? '&' : '?') . 'hostname=' . \rawurlencode($host);
227 }
228
229 // append original fragment if known
230 if (isset($parts['fragment'])) {
231 $uri .= '#' . $parts['fragment'];
232 }
233
234 return $uri;
235 }
236}