ReactPHP 的异步 DNS 解析器

v1.2.0 2019-08-15 09:06 UTC

README

Build Status

ReactPHP 的异步 DNS 解析器 ReactPHP

DNS 组件的主要目的是提供异步 DNS 解析。然而,它实际上是一个用于处理 DNS 消息的工具包,可以很容易地用来创建 DNS 服务器。

目录

基本用法

最基本的用法是通过解析器工厂创建一个解析器。你需要提供的只是域名服务器,然后你就可以开始解析名称了,宝贝!

$loop = React\EventLoop\Factory::create();

$config = React\Dns\Config\Config::loadSystemConfigBlocking();
$server = $config->nameservers ? reset($config->nameservers) : '8.8.8.8';

$factory = new React\Dns\Resolver\Factory();
$dns = $factory->create($server, $loop);

$dns->resolve('igor.io')->then(function ($ip) {
    echo "Host: $ip\n";
});

$loop->run();

请参阅第一个示例

可以使用 Config 类加载系统默认配置。这是一个可能访问文件系统并阻塞的操作。因此,理想情况下,应该在循环开始之前只执行一次,而不是在运行时重复执行。请注意,如果无法加载系统配置,该类可能会返回一个 配置。因此,如果你找不到任何配置,你可能需要像上面那样应用默认的域名服务器。

请注意,工厂在创建解析器实例时仅从文件系统中加载一次主机文件。因此,理想情况下,应该在循环开始之前只执行一次,而不是在运行时重复执行。

但还有更多。

缓存

你可以通过将解析器配置为使用 CachedExecutor 来缓存结果

$loop = React\EventLoop\Factory::create();

$config = React\Dns\Config\Config::loadSystemConfigBlocking();
$server = $config->nameservers ? reset($config->nameservers) : '8.8.8.8';

$factory = new React\Dns\Resolver\Factory();
$dns = $factory->createCached($server, $loop);

$dns->resolve('igor.io')->then(function ($ip) {
    echo "Host: $ip\n";
});

...

$dns->resolve('igor.io')->then(function ($ip) {
    echo "Host: $ip\n";
});

$loop->run();

如果第一次调用在第二次调用之前返回,则只会执行一个查询。第二个结果将从一个内存缓存中提供。这对于长时间运行的脚本非常有用,其中必须多次查找相同的域名。

请参阅第三个示例

自定义缓存适配器

默认情况下,上述将使用内存缓存。

你还可以指定一个自定义的 CacheInterface 实现来处理记录缓存

$cache = new React\Cache\ArrayCache();
$loop = React\EventLoop\Factory::create();
$factory = new React\Dns\Resolver\Factory();
$dns = $factory->createCached('8.8.8.8', $loop, $cache);

请参阅维基百科中可能的缓存实现

ResolverInterface

resolve()

可以使用 resolve(string $domain): PromiseInterface<string,Exception> 方法将给定的 $domain 名称解析为单个 IPv4 地址(类型 A 查询)。

$resolver->resolve('reactphp.org')->then(function ($ip) {
    echo 'IP for reactphp.org is ' . $ip . PHP_EOL;
});

这是本包中的主要方法之一。它向你的 DNS 服务器发送一个针对给定 $domain 名称的 DNS 查询,并在成功时返回单个 IP 地址。

如果 DNS 服务器发送的 DNS 响应消息包含此查询的多个 IP 地址,它将随机从响应中选取一个 IP 地址。如果你想获取完整的 IP 地址列表或想发送不同类型的查询,你应该使用 resolveAll() 方法。

如果 DNS 服务器发送的 DNS 响应消息指示错误代码,此方法将拒绝并抛出 RecordNotFoundException。你可以使用其消息和代码来检查响应代码。

如果 DNS 通信失败并且服务器没有返回有效的响应消息,则此消息将拒绝并抛出 Exception

可以通过取消其挂起的承诺来取消待处理的 DNS 查询,如下所示

$promise = $resolver->resolve('reactphp.org');

$promise->cancel();

resolveAll()

可以使用 resolveAll(string $host, int $type): PromiseInterface<array,Exception> 方法来解析给定 $domain 名称和查询 $type 的所有记录值。

$resolver->resolveAll('reactphp.org', Message::TYPE_A)->then(function ($ips) {
    echo 'IPv4 addresses for reactphp.org ' . implode(', ', $ips) . PHP_EOL;
});

