Resolving DNS Asynchronously With ReactPHP
Basic Usage
It is always much more convenient to use domain names instead of IPs addresses. ReactPHP DNS Component provides this lookup feature for you. To start using it first you should create a resolver via factory React\Dns\Resolver\Factory
. Its create()
method accepts a nameserver and an instance of the event loop.
In the example above we have created a DNS resolver with Google nameserver.
Notice! Factory
create()
method loads your systemhosts
file. This method usesfile_get_contents()
function to load the contents of the file, which means that when being executed it blocks the loop. This may be an issue if youhosts
file is too huge or is located on a slow device. So, a good practice is to create a resolver once before the loop starts, not while it is already running.
Then to start resolving IP addresses we use method resolve()
on the resolver. Because things happen asynchronously resolve()
method returns a promise (read this article if you are new to promises):
When a domain is resolved onFulfilled
handler of the promise is called. It will receive a resolved IP address as an argument. If resolving fails onRejected
handler is called. This handler receives an instance of the React\Dns\RecordNotFoundException
:
The output of this script will be the following:
The full example (handling both success and failure) can be the following:
There may be situations when we don’t want to wait too long for a pending request. For example, if we haven’t received IP address in 2 seconds we don’t care anymore. The resolve()
method returns a promise, so we can use this object and later cancel it:
You can also use Promise Timeouts for this example:
By default
resolve()
method tries to resolve a domain name twice for 5 seconds.
Caching
For situations when you are going to resolve the same domain many times you can use a cached resolver. It will store all results in memory and next time when you try to resolve a domain which has already been resolved it will return its IP address from a cache. No additional queries will be executed.
You can use the same factory to create a cached resolver. But this time createCached()
method is being used:
A script where the same domain has to looked up several times:
In the snippet above the second call will be served from a cache. By default, an in-memory (React\Cache\Array
) cache is being used but you can specify your own implementation of the React\Cache\CacheInterface
. It is an async, promise-based cache interface. Then simply pass an instance of your own cache as a third argument to the createCached()
method:
Custom DNS queries
React\Dns\Resolve\Resolver
doesn’t make queries itself, instead, it proxies resolve calls to another executor class (React\Dns\Query\Executor
). This class actually performs all queries. Let’s create an instance of it. The constructor accepts four arguments:
- an instance of the event loop
- an instance of the
React\Dns\Protoco\Parser
class. This class is responsible for parsing raw binary data. - an instance of the
React\Dns\Protocol\BinaryDumper
class, which is used to convert the request to a binary data. - a timeout, which is currently deprecated and you should pass
null
.
Class Executor
implements React\Dns\Query\ExecutorInterface
which has only one public method query($nameserver, Query $query)
. This method accepts a nameserver string and React\Dns\Query
object. Under the hood, when you call resolve()
on a resolver object, it creates an instance of the Query
object and passes it to the executor:
And here the customization comes. We can create our own custom Query
object. In the constructor, the most interesting argument is the second one ($type
). It is a string containing the types of records being requested. It requires some knowledge how DNS works. Here are some popular record types:
React\Dns\Model\Message::TYPE_A
The most frequently used is address or A type. This type of record maps an IPv4 address to a domain name.React\Dns\Model\Message::TYPE_CNAME
The canonical name (CNAME) is used for aliases, for example when we have domain with and without www.React\Dns\Model\Message::TYPE_MX
MX records point to a mail server. When you send email toadmin@mydomain.com
, the MX record tells your email server where to send the email.React\Dns\Model\Message::TYPE_AAAA
is an equivalent ofTYPE_A
but for IPv6.
Class
React\Dns\Model\Message
contains 8 different constants related to DNS record types. Take a look at this class when you need to request some specific record.
Now, let’s get IPv6 address for php.net. First, we need to create a new Query
object:
Then pass this object to the executor query()
method. This method returns a promise so we can add onFulfilled
handler to receive the results:
Notice! onFulfilled
handler receives an instance of the React\Dns\Model\Message
class. This class has a public property $answers
, which is an array of React\Dns\Model\Record
class instances. To get the actual address we can grab it from its public property $data
. The result of this script:
Resolver and Executor
You can notice that a handler for Executor
receives Message
object which contains an array of answers (DNS records) for a specified domain and type. But when we use Resolver
, its handler receives only one address. Lets check on google.com.
Using Resolver
and Factory
:
And using custom Executor
and Query
:
Such different results are explained by the fact that under the hood, Resolver
parses Message
object and returns a random address from the $answers
variable. Here is the source code of the Resolver::extractAddress()
method:
Also, Resolver
when being created by the Factory
doesn’t use only Executor
class. The Factory
wraps an instance of the Executor
in several decorators before passing it to the Resolver
constructor as a dependency:
TimeoutExecutor
which will cancel resolving in 5 seconds (by default). Uses PromiseTimer Component under the hood.RetryExecutor
which tries twice (by default) to resolve a domain ifTimeoutException
was thrown.HostsFileExecutor
which tries to resolve a domain fromhosts
file in your system.CachedExecutor
is used only when creating a cached resolver viacreateCached()
method.
You can find examples from this article on GitHub.
This article is a part of the ReactPHP Series.
Learning Event-Driven PHP With ReactPHP
The book about asynchronous PHP that you NEED!
A complete guide to writing asynchronous applications with ReactPHP. Discover event-driven architecture and non-blocking I/O with PHP!
Minimum price: 5.99$