DnsConnector.php 4.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117
  1. <?php
  2. namespace React\Socket;
  3. use React\Dns\Resolver\ResolverInterface;
  4. use React\Promise;
  5. use React\Promise\PromiseInterface;
  6. final class DnsConnector implements ConnectorInterface
  7. {
  8. private $connector;
  9. private $resolver;
  10. public function __construct(ConnectorInterface $connector, ResolverInterface $resolver)
  11. {
  12. $this->connector = $connector;
  13. $this->resolver = $resolver;
  14. }
  15. public function connect($uri)
  16. {
  17. $original = $uri;
  18. if (\strpos($uri, '://') === false) {
  19. $uri = 'tcp://' . $uri;
  20. $parts = \parse_url($uri);
  21. if (isset($parts['scheme'])) {
  22. unset($parts['scheme']);
  23. }
  24. } else {
  25. $parts = \parse_url($uri);
  26. }
  27. if (!$parts || !isset($parts['host'])) {
  28. return Promise\reject(new \InvalidArgumentException(
  29. 'Given URI "' . $original . '" is invalid (EINVAL)',
  30. \defined('SOCKET_EINVAL') ? \SOCKET_EINVAL : (\defined('PCNTL_EINVAL') ? \PCNTL_EINVAL : 22)
  31. ));
  32. }
  33. $host = \trim($parts['host'], '[]');
  34. $connector = $this->connector;
  35. // skip DNS lookup / URI manipulation if this URI already contains an IP
  36. if (@\inet_pton($host) !== false) {
  37. return $connector->connect($original);
  38. }
  39. $promise = $this->resolver->resolve($host);
  40. $resolved = null;
  41. return new Promise\Promise(
  42. function ($resolve, $reject) use (&$promise, &$resolved, $uri, $connector, $host, $parts) {
  43. // resolve/reject with result of DNS lookup
  44. $promise->then(function ($ip) use (&$promise, &$resolved, $uri, $connector, $host, $parts) {
  45. $resolved = $ip;
  46. return $promise = $connector->connect(
  47. Connector::uri($parts, $host, $ip)
  48. )->then(null, function (\Exception $e) use ($uri) {
  49. if ($e instanceof \RuntimeException) {
  50. $message = \preg_replace('/^(Connection to [^ ]+)[&?]hostname=[^ &]+/', '$1', $e->getMessage());
  51. $e = new \RuntimeException(
  52. 'Connection to ' . $uri . ' failed: ' . $message,
  53. $e->getCode(),
  54. $e
  55. );
  56. // avoid garbage references by replacing all closures in call stack.
  57. // what a lovely piece of code!
  58. $r = new \ReflectionProperty('Exception', 'trace');
  59. $r->setAccessible(true);
  60. $trace = $r->getValue($e);
  61. // Exception trace arguments are not available on some PHP 7.4 installs
  62. // @codeCoverageIgnoreStart
  63. foreach ($trace as $ti => $one) {
  64. if (isset($one['args'])) {
  65. foreach ($one['args'] as $ai => $arg) {
  66. if ($arg instanceof \Closure) {
  67. $trace[$ti]['args'][$ai] = 'Object(' . \get_class($arg) . ')';
  68. }
  69. }
  70. }
  71. }
  72. // @codeCoverageIgnoreEnd
  73. $r->setValue($e, $trace);
  74. }
  75. throw $e;
  76. });
  77. }, function ($e) use ($uri, $reject) {
  78. $reject(new \RuntimeException('Connection to ' . $uri .' failed during DNS lookup: ' . $e->getMessage(), 0, $e));
  79. })->then($resolve, $reject);
  80. },
  81. function ($_, $reject) use (&$promise, &$resolved, $uri) {
  82. // cancellation should reject connection attempt
  83. // reject DNS resolution with custom reason, otherwise rely on connection cancellation below
  84. if ($resolved === null) {
  85. $reject(new \RuntimeException(
  86. 'Connection to ' . $uri . ' cancelled during DNS lookup (ECONNABORTED)',
  87. \defined('SOCKET_ECONNABORTED') ? \SOCKET_ECONNABORTED : 103
  88. ));
  89. }
  90. // (try to) cancel pending DNS lookup / connection attempt
  91. if ($promise instanceof PromiseInterface && \method_exists($promise, 'cancel')) {
  92. // overwrite callback arguments for PHP7+ only, so they do not show
  93. // up in the Exception trace and do not cause a possible cyclic reference.
  94. $_ = $reject = null;
  95. $promise->cancel();
  96. $promise = null;
  97. }
  98. }
  99. );
  100. }
  101. }