$resolver->resolveAll('reactphp.org', Message::TYPE_AAAA)->then(function ($ips) {
    echo 'IPv6 addresses for reactphp.org ' . implode(', ', $ips) . PHP_EOL;
});

这是本包中的主要方法之一。它向您的 DNS 服务器发送针对给定 $domain 名称的 DNS 查询,并在成功时返回一个包含所有记录值的列表。

如果 DNS 服务器发送包含一个或多个该查询记录的 DNS 响应消息,则将返回一个包含所有记录值的列表。您可以使用 Message::TYPE_* 常量来控制要发送哪种类型的查询。请注意,此方法始终返回记录值列表,但每种记录值类型取决于查询类型。例如,它返回类型 A 查询的 IPv4 地址,类型 AAAA 查询的 IPv6 地址,类型 NSCNAMEPTR 查询的主机名以及其他查询的结构化数据。有关更多详细信息,请参阅 Record 文档。

如果 DNS 服务器发送的 DNS 响应消息指示错误代码,此方法将拒绝并抛出 RecordNotFoundException。你可以使用其消息和代码来检查响应代码。

如果 DNS 通信失败并且服务器没有返回有效的响应消息,则此消息将拒绝并抛出 Exception

可以通过取消其挂起的承诺来取消待处理的 DNS 查询,如下所示

$promise = $resolver->resolveAll('reactphp.org', Message::TYPE_AAAA);

$promise->cancel();

高级用法

UdpTransportExecutor

可以使用 UdpTransportExecutor 通过 UDP 传输发送 DNS 查询。

这是主要发送 DNS 查询到您的 DNS 服务器并由 Resolver 内部用于实际消息传输的类。

对于更高级的用法,可以直接使用此类。以下示例查找 igor.ioIPv6 地址。

$loop = Factory::create();
$executor = new UdpTransportExecutor('8.8.8.8:53', $loop);

$executor->query(
    new Query($name, Message::TYPE_AAAA, Message::CLASS_IN)
)->then(function (Message $message) {
    foreach ($message->answers as $answer) {
        echo 'IPv6: ' . $answer->data . PHP_EOL;
    }
}, 'printf');

$loop->run();

另请参阅第四个示例

请注意,此执行器不实现超时,因此您很可能会将其与 TimeoutExecutor 结合使用,如下所示

$executor = new TimeoutExecutor(
    new UdpTransportExecutor($nameserver, $loop),
    3.0,
    $loop
);

还请注意,此执行器使用不可靠的 UDP 传输,并且不实现任何重试逻辑,因此您很可能会将其与 RetryExecutor 结合使用,如下所示

$executor = new RetryExecutor(
    new TimeoutExecutor(
        new UdpTransportExecutor($nameserver, $loop),
        3.0,
        $loop
    )
);

请注意,此执行器完全异步,因此允许您并发执行任意数量的查询。您可能需要限制应用程序中的并发查询数量,否则您可能会在解析器端遇到速率限制和封禁。对于许多常见应用程序,当第一个查询仍在挂起时,您可能希望避免发送相同的查询,因此您可能需要将其与 CoopExecutor 结合使用,如下所示

$executor = new CoopExecutor(
    new RetryExecutor(
        new TimeoutExecutor(
            new UdpTransportExecutor($nameserver, $loop),
            3.0,
            $loop
        )
    )
);

内部,此类使用 PHP 的 UDP 套接字,并且出于组织原因没有利用 react/datagram,以避免两个包之间的循环依赖。高级组件应利用数据报组件,而不是从头实现此套接字逻辑。

TcpTransportExecutor

可以使用 TcpTransportExecutor 类通过 TCP/IP 流传输发送 DNS 查询。

这是发送 DNS 查询到您的 DNS 服务器的几个主要类之一。

对于更高级的用法,可以直接使用此类。以下示例查找 reactphp.orgIPv6 地址。

$loop = Factory::create();
$executor = new TcpTransportExecutor('8.8.8.8:53', $loop);

$executor->query(
    new Query($name, Message::TYPE_AAAA, Message::CLASS_IN)
)->then(function (Message $message) {
    foreach ($message->answers as $answer) {
        echo 'IPv6: ' . $answer->data . PHP_EOL;
    }
}, 'printf');

$loop->run();

另请参阅示例 #92

请注意,此执行器不实现超时,因此您很可能会将其与 TimeoutExecutor 结合使用,如下所示

