javascriptgoogle-apps-scripthttp-headerstry-catchurlfetch

How do I get the HTTP status code from UrlFetchApp.fetch() when it throws an error?


I am using Google Apps Script to fetch data from an external URL. When the request is successful, I can get the HTTP status code with HTTPResponse.getResponseCode. However, if the request fails with a HTTP status error (such as 404 or 429), UrlFetchApp.fetch() throws an exception, and I am unable to access the HTTP status code.

function GetHttpResponseCode(url) {
  try {
    const response = UrlFetchApp.fetch(url);
    return response.getResponseCode();
  } catch (error) {
    return response.getResponseCode(); // ReferenceError: response is not defined
    // return error.getResponseCode(); // TypeError: error.getResponseCode is not a function
    // throw new Error(error.message);
  }
}
GetHttpResponseCode("https://www.google.com/invalidurl") // Should return 404 (Page not Found)

When I catch the error, the response variable isn't accessible from the catch block. Similarly, calling error.getResponseCode() results in a TypeError.

For context, my goal is to detect a 429 (Too Many Requests) response and retry the request after a delay. Here is the actual retry logic I am trying to implement.

function FetchURLContents(url) {
    const maxRetries = 3;

    for (let attempt = 0; attempt < maxRetries; attempt++) {
        try {
            const response = UrlFetchApp.fetch(url);
            return response.getContentText();
        } catch (error) {
            // If statusCode is 429 (too many requests), retry after delay
            if (response.getResponseCode() === 429) // ReferenceError here
            {
                Utilities.sleep(2000);
            } else {
                throw new Error(error.message);
            }
        }
    }
    throw new Error('Failed to fetch URL after multiple attempts.');
}

Is there a way to retrieve the HTTP status code after UrlFetchApp.fetch() throws an error?


Solution

  • You can prevent UrlFetchApp.fetch from raising an exception for response codes that indicate failure by setting the advanced parameter muteHttpExceptions=True.

    function GetHttpResponseCode(url) {
        const options = {muteHttpExceptions: true};
        const response = UrlFetchApp.fetch(url, options);
        return response.getResponseCode();
    }
    
    function test() {
      var responseCode = GetHttpResponseCode("https://www.google.com/invalidurl");
      console.log(responseCode); // 404
    }
    

    Note that this does mean you need handle unexpected HTTP status errors outside the catch blocks. For my intended use case of retrying on a 429 error, I wrote the following code:

    function FetchURLContents(url) {
      const maxRetries = 3;
      const waitTime = 2000; // 2 seconds
      const options = {muteHttpExceptions: true};
    
      for (let attempt = 0; attempt < maxRetries; attempt++) {
        console.log(`Sending request to ${url}`);
        var response = UrlFetchApp.fetch(url, options);
        var responseCode = response.getResponseCode();
        var responseBody = response.getContentText();
    
        if (responseCode >= 100 && responseCode < 400) {
          // Info/Success/Redirect
          console.log("Request successful.");
          return responseBody;
        } else if (responseCode === 429) {
          // Too many requests, wait then retry
          if (attempt < maxRetries-1) {
            console.log(`You are being rate limited. Retrying again in ${waitTime} ms.`)
            Utilities.sleep(waitTime);
          } else {
            throw new Error(`Failed to fetch url after ${maxRetries} attempts due to rate limits (Status 429).`)
          }
        } else {
          // Unhandled error code
          throw new Error(`Request failed for ${url} returned code ${responseCode}. Server response: ${responseBody}`);
        }
      }
    }