summaryrefslogtreecommitdiffstats
path: root/library/Director/Core/RestApiClient.php
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--library/Director/Core/RestApiClient.php276
1 files changed, 276 insertions, 0 deletions
diff --git a/library/Director/Core/RestApiClient.php b/library/Director/Core/RestApiClient.php
new file mode 100644
index 0000000..b0854ff
--- /dev/null
+++ b/library/Director/Core/RestApiClient.php
@@ -0,0 +1,276 @@
+<?php
+
+namespace Icinga\Module\Director\Core;
+
+use Icinga\Application\Benchmark;
+use RuntimeException;
+
+class RestApiClient
+{
+ protected $version = 'v1';
+
+ protected $peer;
+
+ protected $port;
+
+ protected $user;
+
+ protected $pass;
+
+ protected $curl;
+
+ protected $readBuffer = '';
+
+ protected $onEvent;
+
+ protected $onEventWantsRaw;
+
+ protected $keepAlive = true;
+
+ public function __construct($peer, $port = 5665, $cn = null)
+ {
+ $this->peer = $peer;
+ $this->port = $port;
+ }
+
+ // TODO: replace with Web2 CA trust resource plus cert and get rid
+ // of user/pass or at least strongly advise against using it
+ public function setCredentials($user, $pass)
+ {
+ $this->user = $user;
+ $this->pass = $pass;
+
+ return $this;
+ }
+
+ public function onEvent($callback, $raw = false)
+ {
+ $this->onEventWantsRaw = $raw;
+ $this->onEvent = $callback;
+
+ return $this;
+ }
+
+ public function getPeerIdentity()
+ {
+ return $this->peer;
+ }
+
+ public function setKeepAlive($keepAlive = true)
+ {
+ $this->keepAlive = (bool) $keepAlive;
+
+ return $this;
+ }
+
+ protected function url($url)
+ {
+ return sprintf('https://%s:%d/%s/%s', $this->peer, $this->port, $this->version, $url);
+ }
+
+ /**
+ * @param $method
+ * @param $url
+ * @param null $body
+ * @param bool $raw
+ * @param bool $stream
+ * @return RestApiResponse
+ */
+ public function request($method, $url, $body = null, $raw = false, $stream = false)
+ {
+ if (function_exists('curl_version')) {
+ return $this->curlRequest($method, $url, $body, $raw, $stream);
+ } else {
+ throw new RuntimeException(
+ 'No CURL extension detected, it must be installed and enabled'
+ );
+ }
+ }
+
+ protected function curlRequest($method, $url, $body = null, $raw = false, $stream = false)
+ {
+ $auth = sprintf('%s:%s', $this->user, $this->pass);
+ $headers = [
+ 'Host: ' . $this->getPeerIdentity(),
+ ];
+
+ if (! $this->keepAlive) {
+ // This fails on Icinga 2.9:
+ // $headers[] = 'Connection: close';
+ }
+
+ if (! $raw) {
+ $headers[] = 'Accept: application/json';
+ }
+
+ if ($body !== null) {
+ $body = Json::encode($body);
+ $headers[] = 'Content-Type: application/json';
+ }
+
+ $curl = $this->curl();
+ $opts = [
+ CURLOPT_URL => $this->url($url),
+ CURLOPT_HTTPHEADER => $headers,
+ CURLOPT_USERPWD => $auth,
+ CURLOPT_CUSTOMREQUEST => strtoupper($method),
+ CURLOPT_RETURNTRANSFER => true,
+ CURLOPT_CONNECTTIMEOUT => 3,
+
+ // TODO: Fix this!
+ CURLOPT_SSL_VERIFYHOST => false,
+ CURLOPT_SSL_VERIFYPEER => false,
+ ];
+
+ if ($body !== null) {
+ $opts[CURLOPT_POSTFIELDS] = $body;
+ }
+
+ if ($stream) {
+ $opts[CURLOPT_WRITEFUNCTION] = [$this, 'readPart'];
+ $opts[CURLOPT_TCP_NODELAY] = 1;
+ }
+
+ curl_setopt_array($curl, $opts);
+ // TODO: request headers, validate status code
+
+ Benchmark::measure('Rest Api, sending ' . $url);
+ $res = curl_exec($curl);
+ if ($res === false) {
+ $error = curl_error($curl);
+ $this->disconnect();
+
+ throw new RuntimeException("CURL ERROR: $error");
+ }
+
+ $statusCode = curl_getinfo($curl, CURLINFO_HTTP_CODE);
+ if ($statusCode === 401) {
+ $this->disconnect();
+ throw new RuntimeException(
+ 'Unable to authenticate, please check your API credentials'
+ );
+ }
+
+ Benchmark::measure('Rest Api, got response');
+ if (! $this->keepAlive) {
+ $this->disconnect();
+ }
+
+ if ($stream) {
+ return $this;
+ }
+
+ if ($raw) {
+ return $res;
+ } else {
+ return RestApiResponse::fromJsonResult($res);
+ }
+ }
+
+ /**
+ * @param resource $curl
+ * @param $data
+ * @return int
+ */
+ protected function readPart($curl, &$data)
+ {
+ $length = strlen($data);
+ $this->readBuffer .= $data;
+ $this->processEvents();
+ return $length;
+ }
+
+ public function get($url, $body = null)
+ {
+ return $this->request('get', $url, $body);
+ }
+
+ public function getRaw($url, $body = null)
+ {
+ return $this->request('get', $url, $body, true);
+ }
+
+ public function post($url, $body = null)
+ {
+ return $this->request('post', $url, $body);
+ }
+
+ public function put($url, $body = null)
+ {
+ return $this->request('put', $url, $body);
+ }
+
+ public function delete($url, $body = null)
+ {
+ return $this->request('delete', $url, $body);
+ }
+
+ /**
+ * @return resource
+ */
+ protected function curl()
+ {
+ if ($this->curl === null) {
+ $this->curl = curl_init(sprintf('https://%s:%d', $this->peer, $this->port));
+ if (! $this->curl) {
+ throw new RuntimeException('CURL INIT ERROR: ' . curl_error($this->curl));
+ }
+ }
+
+ return $this->curl;
+ }
+
+ protected function processEvents()
+ {
+ $offset = 0;
+ while (false !== ($pos = strpos($this->readBuffer, "\n", $offset))) {
+ if ($pos === $offset) {
+ // echo "Got empty line $offset / $pos\n";
+ $offset = $pos + 1;
+ continue;
+ }
+ $this->processReadBuffer($offset, $pos);
+
+ $offset = $pos + 1;
+ }
+
+ if ($offset > 0) {
+ $this->readBuffer = substr($this->readBuffer, $offset + 1);
+ }
+
+ // echo "REMAINING: " . strlen($this->readBuffer) . "\n";
+ }
+
+ protected function processReadBuffer($offset, $pos)
+ {
+ if ($this->onEvent === null) {
+ return;
+ }
+
+ $func = $this->onEvent;
+ $str = substr($this->readBuffer, $offset, $pos);
+ // printf("Processing %s bytes\n", strlen($str));
+
+ if ($this->onEventWantsRaw) {
+ $func($str);
+ } else {
+ $func(Json::decode($str));
+ }
+ }
+
+ public function disconnect()
+ {
+ if ($this->curl !== null) {
+ if (is_resource($this->curl)) {
+ @curl_close($this->curl);
+ }
+
+ $this->curl = null;
+ }
+ }
+
+ public function __destruct()
+ {
+ $this->disconnect();
+ }
+}