Skip to content

Commit a0ddad1

Browse files
committed
Add "extended" host configuration syntax
Related to #442 #461
1 parent 3b81615 commit a0ddad1

File tree

3 files changed

+238
-4
lines changed

3 files changed

+238
-4
lines changed

docs/configuration.asciidoc

Lines changed: 39 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ their needs, but it is possible to completely replace much of the internals if r
77
Custom configuration is accomplished before the client is instantiated, through the ClientBuilder helper object.
88
We'll walk through all the configuration options and show sample code to replace the various components.
99

10-
=== Host Configuration
10+
=== Inline Host Configuration
1111

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

52+
=== Extended Host Configuration
53+
54+
The client also supports an _extended_ host configuration syntax. The inline configuration method relies on PHP's
55+
`filter_var()` and `parse_url()` methods to validate and extract the components of a URL. Unfortunately, these built-in
56+
methods run into problems with certain edge-cases. For example, `filter_var()` will not accept URL's that have underscores
57+
(which are questionably legal, depending on how you interpret the RFCs). Similarly, `parse_url()` will choke if a
58+
Basic Auth's password contains special characters such as a pound sign (`#`) or question-marks (`?`).
59+
60+
For this reason, the client supports an extended host syntax which provides greater control over host initialization.
61+
None of the components are validated, so edge-cases like underscores domain names will not cause problems.
62+
63+
The extended syntax is an array of parameters for each host:
64+
65+
[source,php]
66+
----
67+
$hosts = [
68+
// This is effectively equal to: "https://username:password!#$?*abc@foo.com:9200/"
69+
[
70+
'host' => 'foo.com',
71+
'port' => '9200',
72+
'scheme' => 'https',
73+
'user' => 'username',
74+
'password' => 'password!#$?*abc'
75+
],
76+
77+
// This is equal to "http://localhost:9200/"
78+
[
79+
'host' => 'localhost', // Only host is required
80+
]
81+
];
82+
$client = ClientBuilder::create() // Instantiate a new ClientBuilder
83+
->setHosts($hosts) // Set the hosts
84+
->build(); // Build the client object
85+
----
86+
87+
Only the `host` parameter is required for each configured host. If not provided, the default port is `9200`. The default
88+
scheme is `http`.
89+
5290
=== Authorization and Encryption
5391

5492
For details about HTTP Authorization and SSL encryption, please see link:_security.html[Authorization and SSL].

src/Elasticsearch/ClientBuilder.php

Lines changed: 30 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -540,19 +540,46 @@ private function getDefaultHost()
540540
private function buildConnectionsFromHosts($hosts)
541541
{
542542
if (is_array($hosts) === false) {
543-
throw new InvalidArgumentException('Hosts parameter must be an array of strings');
543+
$this->logger->error("Hosts parameter must be an array of strings, or an array of Connection hashes.");
544+
throw new InvalidArgumentException('Hosts parameter must be an array of strings, or an array of Connection hashes.');
544545
}
545546

546547
$connections = [];
547548
foreach ($hosts as $host) {
548-
$host = $this->prependMissingScheme($host);
549-
$host = $this->extractURIParts($host);
549+
if (is_string($host)) {
550+
$host = $this->prependMissingScheme($host);
551+
$host = $this->extractURIParts($host);
552+
} else if (is_array($host)) {
553+
$host = $this->normalizeExtendedHost($host);
554+
} else {
555+
$this->logger->error("Could not parse host: ".print_r($host, true));
556+
throw new RuntimeException("Could not parse host: ".print_r($host, true));
557+
}
550558
$connections[] = $this->connectionFactory->create($host);
551559
}
552560

553561
return $connections;
554562
}
555563

