Passed
Push — master ( 4a930e...4b35d1 )
by Timo
22:47
created

AbstractSolrService::_sendRawPost()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 9
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 6
CRAP Score 1

Importance

Changes 0
Metric Value
eloc 5
dl 0
loc 9
ccs 6
cts 6
cp 1
rs 10
c 0
b 0
f 0
cc 1
nc 1
nop 3
crap 1
1
<?php
2
namespace ApacheSolrForTypo3\Solr\System\Solr\Service;
3
4
/***************************************************************
5
 *  Copyright notice
6
 *
7
 *  (c) 2009-2017 Timo Hund <[email protected]>
8
 *  All rights reserved
9
 *
10
 *  This script is part of the TYPO3 project. The TYPO3 project is
11
 *  free software; you can redistribute it and/or modify
12
 *  it under the terms of the GNU General Public License as published by
13
 *  the Free Software Foundation; either version 3 of the License, or
14
 *  (at your option) any later version.
15
 *
16
 *  The GNU General Public License can be found at
17
 *  http://www.gnu.org/copyleft/gpl.html.
18
 *
19
 *  This script is distributed in the hope that it will be useful,
20
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
21
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
22
 *  GNU General Public License for more details.
23
 *
24
 *  This copyright notice MUST APPEAR in all copies of the script!
25
 ***************************************************************/
