Score:0

Adding Middleware::retry() to a custom httpClient effects the default http_client

nl flag

We have two http_clients in our custom service file on __construct, one default from Drupal's http_client service, another custom new Client created by the use of http_client_factory and http_handler_stack services following formOptions function.

The custom client was used to implement guzzle's retry mechanism by pushing Middleware::retry into the current handler_stack in our custom services. But this middleware seems to be consistent in the default httpClient as well, causing all client calls going through the retryDecider(). gist of reference

I want to seperate Middleware effect of both clients, what should I do? Thanks for providing your share of knowledge!

  /**
   * Constructs a new service object.
   */
  public function __construct(EntityTypeManagerInterface $entity_type_manager, ClientInterface $http_client, ClientFactory $http_client_factory, HandlerStack $stack, TimeInterface $datetime_time, ConfigFactoryInterface $config_factory) {
    $this->entityTypeManager = $entity_type_manager;
    $this->datetimeTime = $datetime_time;
    $this->configFactory = $config_factory;
    $this->httpClient = $http_client;

    // Utilise retry middleware to retry 2 times, base on timeout of 20sec.
    $stack->push(Middleware::retry($this->retryDecider(), $this->retryDelay()));
    $this->customClient = $http_client_factory->fromOptions([
      'handler' => $stack,
      'timeout' => 20,
    ]);
  }

  /**
   * {@inheritdoc}
   */
  public function apiRequest($type, $method, array $data = [], $retry = FALSE) {
    $client = $retry ? $this->customClient : $this->httpClient;
    switch ($method) {
      case "POST":
        $result = $client->request('POST', $this->requestUrl . '/' . $type, [
          'form_params' => $data,
          'headers' => [
            'Accept'     => 'application/x-www-form-urlencoded',
          ],
        ]
        );
        break;

      case "GET":
        $result = $client->request('GET', $this->requestUrl . '/' . $type, [
          'query' => $data,
          'headers' => [
            'Accept'     => 'application/json',
          ],
        ]
        );
        break;

      case "PATCH":
        $result = $client->request('PATCH', $this->requestUrl . '/' . $type, [
          'form_params' => $data,
          'headers' => [
            'Accept'     => 'application/x-www-form-urlencoded',
          ],
        ]);
    }

    $apiRequest = $result->getBody()->getContents();

    return json_decode($apiRequest);
  }

  /**
   * Boolean decider of retry attempts for Guzzle.
   */
  protected function retryDecider() {
    return function (
      $retries,
      Request $request,
      Response $response = NULL,
      RequestException $exception = NULL
   ) {
      if ((0 < $retries) && ($retries <= 2)) {
        $this->getLogger('API')->info('%uniqid SystemAction retryDecider msg="Retrying %retries"', [
          '%uniqid' => $this->uniqid,
          '%retries' => $retries,
        ]);
      }
      // Limit the number of retries to 3.
      if ($retries >= 2) {
        $this->getLogger('API')->info('%uniqid SystemAction retryDecider msg="Attempt on Retrying for Guzzle Client"', [
          '%uniqid' => $this->uniqid,
        ]);
        return FALSE;
      }

      // Retry connection exceptions.
      if ($exception instanceof ConnectException) {
        return TRUE;
      }

      if ($response) {
        // Retry on server errors.
        if ($response->getStatusCode() >= 500) {
          return TRUE;
        }
      }

      return FALSE;
    };
  }

  /**
   * Delay of each retries between request attempts.
   */
  protected function retryDelay() {
    return function ($numberOfRetries) {
      return 1000 * $numberOfRetries;
    };
  }
Score:1
vg flag

It seems to me like by altering the injected $stack, you affect the default one.

Try to create a new one with HandlerStack::create(); or clone the $stack you get injected, by $mystack = clone $stack;

There is also the onHandlerStack event you could apply, based on "what", I cannot tell you ;)

Michael Chen avatar
nl flag
Yes, I gave it a test and it worked! After your suggestion I looked into guzzle's [document](https://docs.guzzlephp.org/en/stable/handlers-and-middleware.html#handlerstack) and vuala! Thanks! Pasting workable code below. ```php $newStack = HandlerStack::create(); $newStack->push(Middleware::retry($this->retryDecider(), $this->retryDelay())); $this->wvtClient = $http_client_factory->fromOptions([ 'handler' => $newStack, 'timeout' => 20, ]); ```
mangohost

Post an answer

Most people don’t grasp that asking a lot of questions unlocks learning and improves interpersonal bonding. In Alison’s studies, for example, though people could accurately recall how many questions had been asked in their conversations, they didn’t intuit the link between questions and liking. Across four studies, in which participants were engaged in conversations themselves or read transcripts of others’ conversations, people tended not to realize that question asking would influence—or had influenced—the level of amity between the conversationalists.