amplify-js icon indicating copy to clipboard operation
amplify-js copied to clipboard

Issues accessing response body when API Gateway returns 400

Open tom-wagner opened this issue 3 years ago • 7 comments

Before opening, please confirm:

JavaScript Framework

React

Amplify APIs

REST API

Amplify Categories

api

Environment information

# Put output below this line
npx envinfo --system --binaries --browsers --npmPackages --duplicates --npmGlobalPackages
npx: installed 1 in 0.723s

  System:
    OS: macOS Mojave 10.14.6
    CPU: (8) x64 Intel(R) Core(TM) i7-8569U CPU @ 2.80GHz
    Memory: 1.66 GB / 16.00 GB
    Shell: 3.2.57 - /bin/bash
  Binaries:
    Node: 14.8.0 - ~/.nvm/versions/node/v14.8.0/bin/node
    Yarn: 1.22.10 - /usr/local/bin/yarn
    npm: 6.14.7 - ~/.nvm/versions/node/v14.8.0/bin/npm
  Browsers:
    Chrome: 92.0.4515.159
    Firefox: 78.10.1
    Safari: 14.1.2
  npmPackages:
    @amzn/awsui-components-react-v3: ^3.0.0 => 3.0.0
    @amzn/awsui-design-tokens: ^3.0.0 => 3.0.0
    @amzn/awsui-global-styles: * => 1.0.0
    @amzn/awsui-jest-preset: * => 1.0.2
    @amzn/awsui-test-utils-multi-version: * => 1.0.0
    @amzn/brazil: ^1.1.2 => 1.1.2
    @amzn/sail_boost_event_bus_commons: * => 1.0.0
    @amzn/sail_boost_types: * => 1.0.0
    @amzn/sales-console-components-react: * => 0.0.1
    @babel/core: 7.12.3 => 7.12.3 (7.9.0)
    @babel/eslint-parser: ^7.14.7 => 7.14.7
    @my-scope/package-a:  0.0.0
    @my-scope/package-b:  0.0.0
    @pmmmwh/react-refresh-webpack-plugin: 0.4.2 => 0.4.2
    @reduxjs/toolkit: ^1.5.0 => 1.5.0
    @svgr/webpack: ^5.4.0 => 5.4.0
    @testing-library/jest-dom: ^5.11.5 => 5.11.5
    @testing-library/react: ^11.0.4 => 11.1.0
    @testing-library/user-event: ^12.2.2 => 12.2.2
    @types/lodash.flatten: ^4.4.6 => 4.4.6
    @types/lodash.get: ^4.4.6 => 4.4.6
    @types/lodash.reduce: ^4.6.6 => 4.6.6
    @types/query-string: ^6.3.0 => 6.3.0
    @types/react: ^16.9.56 => 16.9.56
    @types/react-dom: ^16.9.9 => 16.9.9
    @types/react-redux: ^7.1.16 => 7.1.16
    @types/react-router: ^5.1.8 => 5.1.8
    @types/react-router-dom: ^5.1.6 => 5.1.6
    @types/sanitize-html: ^1.27.1 => 1.27.1
    @types/yup: ^0.29.9 => 0.29.9
    @typescript-eslint/eslint-plugin: ^4 => 4.5.0
    @typescript-eslint/parser: ^4 => 4.5.0
    amazon-connect-streams: ^1.6.8 => 1.6.8
    aws-amplify: ^3.3.8 => 3.3.8
    babel-jest: ^26.6.0 => 26.6.1 (25.2.4)
    babel-loader: 8.1.0 => 8.1.0
    babel-plugin-named-asset-import: ^0.3.7 => 0.3.7
    babel-preset-react-app: ^10.0.0 => 10.0.0
    baz:  undefined ()
    bfj: ^7.0.2 => 7.0.2
    browser_field:  undefined ()
    browserslist: ^4.16.3 => 4.16.3 (4.14.2)
    case-sensitive-paths-webpack-plugin: 2.3.0 => 2.3.0
    css-loader: 4.3.0 => 4.3.0
    csstype: 3.0.4 => 3.0.4 (3.0.8)
    dotenv: ^8.2.0 => 8.2.0
    dotenv-expand: ^5.1.0 => 5.1.0
    eslint: ^7.11.0 => 7.12.0
    eslint-config-prettier: 6 => 6.14.0
    eslint-config-react-app: ^6.0.0 => 6.0.0
    eslint-plugin-flowtype: ^5.2.0 => 5.2.0
    eslint-plugin-import: ^2.22.1 => 2.22.1
    eslint-plugin-jest: ^24.1.0 => 24.1.0
    eslint-plugin-jsx-a11y: ^6.3.1 => 6.4.0
    eslint-plugin-react: ^7.21.5 => 7.21.5
    eslint-plugin-react-hooks: ^4.2.0 => 4.2.0
    eslint-plugin-testing-library: ^3.9.2 => 3.9.2
    eslint-webpack-plugin: ^2.1.0 => 2.1.0
    file-loader: 6.1.1 => 6.1.1
    formik: ^2.2.5 => 2.2.5
    fs-extra: ^9.0.1 => 9.0.1 (8.1.0, 7.0.1)
    graphql: ^15.4.0 => 15.4.0 (14.0.0)
    handlebars: ^4.7.6 => 4.7.6
    html-webpack-plugin: 4.5.0 => 4.5.0
    identity-obj-proxy: 3.0.0 => 3.0.0
    invalid main:  undefined ()
    jest: 26.6.0 => 26.6.0
    jest-circus: 26.6.0 => 26.6.0
    jest-watch-typeahead: 0.6.1 => 0.6.1
    lodash.flatten: ^4.4.0 => 4.4.0
    lodash.get: ^4.4.2 => 4.4.2
    lodash.reduce: ^4.6.0 => 4.6.0
    memo-parser:  0.2.1
    merge: ^2.1.0 => 2.1.0
    mini-css-extract-plugin: 0.11.3 => 0.11.3
    monorepo-symlink-test:  0.0.0
    mylib:  0.0.0
    node-sass: ^4.14.1 => 4.14.1
    optimize-css-assets-webpack-plugin: 5.0.4 => 5.0.4
    pnp-webpack-plugin: 1.6.4 => 1.6.4
    postcss-flexbugs-fixes: 4.2.1 => 4.2.1
    postcss-loader: 3.0.0 => 3.0.0
    postcss-normalize: 8.0.1 => 8.0.1
    postcss-preset-env: 6.7.0 => 6.7.0
    postcss-safe-parser: 5.0.2 => 5.0.2
    prettier: 2 => 2.1.2
    query-string: ^6.13.8 => 6.13.8 (4.3.4)
    quill: ^1.3.7 => 1.3.7
    react: ^17.0.1 => 17.0.1
    react-app-polyfill: ^2.0.0 => 2.0.0
    react-dev-utils: ^11.0.0 => 11.0.0
    react-dom: ^17.0.1 => 17.0.1
    react-quill: ^1.3.5 => 1.3.5
    react-redux: ^7.2.2 => 7.2.2
    react-refresh: ^0.8.3 => 0.8.3
    react-router-dom: ^5.2.0 => 5.2.0
    react-string-replace: ^0.4.4 => 0.4.4
    react-tooltip: ^4.2.13 => 4.2.13
    resolve: 1.18.1 => 1.18.1 (1.15.1, 1.20.0)
    resolve-url-loader: ^3.1.2 => 3.1.2
    sanitize-html: ^2.3.2 => 2.3.2
    sass-loader: 8.0.2 => 8.0.2
    semver: 7.3.2 => 7.3.2 (6.3.0, 5.7.1, 7.0.0, 5.3.0)
    style-loader: 1.3.0 => 1.3.0
    terser-webpack-plugin: 4.2.3 => 4.2.3 (1.4.5)
    typescript: 4.0 => 4.0.3
    url-loader: 4.1.1 => 4.1.1
    webpack: 4.44.2 => 4.44.2
    webpack-dev-server: 3.11.0 => 3.11.0
    webpack-manifest-plugin: 2.2.0 => 2.2.0
    workbox-webpack-plugin: 5.1.4 => 5.1.4
    yup: ^0.29.3 => 0.29.3
  npmGlobalPackages:
    aws-cdk: 1.115.0
    npm: 6.14.7
    serverless: 2.52.1

