diff --git a/composer.json b/composer.json index 3bc20c5..fe3876c 100644 --- a/composer.json +++ b/composer.json @@ -47,7 +47,7 @@ "danog/libdns-json": "^0.1", "daverandom/libdns": "^2.0.1", "amphp/amp": "^2", - "amphp/artax": "dev-master", + "amphp/http-client": "4.0-rc10", "amphp/dns": "dev-master as v0.9.x-dev", "ext-filter": "*", "ext-json": "*" diff --git a/examples/benchmark.php b/examples/benchmark.php index 343f331..a43b543 100644 --- a/examples/benchmark.php +++ b/examples/benchmark.php @@ -8,7 +8,7 @@ use Amp\Loop; print "Downloading top 500 domains..." . PHP_EOL; -$domains = \file_get_contents("https://moz.com/top-500/download?table=top500Domains"); +$domains = \file_get_contents("https://moz.com/top-500/download/?table=top500Domains"); $domains = \array_map(function ($line) { return \trim(\explode(",", $line)[1], '"/'); }, \array_filter(\explode("\n", $domains))); diff --git a/examples/domain-fronting.php b/examples/domain-fronting.php deleted file mode 100644 index 79915a0..0000000 --- a/examples/domain-fronting.php +++ /dev/null @@ -1,22 +0,0 @@ - "dns.google.com"])]); -Dns\resolver(new DoH\Rfc8484StubResolver($DohConfig)); - -Loop::run(function () { - $hostname = "amphp.org"; - - try { - pretty_print_records($hostname, yield Dns\resolve($hostname)); - } catch (Dns\DnsException $e) { - pretty_print_error($hostname, $e); - } -}); diff --git a/lib/DoHConfig.php b/lib/DoHConfig.php index b929808..b310aa4 100644 --- a/lib/DoHConfig.php +++ b/lib/DoHConfig.php @@ -2,8 +2,6 @@ namespace Amp\DoH; -use Amp\Artax\Client; -use Amp\Artax\DefaultClient; use Amp\Cache\ArrayCache; use Amp\Cache\Cache; use Amp\Dns\ConfigException; @@ -12,16 +10,18 @@ use Amp\Dns\Resolver; use Amp\Dns\Rfc1035StubResolver; use Amp\Dns\UnixConfigLoader; use Amp\Dns\WindowsConfigLoader; +use Amp\Http\Client\DelegateHttpClient; +use Amp\Http\Client\HttpClientBuilder; final class DoHConfig { private $nameservers; - private $artax; + private $httpClient; private $subResolver; private $configLoader; private $cache; - public function __construct(array $nameservers, Client $artax = null, Resolver $resolver = null, ConfigLoader $configLoader = null, Cache $cache = null) + public function __construct(array $nameservers, DelegateHttpClient $httpClient = null, Resolver $resolver = null, ConfigLoader $configLoader = null, Cache $cache = null) { if (\count($nameservers) < 1) { throw new ConfigException("At least one nameserver is required for a valid config"); @@ -32,7 +32,7 @@ final class DoHConfig } $this->nameservers = $nameservers; - $this->artax = $artax ?? new DefaultClient(); + $this->httpClient = $httpClient ?? HttpClientBuilder::buildDefault(); $this->cache = $cache ?? new ArrayCache(5000/* default gc interval */, 256/* size */); $this->configLoader = $configLoader ?? (\stripos(PHP_OS, "win") === 0 ? new WindowsConfigLoader @@ -61,9 +61,9 @@ final class DoHConfig return false; } - public function getArtax(): Client + public function getHttpClient(): DelegateHttpClient { - return $this->artax; + return $this->httpClient; } public function getCache(): Cache diff --git a/lib/Internal/HttpsSocket.php b/lib/Internal/HttpsSocket.php index 26c39cb..4af5cac 100644 --- a/lib/Internal/HttpsSocket.php +++ b/lib/Internal/HttpsSocket.php @@ -2,10 +2,10 @@ namespace Amp\DoH\Internal; -use Amp\Artax\Client; -use Amp\Artax\Request; use Amp\DoH\DoHException; use Amp\DoH\Nameserver; +use Amp\Http\Client\DelegateHttpClient; +use Amp\Http\Client\Request; use Amp\Promise; use danog\LibDNSJson\JsonDecoderFactory; use danog\LibDNSJson\QueryEncoderFactory; @@ -17,7 +17,7 @@ use function Amp\call; /** @internal */ final class HttpsSocket extends Socket { - /** @var \Amp\Artax\Client */ + /** @var \Amp\Http\HttpClient */ private $httpClient; /** @var \Amp\DoH\Nameserver */ @@ -32,14 +32,14 @@ final class HttpsSocket extends Socket /** @var \Amp\Deferred */ private $responseDeferred; - public static function connect(Client $artax, Nameserver $nameserver): Socket + public static function connect(DelegateHttpClient $httpClient, Nameserver $nameserver): Socket { - return new self($artax, $nameserver); + return new self($httpClient, $nameserver); } - protected function __construct(Client $artax, Nameserver $nameserver) + protected function __construct(DelegateHttpClient $httpClient, Nameserver $nameserver) { - $this->httpClient = $artax; + $this->httpClient = $httpClient; $this->nameserver = $nameserver; if ($nameserver->getType() !== Nameserver::GOOGLE_JSON) { @@ -60,23 +60,24 @@ final class HttpsSocket extends Socket switch ($this->nameserver->getType()) { case Nameserver::RFC8484_GET: $data = $this->encoder->encode($message); - $request = (new Request($this->nameserver->getUri().'?'.\http_build_query(['dns' => \base64_encode($data), 'ct' => 'application/dns-message']), "GET")) - ->withHeader('accept', 'application/dns-message') - ->withHeaders($this->nameserver->getHeaders()); + $request = new Request($this->nameserver->getUri().'?'.\http_build_query(['dns' => \base64_encode($data), 'ct' => 'application/dns-message']), "GET"); + $request->setHeader('accept', 'application/dns-message'); + $request->setHeaders($this->nameserver->getHeaders()); break; case Nameserver::RFC8484_POST: $data = $this->encoder->encode($message); - $request = (new Request($this->nameserver->getUri(), "POST")) - ->withBody($data) - ->withHeader('content-type', 'application/dns-message') - ->withHeader('accept', 'application/dns-message') - ->withHeaders($this->nameserver->getHeaders()); + $request = new Request($this->nameserver->getUri(), "POST"); + $request->setBody($data); + $request->setHeader('content-type', 'application/dns-message'); + $request->setHeader('accept', 'application/dns-message'); + $request->setHeader('content-length', strlen($data)); + $request->setHeaders($this->nameserver->getHeaders()); break; case Nameserver::GOOGLE_JSON: $data = $this->encoder->encode($message); - $request = (new Request($this->nameserver->getUri().'?'.$data, "GET")) - ->withHeader('accept', 'application/dns-json') - ->withHeaders($this->nameserver->getHeaders()); + $request = new Request($this->nameserver->getUri().'?'.$data, "GET"); + $request->setHeader('accept', 'application/dns-json'); + $request->setHeaders($this->nameserver->getHeaders()); break; } $response = $this->httpClient->request($request); @@ -85,8 +86,7 @@ final class HttpsSocket extends Socket if ($response->getStatus() !== 200) { throw new DoHException("HTTP result !== 200: ".$response->getStatus()." ".$response->getReason(), $response->getStatus()); } - $response = yield $response->getBody(); - + $response = yield $response->getBody()->buffer(); switch ($this->nameserver->getType()) { case Nameserver::RFC8484_GET: diff --git a/lib/Internal/Socket.php b/lib/Internal/Socket.php index 0dee5b5..5debc5a 100644 --- a/lib/Internal/Socket.php +++ b/lib/Internal/Socket.php @@ -3,14 +3,13 @@ namespace Amp\DoH\Internal; use Amp; -use Amp\Artax\Client; -use Amp\Artax\Response; use Amp\ByteStream\StreamException; use Amp\Deferred; use Amp\Dns\DnsException; use Amp\Dns\TimeoutException; use Amp\DoH\DoHException; use Amp\DoH\Nameserver; +use Amp\Http\Client\DelegateHttpClient; use Amp\Promise; use LibDNS\Messages\Message; use LibDNS\Messages\MessageFactory; @@ -41,7 +40,7 @@ abstract class Socket * @return Promise */ - abstract public static function connect(Client $artax, Nameserver $nameserver): self; + abstract public static function connect(DelegateHttpClient $httpClient, Nameserver $nameserver): self; /** * @param Message $message @@ -147,7 +146,7 @@ abstract class Socket $message = "Unexpected error during resolution: ".$exception->getMessage(); $exception = new DnsException($message, 0, $exception); } - + $pending = $this->pending; $this->pending = []; diff --git a/lib/Rfc8484StubResolver.php b/lib/Rfc8484StubResolver.php index 3f6a2c5..8376d4f 100644 --- a/lib/Rfc8484StubResolver.php +++ b/lib/Rfc8484StubResolver.php @@ -147,7 +147,15 @@ final class Rfc8484StubResolver implements Resolver if ($reason instanceof NoRecordException) { throw $reason; } - $errors[] = $reason->getMessage(); + $error = $reason->getMessage(); + if ($reason instanceof MultiReasonException) { + $reasons = []; + foreach ($reason->getReasons() as $reason) { + $reasons []= $reason->getMessage(); + } + $error .= " (".implode(", ", $reasons).")"; + } + $errors[] = $error; } throw new DnsException("All query attempts failed for {$name}: ".\implode(", ", $errors), 0, $e); @@ -254,6 +262,8 @@ final class Rfc8484StubResolver implements Resolver $attemptDescription = []; + $exceptions = []; + while ($attempt < $attempts) { try { $attemptDescription[] = $nameserver; @@ -263,6 +273,7 @@ final class Rfc8484StubResolver implements Resolver $response = yield $socket->ask($question, $this->config->getTimeout()); } catch (DoHException $e) { // Defer call, because it might interfere with the unreference() call in Internal\Socket otherwise + $exceptions []= $e; $i = ++$attempt % \count($nameservers); $nameserver = $nameservers[$i]; @@ -319,13 +330,17 @@ final class Rfc8484StubResolver implements Resolver } } - throw new TimeoutException(\sprintf( + $timeout = new TimeoutException(\sprintf( "No response for '%s' (%s) from any nameserver after %d attempts, tried %s", $name, Record::getName($type), $attempts, \implode(", ", $attemptDescription) )); + if (!$exceptions) { + throw $timeout; + } + throw new MultiReasonException($exceptions, $timeout->getMessage()); }); $this->pendingQueries[$type." ".$name] = $promise; @@ -401,7 +416,7 @@ final class Rfc8484StubResolver implements Resolver return $this->sockets[$uri]; } - $this->sockets[$uri] = HttpsSocket::connect($this->dohConfig->getArtax(), $nameserver); + $this->sockets[$uri] = HttpsSocket::connect($this->dohConfig->getHttpClient(), $nameserver); return $this->sockets[$uri]; } diff --git a/test/DoHConfigTest.php b/test/DoHConfigTest.php index 1b84d09..13b5ed9 100644 --- a/test/DoHConfigTest.php +++ b/test/DoHConfigTest.php @@ -2,7 +2,6 @@ namespace Amp\DoH\Test; -use Amp\Artax\DefaultClient; use Amp\Cache\ArrayCache; use Amp\Dns\ConfigException; use Amp\Dns\Rfc1035StubResolver; @@ -10,6 +9,7 @@ use Amp\Dns\UnixConfigLoader; use Amp\Dns\WindowsConfigLoader; use Amp\DoH\DoHConfig; use Amp\DoH\Nameserver; +use Amp\Http\Client\HttpClientBuilder; use Amp\PHPUnit\TestCase; class DoHConfigTest extends TestCase @@ -31,8 +31,8 @@ class DoHConfigTest extends TestCase [[new Nameserver('https://cloudflare-dns.com/dns-query', Nameserver::RFC8484_POST)]], [[new Nameserver('https://cloudflare-dns.com/dns-query', Nameserver::RFC8484_GET)]], [[new Nameserver('https://cloudflare-dns.com/dns-query', Nameserver::GOOGLE_JSON)]], - [[new Nameserver('https://google.com/resolve', Nameserver::GOOGLE_JSON, ["host" => "dns.google.com"])]], - [[new Nameserver('https://cloudflare-dns.com/dns-query', Nameserver::GOOGLE_JSON), new Nameserver('https://google.com/resolve', Nameserver::GOOGLE_JSON, ["host" => "dns.google.com"])]], + [[new Nameserver('https://dns.google/resolve', Nameserver::GOOGLE_JSON)]], + [[new Nameserver('https://cloudflare-dns.com/dns-query', Nameserver::GOOGLE_JSON), new Nameserver('dns.google/resolve', Nameserver::GOOGLE_JSON)]], ]; } @@ -74,19 +74,19 @@ class DoHConfigTest extends TestCase } /** - * @param \Amp\Artax\Client $client Valid artax instance + * @param \Amp\Http\Client\DelegateHttpClient $client Valid HttpClient instance * - * @dataProvider provideValidArtax + * @dataProvider provideValidHttpClient */ - public function testAcceptsValidArtax($client) + public function testAcceptsValidHttpClient($client) { $this->assertInstanceOf(DoHConfig::class, new DoHConfig([new Nameserver('https://cloudflare-dns.com/dns-query')], $client)); } - public function provideValidArtax() + public function provideValidHttpClient() { return [ - [new DefaultClient()], + [HttpClientBuilder::buildDefault()], ]; } /** diff --git a/test/HttpsSocketTest.php b/test/HttpsSocketTest.php index af25029..d145d10 100644 --- a/test/HttpsSocketTest.php +++ b/test/HttpsSocketTest.php @@ -2,11 +2,11 @@ namespace Amp\DoH\Test; -use Amp\Artax\DefaultClient; use Amp\Dns; use Amp\DoH; use Amp\DoH\Internal\Socket; use Amp\DoH\Nameserver; +use Amp\Http\Client\HttpClientBuilder; use LibDNS\Records\QuestionFactory; use function Amp\Promise\wait; @@ -14,7 +14,7 @@ class HttpsSocketTest extends SocketTest { protected function connect(Nameserver $nameserver): Socket { - return DoH\Internal\HttpsSocket::connect(new DefaultClient(), $nameserver); + return DoH\Internal\HttpsSocket::connect(HttpClientBuilder::buildDefault(), $nameserver); } public function testTimeout() diff --git a/test/IntegrationTest.php b/test/IntegrationTest.php index 98d9cca..83a22b6 100644 --- a/test/IntegrationTest.php +++ b/test/IntegrationTest.php @@ -133,7 +133,7 @@ class IntegrationTest extends TestCase /** @var Record $record */ $record = $result[0]; - $this->assertSame("google-public-dns-b.google.com", $record->getValue()); + $this->assertSame("dns.google", $record->getValue()); $this->assertNotNull($record->getTtl()); $this->assertSame(Record::PTR, $record->getType()); }); @@ -194,7 +194,7 @@ class IntegrationTest extends TestCase ['https://mozilla.cloudflare-dns.com/dns-query', Nameserver::RFC8484_POST], ['https://mozilla.cloudflare-dns.com/dns-query', Nameserver::RFC8484_GET], ['https://mozilla.cloudflare-dns.com/dns-query', Nameserver::GOOGLE_JSON], - ['https://google.com/resolve', Nameserver::GOOGLE_JSON, ["Host" => "dns.google.com"]], + ['https://dns.google/resolve', Nameserver::GOOGLE_JSON], ]; $result = []; for ($start = 0; $start < \count($nameservers); $start++) { diff --git a/test/NameserverTest.php b/test/NameserverTest.php index 444922f..601319d 100644 --- a/test/NameserverTest.php +++ b/test/NameserverTest.php @@ -25,7 +25,7 @@ class NameserverTest extends TestCase ['https://mozilla.cloudflare-dns.com/dns-query', Nameserver::RFC8484_POST], ['https://mozilla.cloudflare-dns.com/dns-query', Nameserver::RFC8484_GET], ['https://mozilla.cloudflare-dns.com/dns-query', Nameserver::GOOGLE_JSON], - ['https://google.com/resolve', Nameserver::GOOGLE_JSON, ["Host" => "dns.google.com"]], + ['https://dns.google/resolve', Nameserver::GOOGLE_JSON], ]; } @@ -62,13 +62,13 @@ class NameserverTest extends TestCase ['http://mozilla.cloudflare-dns.com/dns-query', Nameserver::RFC8484_POST], ['http://mozilla.cloudflare-dns.com/dns-query', Nameserver::RFC8484_GET], ['http://mozilla.cloudflare-dns.com/dns-query', Nameserver::GOOGLE_JSON], - ['http://google.com/resolve', Nameserver::GOOGLE_JSON, ["Host" => "dns.google.com"]], + ['http://dns.google/resolve', Nameserver::GOOGLE_JSON], ['mozilla.cloudflare-dns.com/dns-query'], ['mozilla.cloudflare-dns.com/dns-query', Nameserver::RFC8484_POST], ['mozilla.cloudflare-dns.com/dns-query', Nameserver::RFC8484_GET], ['mozilla.cloudflare-dns.com/dns-query', Nameserver::GOOGLE_JSON], - ['google.com/resolve', Nameserver::GOOGLE_JSON, ["Host" => "dns.google.com"]], + ['dns.google/resolve', Nameserver::GOOGLE_JSON], ['https://mozilla.cloudflare-dns.com/dns-query', 100], ['https://mozilla.cloudflare-dns.com/dns-query', -1],