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}