Skip to content

Commit

Permalink
Add "extended" host configuration syntax
Browse files Browse the repository at this point in the history
Related to #442 #461
  • Loading branch information
polyfractal committed Sep 12, 2016
1 parent 3b81615 commit a0ddad1
Show file tree
Hide file tree
Showing 3 changed files with 238 additions and 4 deletions.
40 changes: 39 additions & 1 deletion docs/configuration.asciidoc
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ their needs, but it is possible to completely replace much of the internals if r
Custom configuration is accomplished before the client is instantiated, through the ClientBuilder helper object.
We'll walk through all the configuration options and show sample code to replace the various components.

=== Host Configuration
=== Inline Host Configuration

The most common configuration is telling the client about your cluster: how many nodes, their addresses and ports. If
no hosts are specified, the client will attempt to connect to `localhost:9200`.
Expand Down Expand Up @@ -49,6 +49,44 @@ $clientBuilder->setHosts($hosts); // Set the hosts
$client = $clientBuilder->build(); // Build the client object
----

=== Extended Host Configuration

The client also supports an _extended_ host configuration syntax. The inline configuration method relies on PHP's
`filter_var()` and `parse_url()` methods to validate and extract the components of a URL. Unfortunately, these built-in
methods run into problems with certain edge-cases. For example, `filter_var()` will not accept URL's that have underscores
(which are questionably legal, depending on how you interpret the RFCs). Similarly, `parse_url()` will choke if a
Basic Auth's password contains special characters such as a pound sign (`#`) or question-marks (`?`).

For this reason, the client supports an extended host syntax which provides greater control over host initialization.
None of the components are validated, so edge-cases like underscores domain names will not cause problems.

The extended syntax is an array of parameters for each host:

[source,php]
----
$hosts = [
// This is effectively equal to: "https://username:password!#$?*[email protected]:9200/"
[
'host' => 'foo.com',
'port' => '9200',
'scheme' => 'https',
'user' => 'username',
'password' => 'password!#$?*abc'
],
// This is equal to "http://localhost:9200/"
[
'host' => 'localhost', // Only host is required
]
];
$client = ClientBuilder::create() // Instantiate a new ClientBuilder
->setHosts($hosts) // Set the hosts
->build(); // Build the client object
----

Only the `host` parameter is required for each configured host. If not provided, the default port is `9200`. The default
scheme is `http`.

=== Authorization and Encryption

For details about HTTP Authorization and SSL encryption, please see link:_security.html[Authorization and SSL].
Expand Down
33 changes: 30 additions & 3 deletions src/Elasticsearch/ClientBuilder.php
Original file line number Diff line number Diff line change
Expand Up @@ -540,19 +540,46 @@ private function getDefaultHost()
private function buildConnectionsFromHosts($hosts)
{
if (is_array($hosts) === false) {
throw new InvalidArgumentException('Hosts parameter must be an array of strings');
$this->logger->error("Hosts parameter must be an array of strings, or an array of Connection hashes.");
throw new InvalidArgumentException('Hosts parameter must be an array of strings, or an array of Connection hashes.');
}

$connections = [];
foreach ($hosts as $host) {
$host = $this->prependMissingScheme($host);
$host = $this->extractURIParts($host);
if (is_string($host)) {
$host = $this->prependMissingScheme($host);
$host = $this->extractURIParts($host);
} else if (is_array($host)) {
$host = $this->normalizeExtendedHost($host);
} else {
$this->logger->error("Could not parse host: ".print_r($host, true));
throw new RuntimeException("Could not parse host: ".print_r($host, true));
}
$connections[] = $this->connectionFactory->create($host);
}

return $connections;
}

/**
* @param $host
* @return array
*/
private function normalizeExtendedHost($host) {
if (isset($host['host']) === false) {
$this->logger->error("Required 'host' was not defined in extended format: ".print_r($host, true));
throw new RuntimeException("Required 'host' was not defined in extended format: ".print_r($host, true));
}

if (isset($host['scheme']) === false) {
$host['scheme'] = 'http';
}
if (isset($host['port']) === false) {
$host['port'] = '9200';
}
return $host;
}

/**
* @param array $host
*
Expand Down
169 changes: 169 additions & 0 deletions tests/Elasticsearch/Tests/ClientTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

use Elasticsearch;
use Elasticsearch\ClientBuilder;
use Elasticsearch\Connections\Connection;
use Mockery as m;

/**
Expand Down Expand Up @@ -266,4 +267,172 @@ public function testMaxRetriesException()
throw $e;
}
}

public function testInlineHosts()
{
$client = Elasticsearch\ClientBuilder::create()->setHosts([
'localhost:9200'
])->build();

// We're casting to Connection here, instead of ConnectionInterface
// so we can access getHost() on Connection

/** @var Connection $host */
$host = $client->transport->getConnection();
$this->assertEquals("localhost:9200", $host->getHost());
$this->assertEquals("http", $host->getTransportSchema());


