1<?php 2 3namespace Elastica\Transport; 4 5use Elastica\Exception\Connection\HttpException; 6use Elastica\Exception\ConnectionException; 7use Elastica\Exception\PartialShardFailureException; 8use Elastica\Exception\ResponseException; 9use Elastica\JSON; 10use Elastica\Request; 11use Elastica\Response; 12use Elastica\Util; 13 14/** 15 * Elastica Http Transport object. 16 * 17 * @author Nicolas Ruflin <spam@ruflin.com> 18 */ 19class Http extends AbstractTransport 20{ 21 /** 22 * Http scheme. 23 * 24 * @var string Http scheme 25 */ 26 protected $_scheme = 'http'; 27 28 /** 29 * Curl resource to reuse. 30 * 31 * @var \CurlHandle|resource|null Curl resource to reuse 32 */ 33 protected static $_curlConnection; 34 35 /** 36 * Makes calls to the elasticsearch server. 37 * 38 * All calls that are made to the server are done through this function 39 * 40 * @param array<string, mixed> $params Host, Port, ... 41 * 42 * @throws ConnectionException 43 * @throws ResponseException 44 * @throws HttpException 45 * 46 * @return Response Response object 47 */ 48 public function exec(Request $request, array $params): Response 49 { 50 $connection = $this->getConnection(); 51 52 $conn = $this->_getConnection($connection->isPersistent()); 53 54 // If url is set, url is taken. Otherwise port, host and path 55 $url = $connection->hasConfig('url') ? $connection->getConfig('url') : ''; 56 57 if (!empty($url)) { 58 $baseUri = $url; 59 } else { 60 $baseUri = $this->_scheme.'://'.$connection->getHost().':'.$connection->getPort().'/'.$connection->getPath(); 61 } 62 63 $requestPath = $request->getPath(); 64 if (!Util::isDateMathEscaped($requestPath)) { 65 $requestPath = Util::escapeDateMath($requestPath); 66 } 67 68 $baseUri .= $requestPath; 69 70 $query = $request->getQuery(); 71 72 if (!empty($query)) { 73 $baseUri .= '?'.\http_build_query( 74 $this->sanityzeQueryStringBool($query) 75 ); 76 } 77 78 \curl_setopt($conn, \CURLOPT_URL, $baseUri); 79 \curl_setopt($conn, \CURLOPT_TIMEOUT_MS, $connection->getTimeout() * 1000); 80 \curl_setopt($conn, \CURLOPT_FORBID_REUSE, 0); 81 82 // Tell ES that we support the compressed responses 83 // An "Accept-Encoding" header containing all supported encoding types is sent 84 // curl will decode the response automatically if the response is encoded 85 \curl_setopt($conn, \CURLOPT_ENCODING, ''); 86 87 /* @see Connection::setConnectTimeout() */ 88 $connectTimeoutMs = $connection->getConnectTimeout() * 1000; 89 90 // Let's only apply this value if the number of ms is greater than or equal to "1". 91 // In case "0" is passed as an argument, the value is reset to its default (300 s) 92 if ($connectTimeoutMs >= 1) { 93 \curl_setopt($conn, \CURLOPT_CONNECTTIMEOUT_MS, $connectTimeoutMs); 94 } 95 96 if (null !== $proxy = $connection->getProxy()) { 97 \curl_setopt($conn, \CURLOPT_PROXY, $proxy); 98 } 99 100 $username = $connection->getUsername(); 101 $password = $connection->getPassword(); 102 if (null !== $username && null !== $password) { 103 \curl_setopt($conn, \CURLOPT_HTTPAUTH, $this->_getAuthType()); 104 \curl_setopt($conn, \CURLOPT_USERPWD, "{$username}:{$password}"); 105 } 106 107 $this->_setupCurl($conn); 108 109 $headersConfig = $connection->hasConfig('headers') ? $connection->getConfig('headers') : []; 110 111 $headers = []; 112 113 if (!empty($headersConfig)) { 114 foreach ($headersConfig as $header => $headerValue) { 115 $headers[] = $header.': '.$headerValue; 116 } 117 } 118 119 // TODO: REFACTOR 120 $data = $request->getData(); 121 $httpMethod = $request->getMethod(); 122 123 $headers[] = 'Content-Type: '.$request->getContentType(); 124 125 if (!empty($data)) { 126 if ($this->hasParam('postWithRequestBody') && true == $this->getParam('postWithRequestBody')) { 127 $httpMethod = Request::POST; 128 } 129 130 if (\is_array($data)) { 131 $content = JSON::stringify($data, \JSON_UNESCAPED_UNICODE | \JSON_UNESCAPED_SLASHES); 132 } else { 133 $content = $data; 134 135 // Escaping of / not necessary. Causes problems in base64 encoding of files 136 $content = \str_replace('\/', '/', $content); 137 } 138 139 if ($connection->hasCompression()) { 140 // Compress the body of the request ... 141 \curl_setopt($conn, \CURLOPT_POSTFIELDS, \gzencode($content)); 142 143 // ... and tell ES that it is compressed 144 $headers[] = 'Content-Encoding: gzip'; 145 } else { 146 \curl_setopt($conn, \CURLOPT_POSTFIELDS, $content); 147 } 148 } else { 149 \curl_setopt($conn, \CURLOPT_POSTFIELDS, ''); 150 } 151 152 \curl_setopt($conn, \CURLOPT_HTTPHEADER, $headers); 153 154 \curl_setopt($conn, \CURLOPT_NOBODY, 'HEAD' === $httpMethod); 155 156 \curl_setopt($conn, \CURLOPT_CUSTOMREQUEST, $httpMethod); 157 158 $start = \microtime(true); 159 160 // cURL opt returntransfer leaks memory, therefore OB instead. 161 \ob_start(); 162 \curl_exec($conn); 163 $responseString = \ob_get_clean(); 164 165 $end = \microtime(true); 166 167 // Checks if error exists 168 $errorNumber = \curl_errno($conn); 169 170 $response = new Response($responseString, \curl_getinfo($conn, \CURLINFO_RESPONSE_CODE)); 171 $response->setQueryTime($end - $start); 172 $response->setTransferInfo(\curl_getinfo($conn)); 173 if ($connection->hasConfig('bigintConversion')) { 174 $response->setJsonBigintConversion($connection->getConfig('bigintConversion')); 175 } 176 177 if ($response->hasError()) { 178 throw new ResponseException($request, $response); 179 } 180 181 if ($response->hasFailedShards()) { 182 throw new PartialShardFailureException($request, $response); 183 } 184 185 if ($errorNumber > 0) { 186 throw new HttpException($errorNumber, $request, $response); 187 } 188 189 return $response; 190 } 191 192 /** 193 * Called to add additional curl params. 194 * 195 * @param \CurlHandle|resource $curlConnection Curl connection 196 */ 197 protected function _setupCurl($curlConnection): void 198 { 199 if ($this->getConnection()->hasConfig('curl')) { 200 foreach ($this->getConnection()->getConfig('curl') as $key => $param) { 201 \curl_setopt($curlConnection, $key, $param); 202 } 203 } 204 } 205 206 /** 207 * Return Curl resource. 208 * 209 * @param bool $persistent False if not persistent connection 210 * 211 * @return \CurlHandle|resource Connection resource 212 */ 213 protected function _getConnection(bool $persistent = true) 214 { 215 if (!$persistent || !self::$_curlConnection) { 216 self::$_curlConnection = \curl_init(); 217 } 218 219 return self::$_curlConnection; 220 } 221 222 /** 223 * @return int 224 */ 225 protected function _getAuthType() 226 { 227 switch ($this->_connection->getAuthType()) { 228 case 'digest': 229 return \CURLAUTH_DIGEST; 230 case 'gssnegotiate': 231 return \CURLAUTH_GSSNEGOTIATE; 232 case 'ntlm': 233 return \CURLAUTH_NTLM; 234 case 'basic': 235 return \CURLAUTH_BASIC; 236 default: 237 return \CURLAUTH_ANY; 238 } 239 } 240} 241