Passed
Push — master ( bd1cec...16f071 )
by Timo
22:19
created

AbstractSolrService::requestServlet()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 14
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 8
CRAP Score 2.0054

Importance

Changes 0
Metric Value
dl 0
loc 14
ccs 8
cts 9
cp 0.8889
rs 9.4285
c 0
b 0
f 0
cc 2
eloc 8
nc 2
nop 6
crap 2.0054
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\Util;
31
use TYPO3\CMS\Core\Utility\GeneralUtility;
32
33
abstract class AbstractSolrService extends \Apache_Solr_Service {
34
35
    const SCHEME_HTTP = 'http';
36
    const SCHEME_HTTPS = 'https';
37
38
    /**
39
     * @var array
40
     */
41
    protected static $pingCache = [];
42
43
    /**
44
     * Server connection scheme. http or https.
45
     *
46
     * @var string
47
     */
48
    protected $_scheme = self::SCHEME_HTTP;
49
50
    /**
51
     * @var TypoScriptConfiguration
52
     */
53
    protected $configuration;
54
55
    /**
56
     * @var \ApacheSolrForTypo3\Solr\System\Logging\SolrLogManager
57
     */
58
    protected $logger = null;
59
60
    /**
61
     * SolrAdminService constructor.
62
     * @param string $host
63
     * @param string $port
64
     * @param string $path
65
     * @param string $scheme
66
     * @param TypoScriptConfiguration $typoScriptConfiguration
67
     * @param SolrLogManager $logManager
68
     */
69 130
    public function __construct($host = '', $port = '8983', $path = '/solr/', $scheme = 'http', $typoScriptConfiguration = null, $logManager = null)
70
    {
71 130
        $this->setScheme($scheme);
72 129
        parent::__construct($host, $port, $path);
73 129
        $this->configuration = $typoScriptConfiguration ?? Util::getSolrConfiguration();
74 129
        $this->logger = $logManager ?? GeneralUtility::makeInstance(SolrLogManager::class, /** @scrutinizer ignore-type */ __CLASS__);
75 129
        $this->initializeTimeoutFromConfiguration();
76 129
    }
77
78
    /**
79
     * Initializes the timeout from TypoScript when configuration is present.
80
     *
81
     * @return void
82
     */
83 129
    protected function initializeTimeoutFromConfiguration()
84
    {
85 129
        $timeout = $this->configuration->getSolrTimeout();
86 129
        if ($timeout > 0) {
87 1
            $this->getHttpTransport()->setDefaultTimeout($timeout);
88
        }
89 129
    }
90
91
    /**
92
     * Creates a string representation of the Solr connection. Specifically
93
     * will return the Solr URL.
94
     *
95
     * @return string The Solr URL.
96
     */
97 77
    public function __toString()
98
    {
99 77
        return $this->_scheme . '://' . $this->_host . ':' . $this->_port . $this->_path;
100
    }
101
102
    /**
103
     * Returns the set scheme
104
     *
105
     * @return string
106
     */
107 3
    public function getScheme()
108
    {
109 3
        return $this->_scheme;
110
    }
111
112
    /**
113
     * Set the scheme used. If empty will fallback to constants
114
     *
115
     * @param string $scheme Either http or https
116
     * @throws \UnexpectedValueException
117
     */
118 130
    public function setScheme($scheme)
119
    {
120
        // Use the provided scheme or use the default
121 130
        if (empty($scheme)) {
122 2
            throw new \UnexpectedValueException('Scheme parameter is empty', 1380756390);
123
        }
124
125 129
        $isHttpOrHttps = in_array($scheme, [self::SCHEME_HTTP, self::SCHEME_HTTPS]);
126 129
        if (!$isHttpOrHttps) {
127 1
            throw new \UnexpectedValueException('Unsupported scheme parameter, scheme must be http or https', 1380756442);
128
        }
129
130
        // we have a valid scheme
131 129
        $this->_scheme = $scheme;
132
133 129
        if ($this->_urlsInited) {
134 1
            $this->_initUrls();
135
        }
136 129
    }
137
138
    /**
139
     * Return a valid http URL given this server's scheme, host, port, and path
140
     * and a provided servlet name.
141
     *
142
     * @param string $servlet Servlet name
143
     * @param array $params Additional URL parameters to attach to the end of the URL
144
     * @return string Servlet URL
145
     */
146 129
    protected function _constructUrl($servlet, $params = [])
147
    {
148 129
        $url = parent::_constructUrl($servlet, $params);
149
150 129
        if (!GeneralUtility::isFirstPartOfStr($url, $this->_scheme)) {
151 2
            $parsedUrl = parse_url($url);
152
153
            // unfortunately can't use str_replace as it replace all
154
            // occurrences of $needle and can't be limited to replace only once
155 2
            $url = $this->_scheme . substr($url, strlen($parsedUrl['scheme']));
156
        }
157
158 129
        return $url;
159
    }
160
161
    /**
162
     * Central method for making a get operation against this Solr Server
163
     *
164
     * @param string $url
165
     * @param float|bool $timeout Read timeout in seconds
166
     * @return \Apache_Solr_Response
167
     */
168 66
    protected function _sendRawGet($url, $timeout = false)
169
    {
170 66
        $logSeverity = SolrLogManager::INFO;
171 66
        $exception = null;
172
173
        try {
174 66
            $response = parent::_sendRawGet($url, $timeout);
0 ignored issues
show
Bug introduced by
It seems like $timeout can also be of type boolean; however, parameter $timeout of Apache_Solr_Service::_sendRawGet() does only seem to accept double, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

174
            $response = parent::_sendRawGet($url, /** @scrutinizer ignore-type */ $timeout);
Loading history...
175 8
        } catch (\Apache_Solr_HttpTransportException $exception) {
176 8
            $logSeverity = SolrLogManager::ERROR;
177 8
            $response = $exception->getResponse();
178
        }
179
180 66
        if ($this->configuration->getLoggingQueryRawGet() || $response->getHttpStatus() != 200) {
181 8
            $this->writeLog($logSeverity, 'Querying Solr using GET', $url, $response, $exception);
182
        }
183
184 66
        return $response;
185
    }
186
187
    /**
188
     * Central method for making a HTTP DELETE operation against the Solr server
189
     *
190
     * @param string $url
191
     * @param bool|float $timeout Read timeout in seconds
192
     * @return \Apache_Solr_Response
193
     */
194 4
    protected function _sendRawDelete($url, $timeout = false)
195
    {
196 4
        $logSeverity = SolrLogManager::INFO;
197 4
        $exception = null;
198
199
        try {
200 4
            $httpTransport = $this->getHttpTransport();
201 4
            $httpResponse = $httpTransport->performDeleteRequest($url, $timeout);
202 4
            $solrResponse = new \Apache_Solr_Response($httpResponse, $this->_createDocuments, $this->_collapseSingleValueArrays);
203
204 4
            if ($solrResponse->getHttpStatus() != 200) {
205 4
                throw new \Apache_Solr_HttpTransportException($solrResponse);
206
            }
207
        } catch (\Apache_Solr_HttpTransportException $exception) {
208
            $logSeverity = SolrLogManager::ERROR;
209
            $solrResponse = $exception->getResponse();
210
        }
211
212 4
        if ($this->configuration->getLoggingQueryRawDelete() || $solrResponse->getHttpStatus() != 200) {
213
            $this->writeLog($logSeverity, 'Querying Solr using DELETE', $url, $solrResponse, $exception);
214
        }
215
216 4
        return $solrResponse;
217
    }
218
219
    /**
220
     * Central method for making a post operation against this Solr Server
221
     *
222
     * @param string $url
223
     * @param string $rawPost
224
     * @param float|bool $timeout Read timeout in seconds
225
     * @param string $contentType
226
     * @return \Apache_Solr_Response
227
     */
228 93
    protected function _sendRawPost($url, $rawPost, $timeout = false, $contentType = 'text/xml; charset=UTF-8')
229
    {
230 93
        $logSeverity = SolrLogManager::INFO;
231 93
        $exception = null;
232
233
        try {
234 93
            $response = parent::_sendRawPost($url, $rawPost, $timeout, $contentType);
0 ignored issues
show
Bug introduced by
It seems like $timeout can also be of type boolean; however, parameter $timeout of Apache_Solr_Service::_sendRawPost() does only seem to accept double, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

234
            $response = parent::_sendRawPost($url, $rawPost, /** @scrutinizer ignore-type */ $timeout, $contentType);
Loading history...
235
        } catch (\Apache_Solr_HttpTransportException $exception) {
236
            $logSeverity = SolrLogManager::ERROR;
237
            $response = $exception->getResponse();
238
        }
239
240 93
        if ($this->configuration->getLoggingQueryRawPost() || $response->getHttpStatus() != 200) {
241
            $this->writeLog($logSeverity, 'Querying Solr using POST', $url, $response, $exception, $rawPost);
242
        }
243
244 93
        return $response;
245
    }
246
247
248
    /**
249
     * Build the log data and writes the message to the log
250
     *
251
     * @param integer $logSeverity
252
     * @param string $message
253
     * @param string $url
254
     * @param \Apache_Solr_Response $solrResponse
255
     * @param \Exception $exception
256
     * @param string $contentSend
257
     */
258 8
    protected function writeLog($logSeverity, $message, $url, $solrResponse, $exception = null, $contentSend = '')
259
    {
260 8
        $logData = $this->buildLogDataFromResponse($solrResponse, $exception, $url, $contentSend);
261 8
        $this->logger->log($logSeverity, $message, $logData);
262 8
    }
263
264
    /**
265
     * Parses the solr information to build data for the logger.
266
     *
267
     * @param \Apache_Solr_Response $solrResponse
268
     * @param \Exception $e
269
     * @param string $url
270
     * @param string $contentSend
271
     * @return array
272
     */
273 8
    protected function buildLogDataFromResponse(\Apache_Solr_Response $solrResponse, \Exception $e = null, $url = '', $contentSend = '')
274
    {
275 8
        $logData = ['query url' => $url, 'response' => (array)$solrResponse];
276
277 8
        if ($contentSend !== '') {
278
            $logData['content'] = $contentSend;
279
        }
280
281 8
        if (!empty($e)) {
282 8
            $logData['exception'] = $e->__toString();
283 8
            return $logData;
284
        } else {
285
            // trigger data parsing
286
            $solrResponse->response;
287
            $logData['response data'] = print_r($solrResponse, true);
288
            return $logData;
289
        }
290
    }
291
292
293
    /**
294
     * Returns the core name from the configured path without the core name.
295
     *
296
     * @return string
297
     */
298 22
    public function getCoreBasePath()
299
    {
300 22
        $pathWithoutLeadingAndTrailingSlashes = trim(trim($this->_path), "/");
301 22
        $pathWithoutLastSegment = substr($pathWithoutLeadingAndTrailingSlashes, 0, strrpos($pathWithoutLeadingAndTrailingSlashes, "/"));
302 22
        return '/' . $pathWithoutLastSegment . '/';
303
    }
304
305
    /**
306
     * Returns the core name from the configured path.
307
     *
308
     * @return string
309
     */
310 7
    public function getCoreName()
311
    {
312 7
        $paths = explode('/', trim($this->_path, '/'));
313 7
        return (string)array_pop($paths);
314
    }
315
316
    /**
317
     * Call the /admin/ping servlet, can be used to quickly tell if a connection to the
318
     * server is available.
319
     *
320
     * Simply overrides the SolrPhpClient implementation, changing ping from a
321
     * HEAD to a GET request, see http://forge.typo3.org/issues/44167
322
     *
323
     * Also does not report the time, see https://forge.typo3.org/issues/64551
324
     *
325
     * @param int $timeout maximum time to wait for ping in seconds, -1 for unlimited (default is 2)
326
     * @param boolean $useCache indicates if the ping result should be cached in the instance or not
327
     * @return bool TRUE if Solr can be reached, FALSE if not
328
     */
329 72
    public function ping($timeout = 2, $useCache = true)
330
    {
331 72
        $httpResponse = $this->performPingRequest($timeout, $useCache);
332 72
        return ($httpResponse->getStatusCode() === 200);
333
    }
334
335
    /**
336
     * Call the /admin/ping servlet, can be used to get the runtime of a ping request.
337
     *
338
     * @param int $timeout maximum time to wait for ping in seconds, -1 for unlimited (default is 2)
339
     * @param boolean $useCache indicates if the ping result should be cached in the instance or not
340
     * @return double runtime in milliseconds
341
     * @throws \ApacheSolrForTypo3\Solr\PingFailedException
342
     */
343 3
    public function getPingRoundTripRuntime($timeout = 2, $useCache = true)
344
    {
345 3
        $start = $this->getMilliseconds();
346 3
        $httpResponse = $this->performPingRequest($timeout, $useCache);
347 3
        $end = $this->getMilliseconds();
348
349 3
        if ($httpResponse->getStatusCode() !== 200) {
350 1
            $message = 'Solr ping failed with unexpected response code: ' . $httpResponse->getStatusCode();
351
            /** @var $exception \ApacheSolrForTypo3\Solr\PingFailedException */
352 1
            $exception = GeneralUtility::makeInstance(PingFailedException::class, /** @scrutinizer ignore-type */ $message);
353 1
            $exception->setHttpResponse($httpResponse);
354 1
            throw $exception;
355
        }
356
357 2
        return $end - $start;
358
    }
359
360
    /**
361
     * Make a request to a servlet (a path) that's not a standard path.
362
     *
363
     * @param string $servlet Path to be added to the base Solr path.
364
     * @param array $parameters Optional, additional request parameters when constructing the URL.
365
     * @param string $method HTTP method to use, defaults to GET.
366
     * @param array $requestHeaders Key value pairs of header names and values. Should include 'Content-Type' for POST and PUT.
367
     * @param string $rawPost Must be an empty string unless method is POST or PUT.
368
     * @param float|bool $timeout Read timeout in seconds, defaults to FALSE.
369
     * @return \Apache_Solr_Response Response object
370
     * @throws \Apache_Solr_HttpTransportException if returned HTTP status is other than 200
371
     */
372 1
    public function requestServlet($servlet, $parameters = [], $method = 'GET', $requestHeaders = [], $rawPost = '', $timeout = false)
373
    {
374
        // Add default parameters
375 1
        $parameters['wt'] = self::SOLR_WRITER;
376 1
        $parameters['json.nl'] = $this->_namedListTreatment;
377 1
        $url = $this->_constructUrl($servlet, $parameters);
378
379 1
        $httpResponse = $this->getResponseFromTransport($url, $method, $requestHeaders, $rawPost, $timeout);
380 1
        $solrResponse = new \Apache_Solr_Response($httpResponse, $this->_createDocuments, $this->_collapseSingleValueArrays);
381 1
        if ($solrResponse->getHttpStatus() != 200) {
382
            throw new \Apache_Solr_HttpTransportException($solrResponse);
383
        }
384
385 1
        return $solrResponse;
386
    }
387
388
    /**
389
     * Decides which transport method to used, depending on the request method and retrieves the response.
390
     *
391
     * @param string $url
392
     * @param string $method
393
     * @param array $requestHeaders
394
     * @param string $rawPost
395
     * @param float|bool $timeout
396
     * @return \Apache_Solr_HttpTransport_Response
397
     */
398 1
    protected function getResponseFromTransport($url, $method, $requestHeaders, $rawPost, $timeout)
399
    {
400 1
        $httpTransport = $this->getHttpTransport();
401
402 1
        if ($method == self::METHOD_GET) {
403
            return $httpTransport->performGetRequest($url, $timeout);
404
        }
405 1
        if ($method == self::METHOD_POST) {
406
            // FIXME should respect all headers, not only Content-Type
407 1
            return $httpTransport->performPostRequest($url, $rawPost, $requestHeaders['Content-Type'], $timeout);
408
        }
409
410
        throw new \InvalidArgumentException('$method should be GET or POST');
411
    }
412
413
    /**
414
     * Performs a ping request and returns the result.
415
     *
416
     * @param int $timeout
417
     * @param boolean $useCache indicates if the ping result should be cached in the instance or not
418
     * @return \Apache_Solr_HttpTransport_Response
419
     */
420 75
    protected function performPingRequest($timeout = 2, $useCache = true)
421
    {
422 75
        $cacheKey = (string)($this);
423 75
        if ($useCache && isset(static::$pingCache[$cacheKey])) {
424 64
            return static::$pingCache[$cacheKey];
425
        }
426
427 75
        $pingResult = $this->getHttpTransport()->performGetRequest($this->_pingUrl, $timeout);
428
429 75
        if ($useCache) {
430 74
            static::$pingCache[$cacheKey] = $pingResult;
431
        }
432
433 75
        return $pingResult;
434
    }
435
436
    /**
437
     * Returns the current time in milliseconds.
438
     *
439
     * @return double
440
     */
441 3
    protected function getMilliseconds()
442
    {
443 3
        return GeneralUtility::milliseconds();
444
    }
445
}