vendor/elasticsearch/elasticsearch/src/Elasticsearch/Connections/Connection.php line 345

Open in your IDE?
  1. <?php
  2. /**
  3.  * Elasticsearch PHP client
  4.  *
  5.  * @link      https://github.com/elastic/elasticsearch-php/
  6.  * @copyright Copyright (c) Elasticsearch B.V (https://www.elastic.co)
  7.  * @license   http://www.apache.org/licenses/LICENSE-2.0 Apache License, Version 2.0
  8.  * @license   https://www.gnu.org/licenses/lgpl-2.1.html GNU Lesser General Public License, Version 2.1
  9.  *
  10.  * Licensed to Elasticsearch B.V under one or more agreements.
  11.  * Elasticsearch B.V licenses this file to you under the Apache 2.0 License or
  12.  * the GNU Lesser General Public License, Version 2.1, at your option.
  13.  * See the LICENSE file in the project root for more information.
  14.  */
  15. declare(strict_types 1);
  16. namespace Elasticsearch\Connections;
  17. use Elasticsearch\Client;
  18. use Elasticsearch\Common\Exceptions\BadRequest400Exception;
  19. use Elasticsearch\Common\Exceptions\Conflict409Exception;
  20. use Elasticsearch\Common\Exceptions\Curl\CouldNotConnectToHost;
  21. use Elasticsearch\Common\Exceptions\Curl\CouldNotResolveHostException;
  22. use Elasticsearch\Common\Exceptions\Curl\OperationTimeoutException;
  23. use Elasticsearch\Common\Exceptions\ElasticsearchException;
  24. use Elasticsearch\Common\Exceptions\Forbidden403Exception;
  25. use Elasticsearch\Common\Exceptions\MaxRetriesException;
  26. use Elasticsearch\Common\Exceptions\Missing404Exception;
  27. use Elasticsearch\Common\Exceptions\NoDocumentsToGetException;
  28. use Elasticsearch\Common\Exceptions\NoShardAvailableException;
  29. use Elasticsearch\Common\Exceptions\RequestTimeout408Exception;
  30. use Elasticsearch\Common\Exceptions\RoutingMissingException;
  31. use Elasticsearch\Common\Exceptions\ScriptLangNotSupportedException;
  32. use Elasticsearch\Common\Exceptions\ServerErrorResponseException;
  33. use Elasticsearch\Common\Exceptions\TransportException;
  34. use Elasticsearch\Common\Exceptions\Unauthorized401Exception;
  35. use Elasticsearch\Serializers\SerializerInterface;
  36. use Elasticsearch\Transport;
  37. use Exception;
  38. use GuzzleHttp\Ring\Core;
  39. use GuzzleHttp\Ring\Exception\ConnectException;
  40. use GuzzleHttp\Ring\Exception\RingException;
  41. use Psr\Log\LoggerInterface;
  42. class Connection implements ConnectionInterface
  43. {
  44.     /**
  45.      * @var callable
  46.      */
  47.     protected $handler;
  48.     /**
  49.      * @var SerializerInterface
  50.      */
  51.     protected $serializer;
  52.     /**
  53.      * @var string
  54.      */
  55.     protected $transportSchema 'http';    // TODO depreciate this default
  56.     /**
  57.      * @var string
  58.      */
  59.     protected $host;
  60.     /**
  61.      * @var string|null
  62.      */
  63.     protected $path;
  64.     /**
  65.      * @var int
  66.      */
  67.     protected $port;
  68.     /**
  69.      * @var LoggerInterface
  70.      */
  71.     protected $log;
  72.     /**
  73.      * @var LoggerInterface
  74.      */
  75.     protected $trace;
  76.     /**
  77.      * @var array
  78.      */
  79.     protected $connectionParams;
  80.     /**
  81.      * @var array
  82.      */
  83.     protected $headers = [];
  84.     /**
  85.      * @var bool
  86.      */
  87.     protected $isAlive false;
  88.     /**
  89.      * @var float
  90.      */
  91.     private $pingTimeout 1;    //TODO expose this
  92.     /**
  93.      * @var int
  94.      */
  95.     private $lastPing 0;
  96.     /**
  97.      * @var int
  98.      */
  99.     private $failedPings 0;
  100.     /**
  101.      * @var mixed[]
  102.      */
  103.     private $lastRequest = array();
  104.     /**
  105.      * @var string
  106.      */
  107.     private $OSVersion null;
  108.     public function __construct(
  109.         callable $handler,
  110.         array $hostDetails,
  111.         array $connectionParams,
  112.         SerializerInterface $serializer,
  113.         LoggerInterface $log,
  114.         LoggerInterface $trace
  115.     ) {
  116.         if (isset($hostDetails['port']) !== true) {
  117.             $hostDetails['port'] = 9200;
  118.         }
  119.         if (isset($hostDetails['scheme'])) {
  120.             $this->transportSchema $hostDetails['scheme'];
  121.         }
  122.         // Only Set the Basic if API Key is not set and setBasicAuthentication was not called prior
  123.         if (isset($connectionParams['client']['headers']['Authorization']) === false
  124.             && isset($connectionParams['client']['curl'][CURLOPT_HTTPAUTH]) === false
  125.             && isset($hostDetails['user'])
  126.             && isset($hostDetails['pass'])
  127.         ) {
  128.             $connectionParams['client']['curl'][CURLOPT_HTTPAUTH] = CURLAUTH_BASIC;
  129.             $connectionParams['client']['curl'][CURLOPT_USERPWD] = $hostDetails['user'].':'.$hostDetails['pass'];
  130.         }
  131.         $connectionParams['client']['curl'][CURLOPT_PORT] = $hostDetails['port'];
  132.         if (isset($connectionParams['client']['headers'])) {
  133.             $this->headers $connectionParams['client']['headers'];
  134.             unset($connectionParams['client']['headers']);
  135.         }
  136.         // Add the User-Agent using the format: <client-repo-name>/<client-version> (metadata-values)
  137.         $this->headers['User-Agent'] = [sprintf(
  138.             "elasticsearch-php/%s (%s %s; PHP %s)",
  139.             Client::VERSION,
  140.             PHP_OS,
  141.             $this->getOSVersion(),
  142.             phpversion()
  143.         )];
  144.         // Add x-elastic-client-meta header, if enabled
  145.         if (isset($connectionParams['client']['x-elastic-client-meta']) && $connectionParams['client']['x-elastic-client-meta']) {
  146.             $this->headers['x-elastic-client-meta'] = [$this->getElasticMetaHeader($connectionParams)];
  147.         }
  148.         $host $hostDetails['host'];
  149.         $path null;
  150.         if (isset($hostDetails['path']) === true) {
  151.             $path $hostDetails['path'];
  152.         }
  153.         $port $hostDetails['port'];
  154.         $this->host             $host;
  155.         $this->path             $path;
  156.         $this->port             $port;
  157.         $this->log              $log;
  158.         $this->trace            $trace;
  159.         $this->connectionParams $connectionParams;
  160.         $this->serializer       $serializer;
  161.         $this->handler $this->wrapHandler($handler);
  162.     }
  163.     /**
  164.      * @param  string    $method
  165.      * @param  string    $uri
  166.      * @param  null|array   $params
  167.      * @param  null      $body
  168.      * @param  array     $options
  169.      * @param  Transport $transport
  170.      * @return mixed
  171.      */
  172.     public function performRequest(string $methodstring $uri, ?array $params = [], $body null, array $options = [], Transport $transport null)
  173.     {
  174.         if ($body !== null) {
  175.             $body $this->serializer->serialize($body);
  176.         }
  177.         $headers $this->headers;
  178.         if (isset($options['client']['headers']) && is_array($options['client']['headers'])) {
  179.             $headers array_merge($this->headers$options['client']['headers']);
  180.         }
  181.         $host $this->host;
  182.         if (isset($this->connectionParams['client']['port_in_header']) && $this->connectionParams['client']['port_in_header']) {
  183.             $host .= ':' $this->port;
  184.         }
  185.         
  186.         $request = [
  187.             'http_method' => $method,
  188.             'scheme'      => $this->transportSchema,
  189.             'uri'         => $this->getURI($uri$params),
  190.             'body'        => $body,
  191.             'headers'     => array_merge(
  192.                 [
  193.                 'Host'  => [$host]
  194.                 ],
  195.                 $headers
  196.             )
  197.         ];
  198.         $request array_replace_recursive($request$this->connectionParams$options);
  199.         // RingPHP does not like if client is empty
  200.         if (empty($request['client'])) {
  201.             unset($request['client']);
  202.         }
  203.         $handler $this->handler;
  204.         $future $handler($request$this$transport$options);
  205.         return $future;
  206.     }
  207.     public function getTransportSchema(): string
  208.     {
  209.         return $this->transportSchema;
  210.     }
  211.     public function getLastRequestInfo(): array
  212.     {
  213.         return $this->lastRequest;
  214.     }
  215.     private function wrapHandler(callable $handler): callable
  216.     {
  217.         return function (array $requestConnection $connectionTransport $transport null$options) use ($handler) {
  218.             $this->lastRequest = [];
  219.             $this->lastRequest['request'] = $request;
  220.             // Send the request using the wrapped handler.
  221.             $response =  Core::proxy(
  222.                 $handler($request), 
  223.                 function ($response) use ($connection$transport$request$options) {
  224.                     $this->lastRequest['response'] = $response;
  225.                     if (isset($response['error']) === true) {
  226.                         if ($response['error'] instanceof ConnectException || $response['error'] instanceof RingException) {
  227.                             $this->log->warning("Curl exception encountered.");
  228.                             $exception $this->getCurlRetryException($request$response);
  229.                             $this->logRequestFail($request$response$exception);
  230.                             $node $connection->getHost();
  231.                             $this->log->warning("Marking node $node dead.");
  232.                             $connection->markDead();
  233.                             // If the transport has not been set, we are inside a Ping or Sniff,
  234.                             // so we don't want to retrigger retries anyway.
  235.                             //
  236.                             // TODO this could be handled better, but we are limited because connectionpools do not
  237.                             // have access to Transport.  Architecturally, all of this needs to be refactored
  238.                             if (isset($transport) === true) {
  239.                                 $transport->connectionPool->scheduleCheck();
  240.                                 $neverRetry = isset($request['client']['never_retry']) ? $request['client']['never_retry'] : false;
  241.                                 $shouldRetry $transport->shouldRetry($request);
  242.                                 $shouldRetryText = ($shouldRetry) ? 'true' 'false';
  243.                                 $this->log->warning("Retries left? $shouldRetryText");
  244.                                 if ($shouldRetry && !$neverRetry) {
  245.                                     return $transport->performRequest(
  246.                                         $request['http_method'],
  247.                                         $request['uri'],
  248.                                         [],
  249.                                         $request['body'],
  250.                                         $options
  251.                                     );
  252.                                 }
  253.                             }
  254.                             $this->log->warning("Out of retries, throwing exception from $node");
  255.                             // Only throw if we run out of retries
  256.                             throw $exception;
  257.                         } else {
  258.                             // Something went seriously wrong, bail
  259.                             $exception = new TransportException($response['error']->getMessage());
  260.                             $this->logRequestFail($request$response$exception);
  261.                             throw $exception;
  262.                         }
  263.                     } else {
  264.                         $connection->markAlive();
  265.                         if (isset($response['headers']['Warning'])) {
  266.                             $this->logWarning($request$response);
  267.                         }
  268.                         if (isset($response['body']) === true) {
  269.                             $response['body'] = stream_get_contents($response['body']);
  270.                             $this->lastRequest['response']['body'] = $response['body'];
  271.                         }
  272.                         if ($response['status'] >= 400 && $response['status'] < 500) {
  273.                             $ignore $request['client']['ignore'] ?? [];
  274.                             // Skip 404 if succeeded true in the body (e.g. clear_scroll)
  275.                             $body $response['body'] ?? '';
  276.                             if (strpos($body'"succeeded":true') !== false) {
  277.                                  $ignore[] = 404;
  278.                             }
  279.                             $this->process4xxError($request$response$ignore);
  280.                         } elseif ($response['status'] >= 500) {
  281.                             $ignore $request['client']['ignore'] ?? [];
  282.                             $this->process5xxError($request$response$ignore);
  283.                         }
  284.                         // No error, deserialize
  285.                         $response['body'] = $this->serializer->deserialize($response['body'], $response['transfer_stats']);
  286.                     }
  287.                     $this->logRequestSuccess($request$response);
  288.                     return isset($request['client']['verbose']) && $request['client']['verbose'] === true $response $response['body'];
  289.                 }
  290.             );
  291.             return $response;
  292.         };
  293.     }
  294.     private function getURI(string $uri, ?array $params): string
  295.     {
  296.         if (isset($params) === true && !empty($params)) {
  297.             $params array_map(
  298.                 function ($value) {
  299.                     if ($value === true) {
  300.                         return 'true';
  301.                     } elseif ($value === false) {
  302.                         return 'false';
  303.                     }
  304.                     return $value;
  305.                 },
  306.                 $params
  307.             );
  308.             $uri .= '?' http_build_query($params);
  309.         }
  310.         if ($this->path !== null) {
  311.             $uri $this->path $uri;
  312.         }
  313.         return $uri ?? '';
  314.     }
  315.     public function getHeaders(): array
  316.     {
  317.         return $this->headers;
  318.     }
  319.     public function logWarning(array $request, array $response): void
  320.     {
  321.         $this->log->warning('Deprecation'$response['headers']['Warning']);
  322.     }
  323.     /**
  324.      * Log a successful request
  325.      *
  326.      * @param  array $request
  327.      * @param  array $response
  328.      * @return void
  329.      */
  330.     public function logRequestSuccess(array $request, array $response): void
  331.     {
  332.         $port $request['client']['curl'][CURLOPT_PORT] ?? $response['transfer_stats']['primary_port'] ?? '';
  333.         $uri $this->addPortInUrl($response['effective_url'], (int) $port);
  334.         $this->log->debug('Request Body', array($request['body']));
  335.         $this->log->info(
  336.             'Request Success:',
  337.             array(
  338.                 'method'    => $request['http_method'],
  339.                 'uri'       => $uri,
  340.                 'port'      => $port,
  341.                 'headers'   => $request['headers'],
  342.                 'HTTP code' => $response['status'],
  343.                 'duration'  => $response['transfer_stats']['total_time'],
  344.             )
  345.         );
  346.         $this->log->debug('Response', array($response['body']));
  347.         // Build the curl command for Trace.
  348.         $curlCommand $this->buildCurlCommand($request['http_method'], $uri$request['body']);
  349.         $this->trace->info($curlCommand);
  350.         $this->trace->debug(
  351.             'Response:',
  352.             array(
  353.                 'response'  => $response['body'],
  354.                 'method'    => $request['http_method'],
  355.                 'uri'       => $uri,
  356.                 'port'      => $port,
  357.                 'HTTP code' => $response['status'],
  358.                 'duration'  => $response['transfer_stats']['total_time'],
  359.             )
  360.         );
  361.     }
  362.     /**
  363.      * Log a failed request
  364.      *
  365.      * @param array      $request
  366.      * @param array      $response
  367.      * @param \Exception $exception
  368.      *
  369.      * @return void
  370.      */
  371.     public function logRequestFail(array $request, array $response\Exception $exception): void
  372.     {
  373.         $port $request['client']['curl'][CURLOPT_PORT] ?? $response['transfer_stats']['primary_port'] ?? '';
  374.         $uri $this->addPortInUrl($response['effective_url'], (int) $port);
  375.         
  376.         $this->log->debug('Request Body', array($request['body']));
  377.         $this->log->warning(
  378.             'Request Failure:',
  379.             array(
  380.                 'method'    => $request['http_method'],
  381.                 'uri'       => $uri,
  382.                 'port'      => $port,
  383.                 'headers'   => $request['headers'],
  384.                 'HTTP code' => $response['status'],
  385.                 'duration'  => $response['transfer_stats']['total_time'],
  386.                 'error'     => $exception->getMessage(),
  387.             )
  388.         );
  389.         $this->log->warning('Response', array($response['body']));
  390.         // Build the curl command for Trace.
  391.         $curlCommand $this->buildCurlCommand($request['http_method'], $uri$request['body']);
  392.         $this->trace->info($curlCommand);
  393.         $this->trace->debug(
  394.             'Response:',
  395.             array(
  396.                 'response'  => $response,
  397.                 'method'    => $request['http_method'],
  398.                 'uri'       => $uri,
  399.                 'port'      => $port,
  400.                 'HTTP code' => $response['status'],
  401.                 'duration'  => $response['transfer_stats']['total_time'],
  402.             )
  403.         );
  404.     }
  405.     public function ping(): bool
  406.     {
  407.         $options = [
  408.             'client' => [
  409.                 'timeout' => $this->pingTimeout,
  410.                 'never_retry' => true,
  411.                 'verbose' => true
  412.             ]
  413.         ];
  414.         try {
  415.             $response $this->performRequest('HEAD''/'nullnull$options);
  416.             $response $response->wait();
  417.         } catch (TransportException $exception) {
  418.             $this->markDead();
  419.             return false;
  420.         }
  421.         if ($response['status'] === 200) {
  422.             $this->markAlive();
  423.             return true;
  424.         } else {
  425.             $this->markDead();
  426.             return false;
  427.         }
  428.     }
  429.     /**
  430.      * @return array|\GuzzleHttp\Ring\Future\FutureArray
  431.      */
  432.     public function sniff()
  433.     {
  434.         $options = [
  435.             'client' => [
  436.                 'timeout' => $this->pingTimeout,
  437.                 'never_retry' => true
  438.             ]
  439.         ];
  440.         return $this->performRequest('GET''/_nodes/'nullnull$options);
  441.     }
  442.     public function isAlive(): bool
  443.     {
  444.         return $this->isAlive;
  445.     }
  446.     public function markAlive(): void
  447.     {
  448.         $this->failedPings 0;
  449.         $this->isAlive true;
  450.         $this->lastPing time();
  451.     }
  452.     public function markDead(): void
  453.     {
  454.         $this->isAlive false;
  455.         $this->failedPings += 1;
  456.         $this->lastPing time();
  457.     }
  458.     public function getLastPing(): int
  459.     {
  460.         return $this->lastPing;
  461.     }
  462.     public function getPingFailures(): int
  463.     {
  464.         return $this->failedPings;
  465.     }
  466.     public function getHost(): string
  467.     {
  468.         return $this->host;
  469.     }
  470.     public function getUserPass(): ?string
  471.     {
  472.         return $this->connectionParams['client']['curl'][CURLOPT_USERPWD] ?? null;
  473.     }
  474.     public function getPath(): ?string
  475.     {
  476.         return $this->path;
  477.     }
  478.     /**
  479.      * @return int
  480.      */
  481.     public function getPort()
  482.     {
  483.         return $this->port;
  484.     }
  485.     protected function getCurlRetryException(array $request, array $response): ElasticsearchException
  486.     {
  487.         $exception null;
  488.         $message $response['error']->getMessage();
  489.         $exception = new MaxRetriesException($message);
  490.         switch ($response['curl']['errno']) {
  491.             case 6:
  492.                 $exception = new CouldNotResolveHostException($message0$exception);
  493.                 break;
  494.             case 7:
  495.                 $exception = new CouldNotConnectToHost($message0$exception);
  496.                 break;
  497.             case 28:
  498.                 $exception = new OperationTimeoutException($message0$exception);
  499.                 break;
  500.         }
  501.         return $exception;
  502.     }
  503.     /**
  504.      * Get the x-elastic-client-meta header
  505.      * 
  506.      * The header format is specified by the following regex:
  507.      * ^[a-z]{1,}=[a-z0-9\.\-]{1,}(?:,[a-z]{1,}=[a-z0-9\.\-]+)*$
  508.      */
  509.     private function getElasticMetaHeader(array $connectionParams): string
  510.     {
  511.         $phpSemVersion sprintf("%d.%d.%d"PHP_MAJOR_VERSIONPHP_MINOR_VERSIONPHP_RELEASE_VERSION);
  512.         // Reduce the size in case of '-snapshot' version
  513.         $clientVersion str_replace('-snapshot''-s'strtolower(Client::VERSION)); 
  514.         $clientMeta sprintf(
  515.             "es=%s,php=%s,t=%s,a=%d",
  516.             $clientVersion,
  517.             $phpSemVersion,
  518.             $clientVersion,
  519.             isset($connectionParams['client']['future']) && $connectionParams['client']['future'] === 'lazy' 0
  520.         );
  521.         if (function_exists('curl_version')) {
  522.             $curlVersion curl_version();
  523.             if (isset($curlVersion['version'])) {
  524.                 $clientMeta .= sprintf(",cu=%s"$curlVersion['version']); // cu = curl library
  525.             }
  526.         }
  527.         return $clientMeta;
  528.     }
  529.     /**
  530.      * Get the OS version using php_uname if available
  531.      * otherwise it returns an empty string
  532.      *
  533.      * @see https://github.com/elastic/elasticsearch-php/issues/922
  534.      */
  535.     private function getOSVersion(): string
  536.     {
  537.         if ($this->OSVersion === null) {
  538.             $this->OSVersion strpos(strtolower(ini_get('disable_functions')), 'php_uname') !== false
  539.                 ''
  540.                 php_uname("r");
  541.         }
  542.         return $this->OSVersion;
  543.     }
  544.     /**
  545.      * Add the port value in the URL if not present
  546.      */
  547.     private function addPortInUrl(string $uriint $port): string
  548.     {
  549.         if (strpos($uri':'7) !== false) {
  550.             return $uri;
  551.         }
  552.         return preg_replace('#([^/])/([^/])#'sprintf("$1:%s/$2"$port), $uri1);
  553.     }
  554.     /**
  555.      * Construct a string cURL command
  556.      */
  557.     private function buildCurlCommand(string $methodstring $url, ?string $body): string
  558.     {
  559.         if (strpos($url'?') === false) {
  560.             $url .= '?pretty=true';
  561.         } else {
  562.             str_replace('?''?pretty=true'$url);
  563.         }
  564.         $curlCommand 'curl -X' strtoupper($method);
  565.         $curlCommand .= " '" $url "'";
  566.         if (isset($body) === true && $body !== '') {
  567.             $curlCommand .= " -d '" $body "'";
  568.         }
  569.         return $curlCommand;
  570.     }
  571.     private function process4xxError(array $request, array $response, array $ignore): ?ElasticsearchException
  572.     {
  573.         $statusCode $response['status'];
  574.         /**
  575.  * @var \Exception $exception
  576. */
  577.         $exception $this->tryDeserialize400Error($response);
  578.         if (array_search($response['status'], $ignore) !== false) {
  579.             return null;
  580.         }
  581.         
  582.         $responseBody $this->convertBodyToString($response['body'], $statusCode$exception);
  583.         if ($statusCode === 401) {
  584.             $exception = new Unauthorized401Exception($responseBody$statusCode);
  585.         } elseif ($statusCode === 403) {
  586.             $exception = new Forbidden403Exception($responseBody$statusCode);
  587.         } elseif ($statusCode === 404) {
  588.             $exception = new Missing404Exception($responseBody$statusCode);
  589.         } elseif ($statusCode === 409) {
  590.             $exception = new Conflict409Exception($responseBody$statusCode);
  591.         } elseif ($statusCode === 400 && strpos($responseBody'script_lang not supported') !== false) {
  592.             $exception = new ScriptLangNotSupportedException($responseBody$statusCode);
  593.         } elseif ($statusCode === 408) {
  594.             $exception = new RequestTimeout408Exception($responseBody$statusCode);
  595.         } else {
  596.             $exception = new BadRequest400Exception($responseBody$statusCode);
  597.         }
  598.         $this->logRequestFail($request$response$exception);
  599.         throw $exception;
  600.     }
  601.     private function process5xxError(array $request, array $response, array $ignore): ?ElasticsearchException
  602.     {
  603.         $statusCode = (int) $response['status'];
  604.         $responseBody $response['body'];
  605.         /**
  606.  * @var \Exception $exception
  607. */
  608.         $exception $this->tryDeserialize500Error($response);
  609.         $exceptionText "[$statusCode Server Exception] ".$exception->getMessage();
  610.         $this->log->error($exceptionText);
  611.         $this->log->error($exception->getTraceAsString());
  612.         if (array_search($statusCode$ignore) !== false) {
  613.             return null;
  614.         }
  615.         if ($statusCode === 500 && strpos($responseBody"RoutingMissingException") !== false) {
  616.             $exception = new RoutingMissingException($exception->getMessage(), $statusCode$exception);
  617.         } elseif ($statusCode === 500 && preg_match('/ActionRequestValidationException.+ no documents to get/'$responseBody) === 1) {
  618.             $exception = new NoDocumentsToGetException($exception->getMessage(), $statusCode$exception);
  619.         } elseif ($statusCode === 500 && strpos($responseBody'NoShardAvailableActionException') !== false) {
  620.             $exception = new NoShardAvailableException($exception->getMessage(), $statusCode$exception);
  621.         } else {
  622.             $exception = new ServerErrorResponseException(
  623.                 $this->convertBodyToString($responseBody$statusCode$exception),
  624.                 $statusCode
  625.             );
  626.         }
  627.         $this->logRequestFail($request$response$exception);
  628.         throw $exception;
  629.     }
  630.     private function convertBodyToString($bodyint $statusCodeException $exception) : string
  631.     {
  632.         if (empty($body)) {
  633.             return sprintf(
  634.                 "Unknown %d error from Elasticsearch %s",
  635.                 $statusCode,
  636.                 $exception->getMessage()
  637.             );
  638.         }
  639.         // if body is not string, we convert it so it can be used as Exception message
  640.         if (!is_string($body)) {
  641.             return json_encode($body);
  642.         }
  643.         return $body;
  644.     }
  645.     private function tryDeserialize400Error(array $response): ElasticsearchException
  646.     {
  647.         return $this->tryDeserializeError($responseBadRequest400Exception::class);
  648.     }
  649.     private function tryDeserialize500Error(array $response): ElasticsearchException
  650.     {
  651.         return $this->tryDeserializeError($responseServerErrorResponseException::class);
  652.     }
  653.     private function tryDeserializeError(array $responsestring $errorClass): ElasticsearchException
  654.     {
  655.         $error $this->serializer->deserialize($response['body'], $response['transfer_stats']);
  656.         if (is_array($error) === true) {
  657.             if (isset($error['error']) === false) {
  658.                 // <2.0 "i just blew up" nonstructured exception
  659.                 // $error is an array but we don't know the format, reuse the response body instead
  660.                 // added json_encode to convert into a string
  661.                 return new $errorClass(json_encode($response['body']), (int) $response['status']);
  662.             }
  663.             
  664.             // 2.0 structured exceptions
  665.             if (is_array($error['error']) && array_key_exists('reason'$error['error']) === true) {
  666.                 // Try to use root cause first (only grabs the first root cause)
  667.                 $root $error['error']['root_cause'];
  668.                 if (isset($root) && isset($root[0])) {
  669.                     $cause $root[0]['reason'];
  670.                     $type $root[0]['type'];
  671.                 } else {
  672.                     $cause $error['error']['reason'];
  673.                     $type $error['error']['type'];
  674.                 }
  675.                 // added json_encode to convert into a string
  676.                 $original = new $errorClass(json_encode($response['body']), $response['status']);
  677.                 return new $errorClass("$type$cause", (int) $response['status'], $original);
  678.             }
  679.             // <2.0 semi-structured exceptions
  680.             // added json_encode to convert into a string
  681.             $original = new $errorClass(json_encode($response['body']), $response['status']);
  682.             
  683.             $errorEncoded $error['error'];
  684.             if (is_array($errorEncoded)) {
  685.                 $errorEncoded json_encode($errorEncoded);
  686.             }
  687.             return new $errorClass($errorEncoded, (int) $response['status'], $original);
  688.         }
  689.         // if responseBody is not string, we convert it so it can be used as Exception message
  690.         $responseBody $response['body'];
  691.         if (!is_string($responseBody)) {
  692.             $responseBody json_encode($responseBody);
  693.         }
  694.         // <2.0 "i just blew up" nonstructured exception
  695.         return new $errorClass($responseBody);
  696.     }
  697. }