Describe the bug

When calling API.get() on a route with CORS enabled and "Default 4xx" configured in the API Gateway console, the 400 response returns a value in the network tab but there is no data on error.response.data in the catch block. This issue has been discussed ad nauseam around the internet, including here: https://github.com/aws-amplify/amplify-js/issues/6661

Additionally, Amplify in the docs mentions it is a wrapper on Axios, and the issue is documented on the Axios repo here. It does not seem like the underlying Axios issue has ever been resolved https://github.com/axios/axios/issues/960

image

image

image

Expected behavior

We would expect to be able to access the error response body via error.response.data.

Reproduction steps

Follow the steps as outlined here: https://github.com/aws-amplify/amplify-js/issues/6661

Code Snippet

return new Promise((resolve, reject) => {
    API
      .get(
        config.getSparkApiGatewayConfig().name,
        "/usr/uploads",
        {}
      )
      .then(response => {
        debugger;
        resolve(response);
      })
      .catch(error => {
        debugger;
        reject(error);
      });
     // as IListUploadsResponse);
  });

Log output

No response

aws-exports.js

No response

Manual configuration

No response

Additional configuration

No response

Mobile Device

No response

Mobile Operating System

No response

Mobile Browser

No response

Mobile Browser Version

No response

Additional information and screenshots

