Resolving DNS Asynchronously With ReactPHP
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
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.
create()method loads your system
hostsfile. This method uses
file_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 you
hostsfile 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
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:
resolve()method tries to resolve a domain name twice for 5 seconds.
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
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\Parserclass. This class is responsible for parsing raw binary data.
- an instance of the
React\Dns\Protocol\BinaryDumperclass, which is used to convert the request to a binary data.
- a timeout, which is currently deprecated and you should pass
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_AThe 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_CNAMEThe canonical name (CNAME) is used for aliases, for example when we have domain with and without www.
React\Dns\Model\Message::TYPE_MXMX records point to a mail server. When you send email to
firstname.lastname@example.org, the MX record tells your email server where to send the email.
React\Dns\Model\Message::TYPE_AAAAis an equivalent of
TYPE_Abut for IPv6.
React\Dns\Model\Messagecontains 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
Then pass this object to the executor
query() method. This method returns a promise so we can add
onFulfilled handler to receive the results:
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
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.
And using custom
Such different results are explained by the fact that under the hood,
Message object and returns a random address from the
$answers variable. Here is the source code of the
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:
TimeoutExecutorwhich will cancel resolving in 5 seconds (by default). Uses PromiseTimer Component under the hood.
RetryExecutorwhich tries twice (by default) to resolve a domain if
HostsFileExecutorwhich tries to resolve a domain from
hostsfile in your system.
CachedExecutoris used only when creating a cached resolver via
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$