Skip to content

Commit c1fca88

Browse files
committed
[CH3370] Support publishing events via ld-relay
1 parent 4e7022a commit c1fca88

File tree

6 files changed

+173
-59
lines changed

6 files changed

+173
-59
lines changed

composer.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@
2727
"zendframework/zend-serializer": "^2.7"
2828
},
2929
"suggest": {
30-
"guzzlehttp/guzzle": "(^6.2.1) Required when using the default FeatureRequester",
30+
"guzzlehttp/guzzle": "(^6.2.1) Required when using RelayEventPublisher or the default FeatureRequester",
3131
"kevinrob/guzzle-cache-middleware": "(^1.4.1) Recommended for performance when using the default FeatureRequester",
3232
"predis/predis": "(^1.0) Required when using LDDFeatureRequester"
3333
},
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
<?php
2+
namespace LaunchDarkly;
3+
4+
class CurlEventPublisher implements EventPublisher
5+
{
6+
private $_sdkKey;
7+
private $_host;
8+
private $_port;
9+
private $_ssl;
10+
private $_curl = '/usr/bin/env curl';
11+
12+
function __construct($apiKey, array $options = array()) {
13+
$this->_sdkKey = $apiKey;
14+
15+
$eventsUri = LDClient::DEFAULT_EVENTS_URI;
16+
if (isset($options['events_uri'])) {
17+
$eventsUri = $options['events_uri'];
18+
}
19+
$url = parse_url(rtrim($eventsUri,'/'));
20+
$this->_host = $url['host'];
21+
$this->_ssl = $url['scheme'] === 'https';
22+
if (isset($url['port'])) {
23+
$this->_port = $url['port'];
24+
}
25+
else {
26+
$this->_port = $this->_ssl ? 443 : 80;
27+
}
28+
if (isset($url['path'])) {
29+
$this->_path = $url['path'];
30+
}
31+
else {
32+
$this->_path = '';
33+
}
34+
35+
if (array_key_exists('curl', $options)) {
36+
$this->_curl = $options['curl'];
37+
}
38+
}
39+
40+
public function publish($payload) {
41+
$args = $this->createArgs($payload);
42+
43+
return $this->makeRequest($args);
44+
}
45+
46+
private function createArgs($payload) {
47+
$scheme = $this->_ssl ? "https://" : "http://";
48+
$args = " -X POST";
49+
$args.= " -H 'Content-Type: application/json'";
50+
$args.= " -H " . escapeshellarg("Authorization: " . $this->_sdkKey);
51+
$args.= " -H 'User-Agent: PHPClient/" . LDClient::VERSION . "'";
52+
$args.= " -H 'Accept: application/json'";
53+
$args.= " -d " . escapeshellarg($payload);
54+
$args.= " " . escapeshellarg($scheme . $this->_host . ":" . $this->_port . $this->_path . "/bulk");
55+
return $args;
56+
}
57+
58+
private function makeRequest($args) {
59+
$cmd = $this->_curl . " " . $args . ">> /dev/null 2>&1 &";
60+
shell_exec($cmd);
61+
return true;
62+
}
63+
}

src/LaunchDarkly/EventProcessor.php

