Guzzling with Drupal.

Drupal uses Guzzle to make http requests. Guzzle comes from Drupal’s Symfony Framework integration and is a full-featured service class to make any type of http request that might be required. Although Drupal’s move in 2021 to implement this highly performant library was a stroke of genius, its adoption has left some devs grumbling.

I’ll talk about the best ways to use Guzzle programmatically for making complex and differing requests in your custom Drupal application.

The basic format for http requests in Drupal that you will see over and over on the web is something like:

use GuzzleHttp\Client;
use GuzzleHttp\Exception\RequestException;

$client = \Drupal::httpClient();

try {
  $response = $client->get('https://someexternal.url.com');
  $result = $response->getBody();
    // Do something with the results here.
}
catch (RequestException $e) {
  // Log exception stacktrace.
}

This method will work, but it’s not particularly robust, nor is it easy to debug when something goes wrong. Http requests can fail for a myriad of reasons, sometimes due to the response we get back from remote resources, other times due to how we handle something unexpected.

In a production environment, it is critical for triage and repair of connectivity issues between applications to get a clear picture of what is happening when things go awry. Without proper handling of the request output and error output, developers are forced to wade through entire bushels of stack trace messages in the Drupal error log to try and figure out went wrong and why.

One thing that really gets my goat is when the error message ends up displayed to the user. That’s bad practice because of information disclosure and degradation of the user experience.

When I am asked to create or upgrade code that will use the http client interface, I like to create a function wrapper, in a Drupal service, to handle the work.

Drupal developers should always ask themselves: “Do I need to write this boilerplate code or does it already exist in Drupal somewhere”.

For a programming a GET request, I recommend the following approach.

  1. Don’t return bad responses to your application, return FALSE instead.
  2. Only pass $response->getBody() on responses that have a valid body.
  3. Log the correct exceptions, not the default one.
  4. Use one service to handle all types of GET requests.
  5. Log the requests that completed, but did not return what we want.

To break it down, here is an example of a protected function from our claude_http module code that will fit the bill. This code comes from the claude_http ConnectorService service class.


  /**
   * @param $url
   * @param $params
   * @param $headers
   * @return false|string
   * @throws GuzzleException
   */
  protected function httpGet($url, $params, $headers) {
    $data = FALSE;
    try {
      $response = $this->httpClient->get($url, [
        'headers' => $headers,
        'query' => $params,
      ]);
      $status = $response->getStatusCode();
      if ($status == 200) {
        $data = $response->getBody()
          ->getContents();
      }
      else {
        $this->loggerFactory->get('claude_http')
          ->warning('httpGet() returned a status ' . $status . ' with the response ' .   $response->getBody()
              ->getContents());
      }
    }
    catch (RequestException | ConnectException $e) {
      $this->loggerFactory->get('claude_http')
        ->error($e);
    }
    return $data;
  }

}

Now we are able to utilize our service in a simple and clean format.

$url = 'https://someexternal.url.com/';
$params = ['size' => '4', 'shape' => 'square'];
$headers = ['User-Agent' => 'testing/1.0', 'X_Foo' => 'foo/1.1']

$ourData = $this->httpGet($url, $params, $headers);

if ($ourData) {
  // Do something with $ourData.
}
else {
  // Exceptions and errors already handled, simplifying complexity.
}

Using this method, logging is also simplified. We are not dumping so many cryptic stack traces into our logs, but rather more structured messages that let us understand what is going on under the hood. Log inspection is simplified with more detailed and specific messages that can be quickly accessed from the Drupal logs page at ‘/admin/reports/dblog’.

The claude_http module repository is on Github.

Thank you!

Image of LAMP stack with animals.