Skip to content

Commit

Permalink
fix: Handle 406 http errors (#57)
Browse files Browse the repository at this point in the history
We have been noticing 406 errors when running discovery, specifically on
the `.../describe` endpoint.

```
requests.exceptions.HTTPError: 406 Client Error: CustomNotAcceptable for url: https://XXX.salesforce.com/services/data/v53.0/sobjects/XXX/describe
```

This MR adds error handling for this. The error code 406 is new to me,
so I wanted to look up what that meant. Salesforce's error codes page
does not even document it.
https://developer.salesforce.com/docs/atlas.en-us.api_rest.meta/api_rest/errorcodes.htm

From the various pieces of literature I've read, it seems that the way
to correct this issue is to set one of the accept, accept-encoding &
accept-language appropriately, however we have not been providing
alternate values for these headers and the error is encountered
intermittently.

My best guess is that a separate issue occurs, but salesforce does not
report the actual error, and instead we are returned a 406, which is not
very meaningful. To recover from these failures, we simply rerun the
taps. However, adding a retry specific to this error code seems like a
better approach and it will benefit the wider meltano / singer
community.

MDN defines 406 as

>The HyperText Transfer Protocol (HTTP) 406 Not Acceptable client error
response code indicates that the server cannot produce a response
matching the list of acceptable values defined in the request's
proactive content negotiation headers, and that the server is unwilling
to supply a default representation.
>
>In practice, this error is very rarely used. Instead of responding
using this error code, which would be cryptic for the end user and
difficult to fix, servers ignore the relevant header and serve an actual
page to the user. It is assumed that even if the user won't be
completely happy, they will prefer this to an error code.
>
>If a server returns such an error status, the body of the message
should contain the list of the available representations of the
resources, allowing the user to choose among them.

source: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/406

Found the following through more google searches:


https://zendenwebdesign.com/causes-and-fixes-for-406-error-not-acceptable/

and also, in the salesforce community forums, users reported seeing this
type of error:

https://trailhead.salesforce.com/trailblazer-community/feed/0D54S00000A93GsSAJ


# Related Issues

- closes: #58
  • Loading branch information
haleemur authored Mar 27, 2024
1 parent 64ca3f9 commit 64e05a4
Show file tree
Hide file tree
Showing 2 changed files with 40 additions and 3 deletions.
29 changes: 26 additions & 3 deletions tap_salesforce/salesforce/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,8 @@
from tap_salesforce.salesforce.rest import Rest
from tap_salesforce.salesforce.exceptions import (
TapSalesforceException,
TapSalesforceQuotaExceededException)
TapSalesforceQuotaExceededException,
SFDCCustomNotAcceptableError)
from tap_salesforce.salesforce.credentials import SalesforceAuth


Expand Down Expand Up @@ -139,6 +140,28 @@ def log_backoff_attempt(details):
LOGGER.info("ConnectionError detected, triggering backoff: %d try", details.get("tries"))


def raise_for_status(resp):
"""
Adds additional handling of HTTP Errors.
`CustomNotAcceptable` is returned during discovery with status code 406.
This error does not seem to be documented on Salesforce, and possibly
is not the best error that Salesforce could return. It also appears
that this error is ephemeral and resolved after retries.
"""
if resp.status_code != 200:
err_msg = (
f"{resp.status_code} Client Error: {resp.reason} "
f"for url: {resp.url}"
)
LOGGER.warning(err_msg)

if resp.status_code == 406 and 'CustomNotAcceptable' in resp.reason:
raise SFDCCustomNotAcceptableError(err_msg)
else:
resp.raise_for_status()


def field_to_property_schema(field, mdata):
property_schema = {}

Expand Down Expand Up @@ -269,7 +292,7 @@ def instance_url(self):

# pylint: disable=too-many-arguments
@backoff.on_exception(backoff.expo,
requests.exceptions.ConnectionError,
(requests.exceptions.ConnectionError, SFDCCustomNotAcceptableError),
max_tries=10,
factor=2,
on_backoff=log_backoff_attempt)
Expand All @@ -283,7 +306,7 @@ def _make_request(self, http_method, url, headers=None, body=None, stream=False,
else:
raise TapSalesforceException("Unsupported HTTP method")

resp.raise_for_status()
raise_for_status(resp)

if resp.headers.get('Sforce-Limit-Info') is not None:
self.rest_requests_attempted += 1
Expand Down
14 changes: 14 additions & 0 deletions tap_salesforce/salesforce/exceptions.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,17 @@ class TapSalesforceException(Exception):

class TapSalesforceQuotaExceededException(TapSalesforceException):
pass


class SFDCCustomNotAcceptableError(Exception):
"""
SFDC returned CustomNotAcceptable error with HTTP Error code 406.
This error is sometimes returned when many discovery calls are made
in quick succession. There does not seem to be documentation on this error
on any salesforce documentation page or forum.
Example Error Message:
```
requests.exceptions.HTTPError: 406 Client Error: CustomNotAcceptable for
url: https://XXX.salesforce.com/services/data/v53.0/sobjects/XXX/describe
"""

0 comments on commit 64e05a4

Please sign in to comment.