Lines changed: 23 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -6,44 +6,13 @@
66
*/
77
class EventProcessor {
88

9-
private $_sdkKey;
9+
private $_eventPublisher;
1010
private $_queue;
1111
private $_capacity;
1212
private $_timeout;
13-
private $_host;
14-
private $_port;
15-
private $_ssl;
16-
private $_curl = '/usr/bin/env curl';
1713

1814
public function __construct($apiKey, $options = array()) {
19-
$this->_sdkKey = $apiKey;
20-
if (!isset($options['events_uri'])) {
21-
$this->_host = 'events.launchdarkly.com';
22-
$this->_port = 443;
23-
$this->_ssl = true;
24-
$this->_path = '';
25-
}
26-
else {
27-
$url = parse_url(rtrim($options['events_uri'],'/'));
28-
$this->_host = $url['host'];
29-
$this->_ssl = $url['scheme'] === 'https';
30-
if (isset($url['port'])) {
31-
$this->_port = $url['port'];
32-
}
33-
else {
34-
$this->_port = $this->_ssl ? 443 : 80;
35-
}
36-
if (isset($url['path'])) {
37-
$this->_path = $url['path'];
38-
}
39-
else {
40-
$this->_path = '';
41-
}
42-
}
43-
44-
if (array_key_exists('curl', $options)) {
45-
$this->_curl = $options['curl'];
46-
}
15+
$this->_eventPublisher = $this->getEventPublisher($apiKey, $options);
4716

4817
$this->_capacity = $options['capacity'];
4918
$this->_timeout = $options['timeout'];
@@ -76,28 +45,29 @@ protected function flush() {
7645

7746
$payload = json_encode($this->_queue);
7847

79-
$args = $this->createArgs($payload);
80-
81-
return $this->makeRequest($args);
48+
return $this->_eventPublisher->publish($payload);
8249
}
8350

84-
private function createArgs($payload) {
85-
$scheme = $this->_ssl ? "https://" : "http://";
86-
$args = " -X POST";
87-
$args.= " -H 'Content-Type: application/json'";
88-
$args.= " -H " . escapeshellarg("Authorization: " . $this->_sdkKey);
89-
$args.= " -H 'User-Agent: PHPClient/" . LDClient::VERSION . "'";
90-
$args.= " -H 'Accept: application/json'";
91-
$args.= " -d " . escapeshellarg($payload);
92-
$args.= " " . escapeshellarg($scheme . $this->_host . ":" . $this->_port . $this->_path . "/bulk");
93-
return $args;
94-
}
95-
96-
private function makeRequest($args) {
97-
$cmd = $this->_curl . " " . $args . ">> /dev/null 2>&1 &";
98-
shell_exec($cmd);
99-
return true;
100-
}
51+
/**
52+
* @param string $sdkKey
53+
* @param mixed[] $options
54+
* @return EventPublisher
55+
*/
56+
private function getEventPublisher($sdkKey, array $options)
57+
{
58+
if (isset($options['event_publisher']) && $options['event_publisher'] instanceof EventPublisher) {
59+
return $options['event_publisher'];
60+
}
10161

62+
if (isset($options['event_publisher_class'])) {
63+
$eventPublisherClass = $options['event_publisher_class'];
64+
} else {
65+
$eventPublisherClass = CurlEventPublisher::class;
66+
}
10267

68+
if (!is_a($eventPublisherClass, EventPublisher::class, true)) {
69+
throw new \InvalidArgumentException;
70+
}
71+
return new $eventPublisherClass($sdkKey, $options);
72+
}
10373
}
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
<?php
2+
namespace LaunchDarkly;
3+
4+
interface EventPublisher {
5+
/**
6+
* @param string $sdkKey The SDK key for your account
7+
* @param mixed[] $options Client configuration settings
8+
*/
9+
public function __construct($sdkKey, array $options);
10+
11+
/**
12+
* Publish events to LaunchDarkly
13+
*
14+
* @param $payload string Event payload
15+
* @return bool Whether the events were successfully published
16+
*/
17+
public function publish($payload);
18+
}

src/LaunchDarkly/LDClient.php

Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -10,12 +10,15 @@
1010
*/
1111
class LDClient {
1212
const DEFAULT_BASE_URI = 'https://app.launchdarkly.com';
13+
const DEFAULT_EVENTS_URI = 'https://events.launchdarkly.com';
1314
const VERSION = '2.1.2';
1415

1516
/** @var string */
1617
protected $_sdkKey;
1718
/** @var string */
1819
protected $_baseUri;
20+
/** @var string */
21+
protected $_eventsUri;
1922
/** @var EventProcessor */
2023
protected $_eventProcessor;
2124
/** @var bool */
@@ -26,8 +29,7 @@ class LDClient {
2629
protected $_defaults = array();
2730
/** @var LoggerInterface */
2831
protected $_logger;
29-
30-
/** @var FeatureRequester */
32+
/** @var FeatureRequester */
3133
protected $_featureRequester;
3234

3335
/**
@@ -43,6 +45,10 @@ class LDClient {
4345
* - send_events: An optional bool that can disable the sending of events to LaunchDarkly. Defaults to false.
4446
* - logger: An optional Psr\Log\LoggerInterface. Defaults to a Monolog\Logger sending all messages to the php error_log.
4547
* - offline: An optional boolean which will disable all network calls and always return the default value. Defaults to false.
48+
* - feature_requester: An optional LaunchDarkly\FeatureRequester instance.
49+
* - feature_requester_class: An optional class implementing LaunchDarkly\FeatureRequester, if `feature_requester` is not specified. Defaults to GuzzleFeatureRequester.
50+
* - event_publisher: An optional LaunchDarkly\EventPublisher instance.
51+
* - event_publisher_class: An optional class implementing LaunchDarkly\EventPublisher, if `event_publisher` is not specified. Defaults to CurlEventPublisher.
4652
*/
4753
public function __construct($sdkKey, $options = array()) {
4854
$this->_sdkKey = $sdkKey;
@@ -51,6 +57,11 @@ public function __construct($sdkKey, $options = array()) {
5157
} else {
5258
$this->_baseUri = rtrim($options['base_uri'], '/');
5359
}
60+
if (!isset($options['events_uri'])) {
61+
$this->_eventsUri = self::DEFAULT_EVENTS_URI;
62+
} else {
63+
$this->_eventsUri = rtrim($options['events_uri'], '/');
64+
}
5465
if (isset($options['send_events'])) {
5566
$this->_send_events = $options['send_events'];
5667
}
@@ -82,15 +93,15 @@ public function __construct($sdkKey, $options = array()) {
8293

8394
$this->_eventProcessor = new EventProcessor($sdkKey, $options);
8495

85-
$this->_featureRequester = $this->getFeatureRequester($options, $sdkKey);
96+
$this->_featureRequester = $this->getFeatureRequester($sdkKey, $options);
8697
}
8798

8899
/**
89-
* @param mixed[] $options
90100
* @param string $sdkKey
101+
* @param mixed[] $options
91102
* @return FeatureRequester
92103
*/
93-
private function getFeatureRequester(array $options, $sdkKey)
104+
private function getFeatureRequester($sdkKey, array $options)
94105
{
95106
if (isset($options['feature_requester']) && $options['feature_requester'] instanceof FeatureRequester) {
96107
return $options['feature_requester'];
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
<?php
2+
namespace LaunchDarkly;
3+
4+
use GuzzleHttp\Client;
5+
use Psr\Log\LoggerInterface;
6+
7+
class RelayEventPublisher implements EventPublisher
8+
{
9+
/** @var string */
10+
private $_sdkKey;
11+
/** @var string */
12+
private $_eventsUri;
13+
/** @var LoggerInterface */
14+
private $_logger;
15+
/** @var mixed[] */
16+
private $_requestOptions;
17+
18+
function __construct($apiKey, array $options = array()) {
19+
$this->_sdkKey = $apiKey;
20+
$this->_logger = $options['logger'];
21+
if (isset($options['events_uri'])) {
22+
$this->_eventsUri = $options['events_uri'];
23+
} else {
24+
$this->_eventsUri = LDClient::DEFAULT_EVENTS_URI;
25+
}
26+
$this->_requestOptions = [
27+
'headers' => [
28+
'Content-Type' => 'application/json',
29+
'Authorization' => $this->_sdkKey,
30+
'User-Agent' => 'PHPClient/' . LDClient::VERSION,
31+
'Accept' => 'application/json'
32+
],
33+
'timeout' => $options['timeout'],
34+
'connect_timeout' => $options['connect_timeout']
35+
];
36+
}
37+
38+
public function publish($payload) {
39+
$client = new Client(['base_uri' => $this->_eventsUri]);
40+
41+
try {
42+
$options = $this->_requestOptions;
43+
$options['body'] = $payload;
44+
$response = $client->request('POST', '/bulk', $options);
45+
46+
return $response->getStatusCode() < 300;
47+
} catch (\Exception $e) {
48+
$this->_logger->warning("RelayEventPublisher::publish caught $e");
49+
return false;
50+
}
51+
}
52+
}

0 commit comments

Comments
 (0)