Skip to content
Open
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
203 changes: 203 additions & 0 deletions src/Query/ExecutorFactory.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,203 @@
<?php

namespace React\Dns\Query;

use React\Cache\ArrayCache;
use React\Cache\CacheInterface;
use React\Dns\Config\Config;
use React\Dns\Config\HostsFile;
use React\EventLoop\Loop;
use React\EventLoop\LoopInterface;

class ExecutorFactory
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This pretty much creates what most end users need to use this component. IMHO using Factory as name tells enough, and since this object holds no state, we can make all methods static.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Never mind this comment's part on naming, missed that it is in the Query directory. But we can still make all methods static IMHO.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

hm, yes it is possible.... @clue ???

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No strong opinion here, but not sure I quite like the idea of going more into factory land. I understand it's not optimal, but perhaps adding a new method to the existing factory class helps reduce the API surface?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

in respect to the upcoming DI/DX in react, a more granular distribution of classes give the best opportunity to combine it in more fancy ways and a test-case can be more precisely. so we should it keep in two classes. the static way can be interest but not really needed, i would it leave as it is ...

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Been thinking about this over the weekend, and part of the issue of creating another factory is that we'd start putting them everywhere. And you have to start creating aliases for them if you use more than one. Not hard opinions here either.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

as long as the reactPHP libs does not depend and implement an DI/DX container, a factory is a good alternate. later we can refactor to DI and can exchange or run in parallel mode.

{

/**
* Creates a DNS executer instance for the given DNS config
*
* As of v1.7.0 it's recommended to pass a `Config` object instead of a
* single nameserver address. If the given config contains more than one DNS
* nameserver, all DNS nameservers will be used in order. The primary DNS
* server will always be used first before falling back to the secondary or
* tertiary DNS server.
*
* @param Config|string $config DNS Config object (recommended) or single nameserver address
* @param ?LoopInterface $loop
* @return ExecutorInterface|HostsFileExecutor
* @throws \InvalidArgumentException for invalid DNS server address
* @throws \UnderflowException when given DNS Config object has an empty list of nameservers
*/
public function create($config, LoopInterface $loop = null)
{
return $this->decorateHostsFileExecutor($this->createExecutor($config, $loop ?: Loop::get()));
}

/**
* Creates a cached DNS executer instance for the given DNS config and cache
*
* As of v1.7.0 it's recommended to pass a `Config` object instead of a
* single nameserver address. If the given config contains more than one DNS
* nameserver, all DNS nameservers will be used in order. The primary DNS
* server will always be used first before falling back to the secondary or
* tertiary DNS server.
*
* @param Config|string $config DNS Config object (recommended) or single nameserver address
* @param ?LoopInterface $loop
* @param ?CacheInterface $cache
* @return ExecutorInterface|HostsFileExecutor
* @throws \InvalidArgumentException for invalid DNS server address
* @throws \UnderflowException when given DNS Config object has an empty list of nameservers
*/
public function createCached($config, LoopInterface $loop = null, CacheInterface $cache = null)
{
// default to keeping maximum of 256 responses in cache unless explicitly given
if (!($cache instanceof CacheInterface)) {
$cache = new ArrayCache(256);
}

$executor = $this->createExecutor($config, $loop ?: Loop::get());
$executor = new CachingExecutor($executor, $cache);
$executor = $this->decorateHostsFileExecutor($executor);

return $executor;
}

/**
* Tries to load the hosts file and decorates the given executor on success
*
* @param ExecutorInterface $executor
* @return ExecutorInterface
* @codeCoverageIgnore
*/
protected function decorateHostsFileExecutor(ExecutorInterface $executor)
{
try {
$executor = new HostsFileExecutor(
HostsFile::loadFromPathBlocking(),
$executor
);
} catch (\RuntimeException $e) {
// ignore this file if it can not be loaded
}

// Windows does not store localhost in hosts file by default but handles this internally
// To compensate for this, we explicitly use hard-coded defaults for localhost
if (DIRECTORY_SEPARATOR === '\\') {
$executor = new HostsFileExecutor(
new HostsFile("127.0.0.1 localhost\n::1 localhost"),
$executor
);
}

return $executor;
}

/**
* @param Config|string $nameserver
* @param LoopInterface $loop
* @return CoopExecutor
* @throws \InvalidArgumentException for invalid DNS server address
* @throws \UnderflowException when given DNS Config object has an empty list of nameservers
*/
protected function createExecutor($nameserver, LoopInterface $loop)
{
if ($nameserver instanceof Config) {
if (!$nameserver->nameservers) {
throw new \UnderflowException('Empty config with no DNS servers');
}

// Hard-coded to check up to 3 DNS servers to match default limits in place in most systems (see MAXNS config).
// Note to future self: Recursion isn't too hard, but how deep do we really want to go?
$primary = reset($nameserver->nameservers);
$secondary = next($nameserver->nameservers);
$tertiary = next($nameserver->nameservers);

if ($tertiary !== false) {
// 3 DNS servers given => nest first with fallback for second and third
return new CoopExecutor(
new RetryExecutor(
new FallbackExecutor(
$this->createSingleExecutor($primary, $loop),
new FallbackExecutor(
$this->createSingleExecutor($secondary, $loop),
$this->createSingleExecutor($tertiary, $loop)
)
)
)
);
} elseif ($secondary !== false) {
// 2 DNS servers given => fallback from first to second
return new CoopExecutor(
new RetryExecutor(
new FallbackExecutor(
$this->createSingleExecutor($primary, $loop),
$this->createSingleExecutor($secondary, $loop)
)
)
);
} else {
// 1 DNS server given => use single executor
$nameserver = $primary;
}
}

return new CoopExecutor(new RetryExecutor($this->createSingleExecutor($nameserver, $loop)));
}

/**
* @param string $nameserver
* @param LoopInterface $loop
* @return ExecutorInterface
* @throws \InvalidArgumentException for invalid DNS server address
*/
protected function createSingleExecutor($nameserver, LoopInterface $loop)
{
$parts = \parse_url($nameserver);

if (isset($parts['scheme']) && $parts['scheme'] === 'tcp') {
$executor = $this->createTcpExecutor($nameserver, $loop);
} elseif (isset($parts['scheme']) && $parts['scheme'] === 'udp') {
$executor = $this->createUdpExecutor($nameserver, $loop);
} else {
$executor = new SelectiveTransportExecutor(
$this->createUdpExecutor($nameserver, $loop),
$this->createTcpExecutor($nameserver, $loop)
);
}

return $executor;
}

/**
* @param string $nameserver
* @param LoopInterface $loop
* @return TimeoutExecutor
* @throws \InvalidArgumentException for invalid DNS server address
*/
protected function createTcpExecutor($nameserver, LoopInterface $loop)
{
return new TimeoutExecutor(
new TcpTransportExecutor($nameserver, $loop),
5.0,
$loop
);
}

/**
* @param string $nameserver
* @param LoopInterface $loop
* @return TimeoutExecutor
* @throws \InvalidArgumentException for invalid DNS server address
*/
protected function createUdpExecutor($nameserver, LoopInterface $loop)
{
return new TimeoutExecutor(
new UdpTransportExecutor(
$nameserver,
$loop
),
5.0,
$loop
);
}
}
Loading