564+
/**
565+
* @param $host
566+
* @return array
567+
*/
568+
private function normalizeExtendedHost($host) {
569+
if (isset($host['host']) === false) {
570+
$this->logger->error("Required 'host' was not defined in extended format: ".print_r($host, true));
571+
throw new RuntimeException("Required 'host' was not defined in extended format: ".print_r($host, true));
572+
}
573+
574+
if (isset($host['scheme']) === false) {
575+
$host['scheme'] = 'http';
576+
}
577+
if (isset($host['port']) === false) {
578+
$host['port'] = '9200';
579+
}
580+
return $host;
581+
}
582+
556583
/**
557584
* @param array $host
558585
*

tests/Elasticsearch/Tests/ClientTest.php

Lines changed: 169 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
use Elasticsearch;
66
use Elasticsearch\ClientBuilder;
7+
use Elasticsearch\Connections\Connection;
78
use Mockery as m;
89

910
/**
@@ -266,4 +267,172 @@ public function testMaxRetriesException()
266267
throw $e;
267268
}
268269
}
270+
271+
public function testInlineHosts()
272+
{
273+
$client = Elasticsearch\ClientBuilder::create()->setHosts([
274+
'localhost:9200'
275+
])->build();
276+
277+
// We're casting to Connection here, instead of ConnectionInterface
278+
// so we can access getHost() on Connection
279+
280+
/** @var Connection $host */
281+
$host = $client->transport->getConnection();
282+
$this->assertEquals("localhost:9200", $host->getHost());
283+
$this->assertEquals("http", $host->getTransportSchema());
284+
285+
286+
$client = Elasticsearch\ClientBuilder::create()->setHosts([
287+
'http://localhost:9200'
288+
])->build();
289+
/** @var Connection $host */
290+
$host = $client->transport->getConnection();
291+
$this->assertEquals("localhost:9200", $host->getHost());
292+
$this->assertEquals("http", $host->getTransportSchema());
293+
294+
$client = Elasticsearch\ClientBuilder::create()->setHosts([
295+
'http://foo.com:9200'
296+
])->build();
297+
/** @var Connection $host */
298+
$host = $client->transport->getConnection();
299+
$this->assertEquals("foo.com:9200", $host->getHost());
300+
$this->assertEquals("http", $host->getTransportSchema());
301+
302+
$client = Elasticsearch\ClientBuilder::create()->setHosts([
303+
'https://foo.com:9200'
304+
])->build();
305+
/** @var Connection $host */
306+
$host = $client->transport->getConnection();
307+
$this->assertEquals("foo.com:9200", $host->getHost());
308+
$this->assertEquals("https", $host->getTransportSchema());
309+
310+
311+
// Note: we can't test user/pass themselves yet, need to introduce
312+
// breaking change to interface in master to do that
313+
// But we can confirm it doesn't break anything
314+
$client = Elasticsearch\ClientBuilder::create()->setHosts([
315+
'https://user:pass@foo.com:9200'
316+
])->build();
317+
/** @var Connection $host */
318+
$host = $client->transport->getConnection();
319+
$this->assertEquals("foo.com:9200", $host->getHost());
320+
$this->assertEquals("https", $host->getTransportSchema());
321+
}
322+
323+
public function testExtendedHosts()
324+
{
325+
$client = Elasticsearch\ClientBuilder::create()->setHosts([
326+
[
327+
'host' => 'localhost',
328+
'port' => 9200,
329+
'scheme' => 'http'
330+
]
331+
])->build();
332+
/** @var Connection $host */
333+
$host = $client->transport->getConnection();
334+
$this->assertEquals("localhost:9200", $host->getHost());
335+
$this->assertEquals("http", $host->getTransportSchema());
336+
337+
338+
$client = Elasticsearch\ClientBuilder::create()->setHosts([
339+
[
340+
'host' => 'foo.com',
341+
'port' => 9200,
342+
'scheme' => 'http'
343+
]
344+
])->build();
345+
/** @var Connection $host */
346+
$host = $client->transport->getConnection();
347+
$this->assertEquals("foo.com:9200", $host->getHost());
348+
$this->assertEquals("http", $host->getTransportSchema());
349+
350+
351+
$client = Elasticsearch\ClientBuilder::create()->setHosts([
352+
[
353+
'host' => 'foo.com',
354+
'port' => 9200,
355+
'scheme' => 'https'
356+
]
357+
])->build();
358+
/** @var Connection $host */
359+
$host = $client->transport->getConnection();
360+
$this->assertEquals("foo.com:9200", $host->getHost());
361+
$this->assertEquals("https", $host->getTransportSchema());
362+
363+
364+
$client = Elasticsearch\ClientBuilder::create()->setHosts([
365+
[
366+
'host' => 'foo.com',
367+
'scheme' => 'http'
368+
]
369+
])->build();
370+
/** @var Connection $host */
371+
$host = $client->transport->getConnection();
372+
$this->assertEquals("foo.com:9200", $host->getHost());
373+
$this->assertEquals("http", $host->getTransportSchema());
374+
375+
376+
$client = Elasticsearch\ClientBuilder::create()->setHosts([
377+
[
378+
'host' => 'foo.com'
379+
]
380+
])->build();
381+
/** @var Connection $host */
382+
$host = $client->transport->getConnection();
383+
$this->assertEquals("foo.com:9200", $host->getHost());
384+
$this->assertEquals("http", $host->getTransportSchema());
385+
386+
387+
$client = Elasticsearch\ClientBuilder::create()->setHosts([
388+
[
389+
'host' => 'foo.com',
390+
'port' => 9500,
391+
'scheme' => 'https'
392+
]
393+
])->build();
394+
/** @var Connection $host */
395+
$host = $client->transport->getConnection();
396+
$this->assertEquals("foo.com:9500", $host->getHost());
397+
$this->assertEquals("https", $host->getTransportSchema());
398+
399+
400+
try {
401+
$client = Elasticsearch\ClientBuilder::create()->setHosts([
402+
[
403+
'port' => 9200,
404+
'scheme' => 'http'
405+
]
406+
])->build();
407+
$this->fail("Expected RuntimeException from missing host, none thrown");
408+
} catch (Elasticsearch\Common\Exceptions\RuntimeException $e) {
409+
// good
410+
}
411+
412+
// Underscore host, questionably legal, but inline method would break
413+
$client = Elasticsearch\ClientBuilder::create()->setHosts([
414+
[
415+
'host' => 'the_foo.com'
416+
]
417+
])->build();
418+
/** @var Connection $host */
419+
$host = $client->transport->getConnection();
420+
$this->assertEquals("the_foo.com:9200", $host->getHost());
421+
$this->assertEquals("http", $host->getTransportSchema());
422+
423+
424+
// Special characters in user/pass, would break inline
425+
$client = Elasticsearch\ClientBuilder::create()->setHosts([
426+
[
427+
'host' => 'foo.com',
428+
'user' => 'user',
429+
'pass' => 'abc#$%!abc'
430+
]
431+
])->build();
432+
/** @var Connection $host */
433+
$host = $client->transport->getConnection();
434+
$this->assertEquals("foo.com:9200", $host->getHost());
435+
$this->assertEquals("http", $host->getTransportSchema());
436+
437+
}
269438
}

0 commit comments

Comments
 (0)