No response

tom-wagner avatar Aug 26 '21 11:08 tom-wagner

This also seems to be a very common customer issue per a simple Google search

tom-wagner avatar Aug 26 '21 11:08 tom-wagner

Bumping this -- any update?

tom-wagner avatar Sep 14 '21 20:09 tom-wagner

Screen Shot 2021-09-15 at 6 16 31 PM

I can see my error response when calling a customer HTTP API endpoint. There might be something missing in REST API endpoint setting.

hkjpotato avatar Sep 15 '21 22:09 hkjpotato

any update?

ynnr85 avatar Jul 04 '22 17:07 ynnr85

image Note it happens on 500 status response also

ynnr85 avatar Jul 04 '22 17:07 ynnr85

I haven't been able to reproduce this error following https://github.com/aws-amplify/amplify-js/issues/6661. @ynnr85 if you have the time, could you find the minimum reproduction so that we can investigate the issue?

dpilch avatar Jul 19 '22 15:07 dpilch

Hi @dpilch , I'm having the same behavior in my application. Every time I call an endpoint and the HTTP code of the response is >=300 I just get a message saying the error code (eg Request failed with status code 404) and I don't have access to the payload.

I'll use the post method as an example. based on what I understand reading the code of this library the problem occurs because on line 413 of the RestClient.ts file. (https://github.com/aws-amplify/amplify-js/blob/41ab57a854676813c3f9d89d647a8ac8f7b2c65f/packages/api-rest/src/RestClient.ts#L413) .

		return axios(signed_params)
			.then(response => (isAllResponse ? response : response.data))
			.catch(error => {
				logger.debug(error);
				throw error;
			});

When axios returns an error, the code throws this error to the RestAPI.ts file, line 158. https://github.com/aws-amplify/amplify-js/blob/41ab57a854676813c3f9d89d647a8ac8f7b2c65f/packages/api-rest/src/RestAPI.ts#L158

	post(apiName, path, init): Promise<any> {
		try {
			const apiInfo = this.getEndpointInfo(apiName, path);

			const cancellableToken = this._api.getCancellableToken();

			const initParams = Object.assign({}, init);
			initParams.cancellableToken = cancellableToken;

			const responsePromise = this._api.post(apiInfo, initParams); //<---- HERE calls RestClient

			this._api.updateRequestToBeCancellable(responsePromise, cancellableToken);

			return responsePromise;
		} catch (err) {
			return Promise.reject(err.message); //<---- HERE 's rejecting the promise sending only the message property. It's why we don't see the the data on response.
		}
	}