$executor = new TimeoutExecutor(
    new TcpTransportExecutor($nameserver, $loop),
    3.0,
    $loop
);

UdpTransportExecutor 不同,此类使用可靠的 TCP/IP 传输,因此您不必实现任何重试逻辑。

请注意,此执行器完全异步,因此允许您并发执行查询。第一个查询将建立与DNS服务器的TCP/IP套接字连接,该连接将保持开放一段时间。额外的查询将自动重新使用到DNS服务器的现有套接字连接,将在单个连接上管道多个请求,并将空闲连接保持开放一段时间。如果您只偶尔发送查询,初始TCP/IP连接的开销可能会造成轻微的延迟 - 当您通过现有连接发送更多的并发查询时,它会变得越来越高效,并避免像基于UDP的执行器那样创建许多并发套接字。您可能仍然想限制您的应用程序中(并发)查询的数量,或者您可能面临解析器端的速度限制和禁令。对于许多常见应用,您可能希望在第一个查询仍然挂起时避免发送相同的查询多次,因此您可能会想结合使用像这样的CoopExecutor

$executor = new CoopExecutor(
    new TimeoutExecutor(
        new TcpTransportExecutor($nameserver, $loop),
        3.0,
        $loop
    )
);

内部,此类使用PHP的TCP/IP套接字,并且出于组织原因不利用react/socket,以避免这两个包之间的循环依赖。高级组件应该利用套接字组件,而不是从头开始重新实现此套接字逻辑。

SelectiveTransportExecutor

SelectiveTransportExecutor类可用于通过UDP或TCP/IP流传输发送DNS查询。

此类将自动选择正确的传输协议来向您的DNS服务器发送DNS查询。它将始终尝试通过更高效的UDP传输发送。如果此查询产生与大小相关的问题(消息截断),它将尝试通过流式TCP/IP传输重新发送。

对于更高级的用法,可以直接使用此类。以下示例查找 reactphp.orgIPv6 地址。

$executor = new SelectiveTransportExecutor($udpExecutor, $tcpExecutor);

$executor->query(
    new Query($name, Message::TYPE_AAAA, Message::CLASS_IN)
)->then(function (Message $message) {
    foreach ($message->answers as $answer) {
        echo 'IPv6: ' . $answer->data . PHP_EOL;
    }
}, 'printf');

请注意,此执行器仅实现了为给定DNS查询选择正确传输的逻辑。实现正确的传输逻辑、实现超时和任何重试逻辑由给定的执行器负责,有关更多详细信息,请参阅UdpTransportExecutorTcpTransportExecutor

请注意,此执行器完全异步,因此允许您并发执行任意数量的查询。您可能需要限制应用程序中的并发查询数量,否则您可能会在解析器端遇到速率限制和封禁。对于许多常见应用程序,当第一个查询仍在挂起时,您可能希望避免发送相同的查询,因此您可能需要将其与 CoopExecutor 结合使用,如下所示

$executor = new CoopExecutor(
    new SelectiveTransportExecutor(
        $datagramExecutor,
        $streamExecutor
    )
);

HostsFileExecutor

请注意,上述UdpTransportExecutor类始终执行实际的DNS查询。如果您还想考虑hosts文件中的条目,您可以使用此代码

$hosts = \React\Dns\Config\HostsFile::loadFromPathBlocking();

$executor = new UdpTransportExecutor('8.8.8.8:53', $loop);
$executor = new HostsFileExecutor($hosts, $executor);

$executor->query(
    new Query('localhost', Message::TYPE_A, Message::CLASS_IN)
);

安装

推荐通过Composer安装此库。 Composer新手?

此项目遵循SemVer。这将安装最新的支持版本

$ composer require react/dns:^1.2

有关版本升级的详细信息,请参阅CHANGELOG

此项目旨在在任意平台上运行,因此不要求任何PHP扩展,并支持在旧的PHP 5.3通过当前的PHP 7+和HHVM上运行。强烈建议使用PHP 7+来运行此项目。

测试

要运行测试套件,您首先需要克隆此仓库,然后通过Composer安装所有依赖项

$ composer install

要运行测试套件,请转到项目根目录并运行

$ php vendor/bin/phpunit

测试套件还包括一些依赖于稳定互联网连接的功能集成测试。如果您不想运行这些测试,可以简单地像这样跳过

$ php vendor/bin/phpunit --exclude-group internet

许可证

MIT,请参阅LICENSE文件

参考