$client = Elasticsearch\ClientBuilder::create()->setHosts([
'http://localhost:9200'
])->build();
/** @var Connection $host */
$host = $client->transport->getConnection();
$this->assertEquals("localhost:9200", $host->getHost());
$this->assertEquals("http", $host->getTransportSchema());

$client = Elasticsearch\ClientBuilder::create()->setHosts([
'http://foo.com:9200'
])->build();
/** @var Connection $host */
$host = $client->transport->getConnection();
$this->assertEquals("foo.com:9200", $host->getHost());
$this->assertEquals("http", $host->getTransportSchema());

$client = Elasticsearch\ClientBuilder::create()->setHosts([
'https://foo.com:9200'
])->build();
/** @var Connection $host */
$host = $client->transport->getConnection();
$this->assertEquals("foo.com:9200", $host->getHost());
$this->assertEquals("https", $host->getTransportSchema());


// Note: we can't test user/pass themselves yet, need to introduce
// breaking change to interface in master to do that
// But we can confirm it doesn't break anything
$client = Elasticsearch\ClientBuilder::create()->setHosts([
'https://user:[email protected]:9200'
])->build();
/** @var Connection $host */
$host = $client->transport->getConnection();
$this->assertEquals("foo.com:9200", $host->getHost());
$this->assertEquals("https", $host->getTransportSchema());
}

public function testExtendedHosts()
{
$client = Elasticsearch\ClientBuilder::create()->setHosts([
[
'host' => 'localhost',
'port' => 9200,
'scheme' => 'http'
]
])->build();
/** @var Connection $host */
$host = $client->transport->getConnection();
$this->assertEquals("localhost:9200", $host->getHost());
$this->assertEquals("http", $host->getTransportSchema());


$client = Elasticsearch\ClientBuilder::create()->setHosts([
[
'host' => 'foo.com',
'port' => 9200,
'scheme' => 'http'
]
])->build();
/** @var Connection $host */
$host = $client->transport->getConnection();
$this->assertEquals("foo.com:9200", $host->getHost());
$this->assertEquals("http", $host->getTransportSchema());


$client = Elasticsearch\ClientBuilder::create()->setHosts([
[
'host' => 'foo.com',
'port' => 9200,
'scheme' => 'https'
]
])->build();
/** @var Connection $host */
$host = $client->transport->getConnection();
$this->assertEquals("foo.com:9200", $host->getHost());
$this->assertEquals("https", $host->getTransportSchema());


$client = Elasticsearch\ClientBuilder::create()->setHosts([
[
'host' => 'foo.com',
'scheme' => 'http'
]
])->build();
/** @var Connection $host */
$host = $client->transport->getConnection();
$this->assertEquals("foo.com:9200", $host->getHost());
$this->assertEquals("http", $host->getTransportSchema());


$client = Elasticsearch\ClientBuilder::create()->setHosts([
[
'host' => 'foo.com'
]
])->build();
/** @var Connection $host */
$host = $client->transport->getConnection();
$this->assertEquals("foo.com:9200", $host->getHost());
$this->assertEquals("http", $host->getTransportSchema());


$client = Elasticsearch\ClientBuilder::create()->setHosts([
[
'host' => 'foo.com',
'port' => 9500,
'scheme' => 'https'
]
])->build();
/** @var Connection $host */
$host = $client->transport->getConnection();
$this->assertEquals("foo.com:9500", $host->getHost());
$this->assertEquals("https", $host->getTransportSchema());


try {
$client = Elasticsearch\ClientBuilder::create()->setHosts([
[
'port' => 9200,
'scheme' => 'http'
]
])->build();
$this->fail("Expected RuntimeException from missing host, none thrown");
} catch (Elasticsearch\Common\Exceptions\RuntimeException $e) {
// good
}

// Underscore host, questionably legal, but inline method would break
$client = Elasticsearch\ClientBuilder::create()->setHosts([
[
'host' => 'the_foo.com'
]
])->build();
/** @var Connection $host */
$host = $client->transport->getConnection();
$this->assertEquals("the_foo.com:9200", $host->getHost());
$this->assertEquals("http", $host->getTransportSchema());


// Special characters in user/pass, would break inline
$client = Elasticsearch\ClientBuilder::create()->setHosts([
[
'host' => 'foo.com',
'user' => 'user',
'pass' => 'abc#$%!abc'
]
])->build();
/** @var Connection $host */
$host = $client->transport->getConnection();
$this->assertEquals("foo.com:9200", $host->getHost());
$this->assertEquals("http", $host->getTransportSchema());

}
}

0 comments on commit a0ddad1

Please sign in to comment.