I believe a solution would be to return the err object instead of the err.message when the response parameter is true, something like:

return Promise.reject(initParams.isAllResponse ? err : err.message) (would need some more refactory in the function to work, but it's the idea)

I tested with the latest version of axios it always throws an error when the http code is >= 300, but I saw in the documentation that it is a configurable behavior through the validateStatus function https://axios -http.com/docs/handling_errors.

I used this piece of code to test. if you comment the validateStatus and call a URL that returns http 404 for example, it will fall into the catch block.

const main = async () => {
    axios.post('http://localhost:9000/app/user/login', {}, 
        {
            validateStatus: function (status) {
                return status < 500;
            }
        }
    ).then((data) => {
        console.log('success');
    }).catch((error) => {
        console.log(error.message);
    })
}
main();

it would also be a solution to add this behavior to axios, but I think it has more potential to cause problems for those who already use the lib as it is. (or exposing some way to inject parameters to axios instance, there is also another open issue about it.)

In my scenario the problem is not with CORS, but this unexpected behavior forced me to change my API responses to code 200 and payload information in order to map the error in the application and continue using this component. for example, the way the code is written at the moment, the only way I found to know an 404 error happened would be parsing the string "Request failed with status code 404".

If I can help in any way to solve the problem, let me know.

oakideas avatar Sep 19 '22 04:09 oakideas

Hi, Any update on this issue? We are facing the same problem. The error is visible in the Network tab, but not available through error.response. It is kind of critical as the frontend error handlers cannot display specific error messages.

shwetajoshi601 avatar Dec 12 '23 09:12 shwetajoshi601

I am using 6.0.9 version and the problem persist. It is related to this other issue #6661. But I have found a workaround only if you are able to modify the backend and I think I have found the problem in the library.

So when the API response with status code greater than or equal to 300 the following code is executed:

export const parseJsonError: ErrorParser = async (response?: HttpResponse) => {
	if (!response || response.statusCode < 300) {
		return;
	}
	const body = await parseJsonBody(response);
	const sanitizeErrorCode = (rawValue: string | number): string => {
		const [cleanValue] = rawValue.toString().split(/[\,\:]+/);
		if (cleanValue.includes('#')) {
			return cleanValue.split('#')[1];
		}
		return cleanValue;
	};
	const code = sanitizeErrorCode(
		response.headers['x-amzn-errortype'] ??
			body.code ??
			body.__type ??
			'UnknownError'
	);
	const message = body.message ?? body.Message ?? 'Unknown error';
	const error = new Error(message);
	return Object.assign(error, {
		name: code,
		$metadata: parseMetadata(response),
	});
};

parseJsonBody looks like is used to retrieve the body of the response:

export const parseJsonBody = async (response: HttpResponse): Promise<any> => {
	if (!response.body) {
		throw new Error('Missing response payload');
	}
	const output = await response.body.json();
	return Object.assign(output, {
		$metadata: parseMetadata(response),
	});
};

(Both functions are in the same file: packages/core/src/clients/serde/json.ts)

In the parseJsonError function the only keys that are being used from the body are message, code and __type. This means that the only way to get any feadback from the backend is that the response body has any of these keys.

So the only way I have found to handle the error on the frontend is that the backend response looks like this:

{
  "message": "The error is..."
}

I hope this can be useful to solve the problem and to those who need a temporary solution.

dvalbuena1 avatar Dec 24 '23 18:12 dvalbuena1

Is there any other way to get the content of the body of the error other than changing the backend code?

tuskermanshu avatar Feb 02 '24 05:02 tuskermanshu

We've released a fix addressing this and https://github.com/aws-amplify/amplify-js/issues/12943 with Amplify JS v6.0.15.

Closing this but if you are still experiencing issues related to accessing the body of error responses, please open a new issue!

Also, please refer to our documentation for more info on accessing http responses: https://docs.amplify.aws/react/build-a-backend/restapi/fetch-data/#access-http-response-from-errors

chrisbonifacio avatar Feb 14 '24 18:02 chrisbonifacio