icicleio / dns
Icicle 的异步 DNS。
Requires
- daverandom/libdns: ^1
- icicleio/icicle: ^0.9.1
- icicleio/socket: ^0.5
- paragonie/random_compat: ^1
Requires (Dev)
- mockery/mockery: ^0.9
- phpunit/phpunit: ^4.6
- symfony/yaml: ^2.7
README
此库是 Icicle 的组件之一,提供异步 DNS 查询执行器、解析器和客户端连接器。目前异步 DNS 服务器正在开发中,将来将被添加到该组件中。像其他 Icicle 组件一样,此库使用从 Awaitables 和 Generators 构建的自定义协程来使异步代码的编写更类似于同步代码。
要求
- PHP 5.5+ 用于 v0.6.x 分支(当前稳定)和 v1.x 分支(镜像当前稳定)
- PHP 7 用于 v2.0 分支(开发中)支持生成器委托和返回表达式
安装
推荐的安装方法是使用 Composer 包管理器。(有关安装和使用 Composer 的信息,请参阅 Composer 安装指南。)
运行以下命令以在您的项目中使用此库
composer require icicleio/dns
您还可以手动编辑 composer.json
以将此库添加为项目需求。
// composer.json { "require": { "icicleio/dns": "^0.6" } }
示例
下面的示例使用 Icicle\Dns\resolver()
函数异步查找域名 icicle.io
的 IP 地址。
use Icicle\Coroutine\Coroutine; use Icicle\Dns; use Icicle\Loop; $generator = function ($domain) { try { $ips = (yield Dns\resolve($domain)); foreach ($ips as $ip) { echo "IP: {$ip}\n"; } } catch (Exception $exception) { echo "Error when executing query: {$exception->getMessage()}\n"; } }; $coroutine = new Coroutine($generator('icicle.io')); $coroutine->done(); Loop\run();
文档
返回 Generator
的方法可用于创建 Coroutine(例如,new Coroutine($executor->execute(...))
)或在其中另一个 Coroutine 中产生(在 PHP 7 中使用 yield from
以获得更好的性能)。
此库使用 LibDNS 创建和解析 DNS 消息。遗憾的是,此库的文档目前仅限于源代码中的 DocComments。如果只在此库中使用解析器和连接器,则无需担心 LibDNS 的工作原理。Executors 返回使用 LibDNS\Messages\Message
实例解析的协程,这些实例表示 DNS 服务器的响应。使用这些对象很简单,将在以下执行器部分中描述。
函数原型
以下语法用于描述对象实例方法的原型
ClassOrInterfaceName::methodName(ArgumentType $arg): ReturnType
执行器
执行器是 DNS 组件的基础,执行任何 DNS 查询并返回该查询的完整结果。解析器和连接器依赖于执行器来执行其操作所需的 DNS 查询。
每个执行器都实现了 Icicle\Dns\Executor\Executor
,它定义了一个方法,即 execute()
。
Executor::execute( string $domain, string|int $type, array $options = [] ): \Generator
如果执行器在 timeout
秒内没有收到响应,它将重试查询多次。查询失败前重试的次数由 retries
定义,每次查询尝试之间都会间隔 timeout
秒。
execute() 函数
执行 DNS 查询的最简单方法是使用 Icicle\Dns\execute()
函数。此函数使用一个 Icicle\Dns\Executor\Executor
对象,该对象可以通过 Icicle\Dns\executor()
函数设置或检索。
`Icicle\Dns\execute( string $domain, string|int $type, array $options = [] ): \Generator
使用 execute()
函数查找域的 A 记录的示例
use Icicle\Coroutine\Coroutine; use Icicle\Dns; use Icicle\Loop; use LibDNS\Messages\Message; $coroutine = new Coroutine(Dns\execute('google.com', 'A')); $coroutine->done( function (Message $message) { foreach ($message->getAnswerRecords() as $resource) { echo "TTL: {$resource->getTTL()} Value: {$resource->getData()}\n"; } }, function (Exception $exception) { echo "Query failed: {$exception->getMessage()}\n"; } ); Loop\run();
创建执行器
最简单的执行器是 Icicle\Dns\Executor\BasicExecutor
,通过将 DNS 服务器的 IP 地址提供给构造函数来创建。建议使用离您最近的 DNS 服务器,例如本地路由器。如果不可能,可以使用 Google 运营的两个 DNS 服务器,地址为 8.8.8.8
和 8.8.4.4
。
use Icicle\Dns\Executor\BasicExecutor; $executor = new BasicExecutor('8.8.8.8');
如果需要自定义连接到名称服务器的行为,则 Icicle\Dns\Executor\BasicExecutor
构造函数还接受一个 Icicle\Socket\Connector\Connector
实例作为第二个参数。如果没有提供实例,则使用默认的全局连接器(见 Icicle\Socket\connector()
)。
使用执行器
创建后,可以通过调用带有要执行的域名和类型的 execute()
方法来使用执行器。类型可以是命名记录类型的无大小写字符串(例如,'A'
、'MX'
、'NS'
、'PTR'
、'AAAA'
)或对应于记录类型的整数值(LibDNS\Records\ResourceQTypes
定义了与类型整数值对应的常量)。execute()
返回一个协程,该协程完成一个代表名称服务器响应的 LibDNS\Messages\Message
实例。LibDNS\Messages\Message
对象有几个方法需要用于检索响应中的数据。
getAnswerRecords()
:返回一个LibDNS\Records\RecordCollection
实例,该实例是包含响应答案记录的可遍历集合。getAuthorityRecords()
:返回一个包含响应授权记录的LibDNS\Records\RecordCollection
实例。getAdditionalRecords()
:返回一个包含响应附加记录的LibDNS\Records\RecordCollection
实例。getAuthorityRecords()
:返回一个包含响应授权记录的LibDNS\Records\RecordCollection
实例。isAuthoritative()
:确定响应是否为返回的记录授权。
可遍历的 LibDNS\Records\RecordCollection
对象中的 DNS 记录表示为 LibDNS\Records\Resource
实例。这些对象有多个方法可以用来访问与记录关联的数据。
getType()
:返回记录类型作为integer
。getName()
:获取与记录关联的域名作为string
。getData()
:返回一个代表记录数据的LibDNS\Records\RData
实例。此对象可以被转换为string
,或者可以通过LibDNS\Records\RData::getField(int $index)
方法访问每个字段。资源中的字段数量取决于资源类型(例如,MX
记录包含两个字段,一个优先级和一个主机名)。getTTL()
:获取 TTL(生存时间)作为integer
。
以下是一个如何使用执行器查找域的 NS 记录的示例。
use Icicle\Coroutine\Coroutine; use Icicle\Dns\Executor\BasicExecutor; use Icicle\Loop; use LibDNS\Messages\Message; $executor = new BasicExecutor('8.8.8.8'); $coroutine = new Coroutine($executor->execute('google.com', 'NS')); $coroutine->done( function (Message $message) { foreach ($message->getAnswerRecords() as $resource) { echo "TTL: {$resource->getTTL()} Value: {$resource->getData()}\n"; } }, function (Exception $exception) { echo "Query failed: {$exception->getMessage()}\n"; } ); Loop\run();
MultiExecutor
可以使用 Icicle\Dns\Executor\MultiExecutor
类将多个执行器组合起来,向多个名称服务器发送查询,以便即使某些名称服务器停止响应,查询也能得到解决。
use Icicle\Coroutine\Coroutine; use Icicle\Dns\Executor\BasicExecutor; use Icicle\Dns\Executor\MultiExecutor; use Icicle\Loop; use LibDNS\Messages\Message; $executor = new MultiExecutor(); $executor->add(new BasicExecutor('8.8.8.8')); $executor->add(new BasicExecutor('8.8.4.4')); // Executor will send query to 8.8.4.4 if 8.8.8.8 does not respond. $coroutine = new Coroutine($executor->execute('google.com', 'MX')); $coroutine->done( function (Message $message) { foreach ($message->getAnswerRecords() as $resource) { echo "TTL: {$resource->getTTL()} Value: {$resource->getData()}\n"; } }, function (Exception $exception) { echo "Query failed: {$exception->getMessage()}\n"; } ); Loop\run();
使用上述执行器的查询将自动向第二个名称服务器发送请求,如果第一个没有响应。后续查询最初发送到最后一个成功响应查询的服务器。
解析器
解析器用于查找给定域的IP地址。Icicle\Dns\Resolver\BasicResolver
实现了Icicle\Dns\Resolver\Resolver
接口,该接口定义了一个单一的方法,即resolve()
。解析器本质上是一个专门用于执行仅A
查询的执行器,通过一个IP地址数组(即使只找到一个或零个IP地址,协程仍然通过数组来解析)来满足从resolve()
返回的协程。
Resolver::resolve( string $domain, array $options = [] ): \Generator
resolve() 函数
找到域IP地址的最简单方法就是使用Icicle\Dns\resolve()
函数。这个函数使用一个Icicle\Dns\Resolver\Resolver
对象,该对象可以通过Icicle\Dns\resolver()
函数进行设置或获取。
`Icicle\Dns\resolve( string $domain, array $options = [] ): \Generator
使用resolve()
函数解析域IP地址的示例
use Icicle\Coroutine\Coroutine; use Icicle\Dns; use Icicle\Loop; $coroutine = new Coroutine(Dns\resolve('google.com')); $coroutine->done( function (array $ips) { foreach ($ips as $ip) { echo "IP: {$ip}\n"; } }, function (Exception $exception) { echo "Query failed: {$exception->getMessage()}\n"; } ); Loop\run();
与执行器类似,如果域名服务器在timeout
秒内没有响应,解析器将重试查询retries
次。
Icicle\Resolver\BasicResolver
类通过传递一个用于执行解析域名查询的Icicle\Executor\Executor
实例来构建。如果没有提供执行器,则默认创建一个,使用8.8.8.8
和8.8.4.4
作为执行器的DNS服务器。
示例
use Icicle\Coroutine\Coroutine; use Icicle\Dns\Resolver\BasicResolver; use Icicle\Loop; $resolver = new BasicResolver(); $coroutine = new Coroutine($resolver->resolve('google.com')); $coroutine->done( function (array $ips) { foreach ($ips as $ip) { echo "IP: {$ip}\n"; } }, function (Exception $exception) { echo "Query failed: {$exception->getMessage()}\n"; } ); Loop\run();
连接器
连接器组件首先解析提供的主机名,然后建立连接,并使用Icicle\Socket\Socket
实例解析返回的协程。
Icicle\Dns\Connector\Connector
定义了一个单一的方法connect()
,该方法应解析主机名并连接到解析出的服务器之一,并通过连接的客户机解析协程。
Connector::connect( string $domain, int $port, array $options = [], ): \Generator
connect() 函数
解析域名并连接到解析主机上端口的简单方法就是使用Icicle\Dns\connect()
函数。这个函数使用一个Icicle\Dns\Connector\Connector
对象,该对象可以通过Icicle\Dns\connector()
函数进行设置或获取。
`Icicle\Dns\connect( string $domain, int $port, array $options = [] ): \Generator
使用connect()
函数解析域IP地址并连接到端口443的示例
use Icicle\Dns; use Icicle\Loop; use Icicle\Socket\Socket; $connector = new DefaultConnector(); $coroutine = new Coroutine(Dns\connect('google.com', 443)); $coroutine->done( function (Socket $client) { echo "IP: {$client->getRemoteAddress()}\n"; echo "Port: {$client->getRemotePort()}\n"; }, function (Exception $exception) { echo "Connecting failed: {$exception->getMessage()}\n"; } ); Loop\run();
Icicle\Dns\Connector\DefaultConnector
将尝试连接到给定主机名找到的IP地址之一。如果该IP的服务器无响应,连接器将尝试连接到列表中的下一个IP,直到服务器接受连接。只有当连接器无法连接到所有IP时,才会拒绝connect()
返回的协程。构造函数还可选地接受一个Icicle\Socket\Connector\Connector
实例,如果需要在连接到解析的主机时实现自定义行为。
此外,还可以使用Icicle\Socket\Connector\Connector::connect()
的所有其他选项。
示例
use Icicle\Dns\Connector\DefaultConnector; use Icicle\Loop; use Icicle\Socket\Socket; $connector = new DefaultConnector(); $coroutine = new Coroutine($connector->connect('google.com', 80)); $coroutine->done( function (Socket $client) { echo "IP: {$client->getRemoteAddress()}\n"; echo "Port: {$client->getRemotePort()}\n"; }, function (Exception $exception) { echo "Connecting failed: {$exception->getMessage()}\n"; } ); Loop\run();