contentful.js icon indicating copy to clipboard operation
contentful.js copied to clipboard

How to access headers on 429 Responses?

Open abmagil opened this issue 5 years ago • 2 comments

Expected Behavior

On error, with retryOnError set to false, when I hit the rate limit, I would expect to receive an error that exposes the rate-limit metadata referenced here.

Actual Behavior

The error that is sent back is a JS error with string fields that do not include the headers that offer the metadata about how long to wait.

{
  "status": 429,
  "statusText": "Too Many Requests",
  "message": "You have exceeded the rate limit of the Organization this Space belongs to by making too many API requests within a short timespan. Please wait a moment before trying the request again.",
  "details": {},
  "request": {
    "url": "<ENTRY URL>",
    "headers": {
      "Accept": "application/json, text/plain, */*",
      "Content-Type": "application/vnd.contentful.management.v1+json",
      "X-Contentful-User-Agent": "sdk contentful-management.js/5.2.1; platform node.js/v10.15.3; os Windows/10.0.17134;",
      "Authorization": "Bearer ...i0Qlc",
      "user-agent": "node.js/v10.15.3",
      "Accept-Encoding": "gzip",
      "X-Contentful-Version": 65,
      "Content-Length": 427
    },
    "method": "put",
    "payloadData": "<ENTRY DATA>"
  },
  "requestId": ""
}

Possible Solution

include a javascript object within the caught error that exposes the X-Contentful-RateLimit-Hour-Limit, X-Contentful-RateLimit-Hour-Remaining, X-Contentful-RateLimit-Reset, X-Contentful-RateLimit-Second-Limit, X-Contentful-RateLimit-Second-Remaining headers.

Even better might be to allow me to provide my own Axios client that I could configure myself, which would give me access to the catch behavior directly.

Steps to Reproduce

  1. Create a Management Environment client with retryOnError set to false
const client = contentfulManagement.createClient({ accessToken: this.config.CONTENT_MANAGEMENT_TOKEN, retryOnError: false });
const space = await client.getSpace(this.spaceId);
const updateEnvironment = await space.getEnvironment(this.environmentId);
  1. Using this environment client, exceed the rate limit.
while(true) {
    try {
        await updateEnvironment.getEntry(entryId)
    } catch (err) {
        console.log(err) // this is where I would like access to rate-limit response headers, but don't have it
    }
}

Context

We need to post back to Contentful with an evergreen timestamp in the payload for validation logic which we do on our own. Because we didn't have access to the payload using retryOnError: true, we needed to roll our own retry logic. Unfortunately, we then encountered this behavior, which prevents us from rate limiting on our side.

Environment

  • Package Version: "contentful": "7.7.0", "contentful-management": "5.8.0",

  • Which API are you using?: Management

abmagil avatar Oct 09 '19 21:10 abmagil

Hi @abmagil, You can pass a handler to be called every time you receive a rate limit error and there you will get the headers you need.

const client = contentfulManagement.createClient({ accessToken: this.config.CONTENT_MANAGEMENT_TOKEN, retryOnError: false, logHandler: (loglevel, data)=>{} });

Khaledgarbaya avatar Oct 10 '19 08:10 Khaledgarbaya

Hi @Khaledgarbaya - I updated my code to be

const logHandler= (loglevel: any, data: any) => { 
                console.log(loglevel);
                console.log(data);
            };
            // We should delete this hard-coded token and delete it from Contentful.
            const client = contentfulManagement.createClient({ accessToken: this.config.CONTENT_MANAGEMENT_TOKEN, retryOnError: false, logHandler});

and re-ran my calls. I saw many Rate limit error occurred. Waiting for 1641 ms before retrying... errors in my console, but never any logLevel or error data logging. I set a breakpoint in this code and it didn't hit either.

Is there a missing step in your solution? I don't seem to ever hit that code.

abmagil avatar Oct 10 '19 12:10 abmagil

Hello. Any update concerning that issue? I do have the same behaviour, using either responseLogger, requestLogger or logHandler.

n-eit avatar Mar 22 '23 18:03 n-eit

closing the issue, if you still need help with this please reach out to Contentful support

mayakarabula avatar Jun 13 '23 14:06 mayakarabula

@n-eit I have been battling this the last few days. I found that on a successful response I could access response.headers['x-contentful-ratelimit-reset'], but in the case of a 429, that is wrapped in an extra layer of response and so you have to access response.response.headers['x-contentful-ratelimit-reset']

const client = contentful.createClient({
  accessToken: '<my-token>',
  responseLogger: async (response) => {
      if (response.headers) {
          console.log(new Date().getTime() / 1000, response.headers['x-contentful-ratelimit-reset'], response.headers['x-contentful-ratelimit-second-limit'], response.headers['x-contentful-ratelimit-second-remaining']);
      } else {
          console.log(new Date().getTime() / 1000, response.response.headers['x-contentful-ratelimit-reset'], response.response.headers['x-contentful-ratelimit-second-limit'], response.response.headers['x-contentful-ratelimit-second-remaining']);
      }
  },
});

votemike avatar Mar 07 '24 11:03 votemike