26
27
use ApacheSolrForTypo3\Solr\PingFailedException;
28
use ApacheSolrForTypo3\Solr\System\Configuration\TypoScriptConfiguration;
29
use ApacheSolrForTypo3\Solr\System\Logging\SolrLogManager;
30
use ApacheSolrForTypo3\Solr\System\Solr\ResponseAdapter;
31
use ApacheSolrForTypo3\Solr\Util;
32
33
use Solarium\Client;
34
use Solarium\Core\Client\Endpoint;
35
use Solarium\Core\Client\Request;
36
use Solarium\Core\Query\QueryInterface;
37
use Solarium\Exception\HttpException;
38
use TYPO3\CMS\Core\Utility\GeneralUtility;
39
40
abstract class AbstractSolrService {
41
42
    /**
43
     * @var array
44
     */
45
    protected static $pingCache = [];
46
47
    /**
48
     * @var TypoScriptConfiguration
49
     */
50
    protected $configuration;
51
52
    /**
53
     * @var \ApacheSolrForTypo3\Solr\System\Logging\SolrLogManager
54
     */
55
    protected $logger = null;
56
57
    /**
58
     * @var Client
59
     */
60
    protected $client = null;
61
62
    /**
63
     * SolrReadService constructor.
64
     */
65 131
    public function __construct(Client $client, $typoScriptConfiguration = null, $logManager = null)
66
    {
67 131
        $this->client = $client;
68 131
        $this->configuration = $typoScriptConfiguration ?? Util::getSolrConfiguration();
69 131
        $this->logger = $logManager ?? GeneralUtility::makeInstance(SolrLogManager::class, /** @scrutinizer ignore-type */ __CLASS__);
70 131
    }
71
72
    /**
73
     * @deprecated Since 9.0.0 will be removed in 10.0.0 please use getPrimaryEndpoint()->getScheme() now.
74
     * @return string
75
     */
76 2
    public function getScheme()
77
    {
78 2
        $endpoint = $this->getPrimaryEndpoint();
79 2
        return is_null($endpoint) ? '' : $endpoint->getScheme();
80
    }
81
82
    /**
83
     * @deprecated Since 9.0.0 will be removed in 10.0.0 please use getPrimaryEndpoint()->getHost() now.
84
     * @return string
85
     */
86 2
    public function getHost()
87
    {
88 2
        $endpoint = $this->getPrimaryEndpoint();
89 2
        return is_null($endpoint) ? '' : $endpoint->getHost();
90
    }
91
92
    /**
93
     * @deprecated Since 9.0.0 will be removed in 10.0.0 please use getPrimaryEndpoint()->getPort() now.
94
     * @return string
95
     */
96 2
    public function getPort()
97
    {
98 2
        $endpoint = $this->getPrimaryEndpoint();
99 2
        return is_null($endpoint) ? '' : $endpoint->getPort();
100
    }
101
102
    /**
103
     * @deprecated Since 9.0.0 will be removed in 10.0.0 please use getCorePath() now
104
     * @return string
105
     */
106 2
    public function getPath()
107
    {
108 2
        return $this->getCorePath();
109
    }
110
111
    /**
112
     * Returns the path to the core solr path + core path.
113
     *
114
     * @return string
115
     */
116 2
    public function getCorePath()
117
    {
118 2
        $endpoint = $this->getPrimaryEndpoint();
119 2
        return is_null($endpoint) ? '' : $endpoint->getPath() .'/'. $endpoint->getCore();
120
    }
121
122
    /**
123
     * Returns the core name from the configured path without the core name.
124
     *
125
     * @deprecated Since 9.0.0 will be removed in 10.0.0 please use getPrimaryEndpoint()->getPath() now.
126
     * @return string
127
     */
128
    public function getCoreBasePath()
129
    {
130
        $endpoint = $this->getPrimaryEndpoint();
131
        return is_null($endpoint) ? '' : $endpoint->getPath();
132
    }
133
134
    /**
135
     * Returns the core name from the configured path.
136
     *
137
     * @deprecated Since 9.0.0 will be removed in 10.0.0 please use getPrimaryEndpoint()->getCore() now.
138
     * @return string
139
     */
140
    public function getCoreName()
141
    {
142
        $endpoint = $this->getPrimaryEndpoint();
143
        return is_null($endpoint) ? '' : $endpoint->getCore();
144
    }
145
146
    /**
147
     * Return a valid http URL given this server's host, port and path and a provided servlet name
148
     *
149
     * @param string $servlet
150
     * @param array $params
151
     * @return string
152
     */
153 21
    protected function _constructUrl($servlet, $params = [])
154
    {
155 21
        $queryString = count($params) ? '?' . http_build_query($params, null, '&') : '';
156 21
        return $this->__toString() . $servlet . $queryString;
157
    }
158
159
    /**
160
     * Creates a string representation of the Solr connection. Specifically
161
     * will return the Solr URL.
162
     *
163
     * @return string The Solr URL.
164
     */
165 93
    public function __toString()
166
    {
167 93
        $endpoint = $this->getPrimaryEndpoint();
168 93
        if (!$endpoint instanceof Endpoint) {
169 2
            return '';
170
        }
171
172 91
        return  $endpoint->getScheme(). '://' . $endpoint->getHost() . ':' . $endpoint->getPort() . $endpoint->getPath() . '/' . $endpoint->getCore() . '/';
173
    }
174
175
    /**
176
     * @return Endpoint|null
177
     */
178 98
    public function getPrimaryEndpoint()
179
    {
180 98
        return is_array($this->client->getEndpoints()) ? reset($this->client->getEndpoints()) : null;
0 ignored issues
show
introduced by
The condition is_array($this->client->getEndpoints()) is always true.
Loading history...
181
    }
182
183
    /**
184
     * Central method for making a get operation against this Solr Server
185
     *
186
     * @param string $url
187
     * @return ResponseAdapter
188
     */
189 15
    protected function _sendRawGet($url)
190
    {
191 15
        return $this->_sendRawRequest($url, Request::METHOD_GET);
192
    }
193
194
    /**
195
     * Central method for making a HTTP DELETE operation against the Solr server
196
     *
197
     * @param string $url
198
     * @return ResponseAdapter
199
     */
200 4
    protected function _sendRawDelete($url)
201
    {
202 4
        return $this->_sendRawRequest($url, Request::METHOD_DELETE);
203
    }
204
205
    /**
206
     * Central method for making a post operation against this Solr Server
207
     *
208
     * @param string $url
209
     * @param string $rawPost
210
     * @param string $contentType
211
     * @return ResponseAdapter
212
     */
213
    protected function _sendRawPost($url, $rawPost, $contentType = 'text/xml; charset=UTF-8')
214
    {
215 4
        $initializeRequest = function(Request $request) use ($rawPost, $contentType) {
216 4
            $request->setRawData($rawPost);
217 4
            $request->addHeader('Content-Type: ' . $contentType);
218 4
            return $request;
219 4
        };
220
221 4
        return $this->_sendRawRequest($url, Request::METHOD_POST, $rawPost, $initializeRequest);
222
    }
223
224
    /**
225
     * Method that performs an http request with the solarium client.
226
     *
227
     * @param string $url
228
     * @param string $method
229
     * @param string $body
230
     * @param \Closure $initializeRequest
231
     * @return ResponseAdapter
232
     */
233 16
    protected function _sendRawRequest($url, $method = Request::METHOD_GET, $body = '', \Closure $initializeRequest = null)
234
    {
235 16
        $logSeverity = SolrLogManager::INFO;
236 16
        $exception = null;
237
238
        try {
239 16
            $request = $this->buildSolariumRequestFromUrl($url, $method);
240
241 16
            if($initializeRequest !== null) {
242 5
                $request = $initializeRequest($request);
243
            }
244
245 16
            $response = $this->executeRequest($request);
246 3
        } catch (HttpException $exception) {
247 3
            $logSeverity = SolrLogManager::ERROR;
248 3
            $response = new ResponseAdapter($exception->getBody(), $exception->getCode(), $exception->getMessage());
249
        }
250
251 16
        if ($this->configuration->getLoggingQueryRawPost() || $response->getHttpStatus() != 200) {
252 3
            $message = 'Querying Solr using '.$method;
253 3
            $this->writeLog($logSeverity, $message, $url, $response, $exception, $body);
254
        }
255
256 16
        return $response;
257
    }
258
259
    /**
260
     * Build the log data and writes the message to the log
261
     *
262
     * @param integer $logSeverity
263
     * @param string $message
264
     * @param string $url
265
     * @param ResponseAdapter $solrResponse
266
     * @param \Exception $exception
267
     * @param string $contentSend
268
     */
269 3
    protected function writeLog($logSeverity, $message, $url, $solrResponse, $exception = null, $contentSend = '')
270
    {
271 3
        $logData = $this->buildLogDataFromResponse($solrResponse, $exception, $url, $contentSend);
272 3
        $this->logger->log($logSeverity, $message, $logData);
273 3
    }
274
275
    /**
276
     * Parses the solr information to build data for the logger.
277
     *
278
     * @param ResponseAdapter $solrResponse
279
     * @param \Exception $e
280
     * @param string $url
281
     * @param string $contentSend
282
     * @return array
283
     */
284 3
    protected function buildLogDataFromResponse(ResponseAdapter $solrResponse, \Exception $e = null, $url = '', $contentSend = '')
285
    {
286 3
        $logData = ['query url' => $url, 'response' => (array)$solrResponse];
287
288 3
        if ($contentSend !== '') {
289
            $logData['content'] = $contentSend;
290
        }
291
292 3
        if (!empty($e)) {
293 3
            $logData['exception'] = $e->__toString();
294 3
            return $logData;
295
        } else {
296
            // trigger data parsing
297
            $solrResponse->response;
298
            $logData['response data'] = print_r($solrResponse, true);
299
            return $logData;
300
        }
301
    }
302
303
    /**
304
     * Call the /admin/ping servlet, can be used to quickly tell if a connection to the
305
     * server is available.
306
     *
307
     * Simply overrides the SolrPhpClient implementation, changing ping from a
308
     * HEAD to a GET request, see http://forge.typo3.org/issues/44167
309
     *
310
     * Also does not report the time, see https://forge.typo3.org/issues/64551
311
     *
312
     * @param boolean $useCache indicates if the ping result should be cached in the instance or not
313
     * @return bool TRUE if Solr can be reached, FALSE if not
314
     */
315 73
    public function ping($useCache = true)
316
    {
317 73
        $httpResponse = $this->performPingRequest($useCache);
318 73
        return ($httpResponse->getHttpStatus() === 200);
319
    }
320
321
    /**
322
     * Call the /admin/ping servlet, can be used to get the runtime of a ping request.
323
     *
324
     * @param boolean $useCache indicates if the ping result should be cached in the instance or not
325
     * @return double runtime in milliseconds
326
     * @throws \ApacheSolrForTypo3\Solr\PingFailedException
327
     */
328 3
    public function getPingRoundTripRuntime($useCache = true)
329
    {
330
        try {
331 3
            $start = $this->getMilliseconds();
332 3
            $httpResponse = $this->performPingRequest($useCache);
333 2
            $end = $this->getMilliseconds();
334 1
        } catch (HttpException $e) {
335 1
            $message = 'Solr ping failed with unexpected response code: ' . $e->getCode();
336
            /** @var $exception \ApacheSolrForTypo3\Solr\PingFailedException */
337 1
            $exception = GeneralUtility::makeInstance(PingFailedException::class, /** @scrutinizer ignore-type */ $message);
338 1
            throw $exception;
339
        }
340
341 2
        if ($httpResponse->getHttpStatus() !== 200) {
342
            $message = 'Solr ping failed with unexpected response code: ' . $httpResponse->getHttpStatus();
343
            /** @var $exception \ApacheSolrForTypo3\Solr\PingFailedException */
344
            $exception = GeneralUtility::makeInstance(PingFailedException::class, /** @scrutinizer ignore-type */ $message);
345
            throw $exception;
346
        }
347
348 2
        return $end - $start;
349
    }
350
351
    /**
352
     * Make a request to a servlet (a path) that's not a standard path.
353
     *
354
     * @deprecated Since 9.0.0 will be removed in 10.0.0 please use getClient()->executeRequest() now
355
     * @param string $servlet Path to be added to the base Solr path.
356
     * @param array $parameters Optional, additional request parameters when constructing the URL.
357
     * @param string $method HTTP method to use, defaults to GET.
358
     * @param array $requestHeaders Key value pairs of header names and values. Should include 'Content-Type' for POST and PUT.
359
     * @param string $rawPost Must be an empty string unless method is POST or PUT.
360
     * @return ResponseAdapter Response object
361
     * @throws HttpException if returned HTTP status is other than 200
362
     */
363 1
    public function requestServlet($servlet, $parameters = [], $method = 'GET', $requestHeaders = [], $rawPost = '')
364
    {
365
        // Add default parameters
366 1
        $parameters['wt'] = 'json';
367 1
        $url = $this->_constructUrl($servlet, $parameters);
368
369 1
        $setHeader = function(Request $request) use ($requestHeaders) {
370 1
            $request->addHeaders($requestHeaders);
371 1
            return $request;
372 1
        };
373
374 1
        if($method == Request::METHOD_GET) {
375 1
            $solrResponse = $this->_sendRawRequest($url, Request::METHOD_GET, '', $setHeader);
376
        } elseif ($method == Request::METHOD_POST) {
377
            $solrResponse = $this->_sendRawRequest($url, Request::METHOD_POST, $rawPost, $setHeader);
378
        } else {
379
            throw new \InvalidArgumentException("Invalid http method passed");
380
        }
381
382 1
        return $solrResponse;
383
    }
384
385
386
    /**
387
     * Performs a ping request and returns the result.
388
     *
389
     * @param boolean $useCache indicates if the ping result should be cached in the instance or not
390
     * @return ResponseAdapter
391
     */
392 76
    protected function performPingRequest($useCache = true)
393
    {
394 76
        $cacheKey = (string)($this);
395 76
        if ($useCache && isset(static::$pingCache[$cacheKey])) {
396 65
            return static::$pingCache[$cacheKey];
397
        }
398
399 76
        $pingQuery = $this->client->createPing();
400 76
        $pingResult = $this->createAndExecuteRequest($pingQuery);
401
402 75
        if ($useCache) {
403 74
            static::$pingCache[$cacheKey] = $pingResult;
404
        }
405
406 75
        return $pingResult;
407
    }
408
409
    /**
410
     * Returns the current time in milliseconds.
411
     *
412
     * @return double
413
     */
414 3
    protected function getMilliseconds()
415
    {
416 3
        return GeneralUtility::milliseconds();
417
    }
418
419
    /**
420
     * @param QueryInterface $query
421
     * @return ResponseAdapter
422
     */
423 100
    protected function createAndExecuteRequest(QueryInterface $query): ResponseAdapter
424
    {
425 100
        $request = $this->client->createRequest($query);
426 100
        return $this->executeRequest($request);
427
    }
428
429
    /**
430
     * @param $request
431
     * @return ResponseAdapter
432
     */
433 117
    protected function executeRequest($request): ResponseAdapter
434
    {
435 117
        $result = $this->client->executeRequest($request);
436 111
        return new ResponseAdapter($result->getBody(), $result->getStatusCode(), $result->getStatusMessage());
437
    }
438
439
    /**
440
     * @param string $url
441
     * @param string $httpMethod
442
     * @return Request
443
     */
444 16
    protected function buildSolariumRequestFromUrl($url, $httpMethod = Request::METHOD_GET): Request
445
    {
446 16
        $params = [];
447 16
        parse_str(parse_url($url, PHP_URL_QUERY), $params);
448
449 16
        $path = parse_url($url, PHP_URL_PATH);
450 16
        $endpoint = $this->getPrimaryEndpoint();
451 16
        $coreBasePath = $endpoint->getPath() . '/' . $endpoint->getCore() . '/';
452 16
        $handler = str_replace($coreBasePath, '', $path);
453
454 16
        $request = new Request();
455 16
        $request->setMethod($httpMethod);
456 16
        $request->setParams($params);
457 16
        $request->setHandler($handler);
458 16
        return $request;
459
    }
460
}