How to return error collection/object from AWS Lam

2019-07-13 17:28发布

问题:

I am attempting to return an object from a AWS Lambda function instead of a simple string.

// ...
    context.fail({
        "email": "Email address is too short",
        "firstname": "First name is too short"
    });
// ...

I have already used the errorMessage for mapping error responses to status codes and that has been great:

// ...
    context.fail('That "username" has already been taken.');
// ...

Am I simply trying to do something that the AWS API Gateway does not afford?

I have also already found this article which helped: Is there a way to change the http status codes returned by Amazon API Gateway?.

回答1:

Update Since time of writing, lambda has updated the invocation signature and now passes event, context, callback.

Instead of calling context.done(err, res) you should use callback(err, res). Note that what was true for context.done still applies to the callback pattern.

Should also add that with API Gateways proxy and integration implementation this entire thread is pretty much obsolete. I recommend reading this article if you are integrating API Gateway with Lambda: http://docs.aws.amazon.com/apigateway/latest/developerguide/api-gateway-create-api-as-simple-proxy-for-lambda.html

Original response below

First things first, let's clear a few things up.

context.done() vs. context.fail()/context.success

context.done(error, result); is nothing but a wrapper around context.fail(error); and context.success(response); The Lambda documentation clearly states that result is ignored if error is non null:

If the Lambda function was invoked using the RequestResponse (synchronous) invocation type, the method returns response body as follows: If the error is null, set the response body to the string representation of result. This is similar to the context.succeed(). If the error is not null, set the response body to error. If the function is called with a single argument of type error, the error value will be populated in the response body. http://docs.aws.amazon.com/lambda/latest/dg/nodejs-prog-model-context.html

What this means is that it won't matter whether you use a combination of fail/success or done, the behaviour is exactly the same.

API Gateway and Response Code Mapping

I have tested every thinkable combination of response handling from Lambda in combination with Response code mapping in API Gateway.

The conclusion of these tests are that the "Lambda Error RegExp" is only executed against a Lambda error, i.e: you have to call context.done(error);or context.fail(error); for the RegExp to actually trigger.

Now, this presents a problem as, has already been noted, Lambda takes your error and sticks it in an object and calls toString() on whatever you supplied:

{ errorMessage: yourError.toString() }

If you supplied an error object you'll get this:

{ errorMessage: "[object Object]" }

Not very helpful at all.

The only workaround I have found thus far is to call

context.fail(JSON.stringify(error));

and then in my client do:

var errorObject = JSON.parse(error.errorMessage);

It's not very elegant but it works. As part of my error I have a property called "code". It could look something like this:

{ 
    code: "BadRequest", 
    message: "Invalid argument: parameter name" 
}

When I stringify this object I get:

"{\"code\":\"BadRequest\",\"message\":\"Invalid argument: parameter name\"}"

Lambda will stick this string in the errorMessage property of the response and I can now safely grep for .*"BadRequest".* in the API Gateway response mapping.

It's very much a hack that works around two somewhat strange quirks of Lambda and API Gateway:

  1. Why does Lambda insist on wrapping the error instead of just giving it back as is?
  2. Why doesn't API Gateway allow us to grep in the Lambda result, only the error?

I am on my way to open a support case with Amazon regarding these two rather odd behaviours.



回答2:

You don't have to use context.fail, use success but send different statusCode and an errorMessage, here is an example of how i format my output:

try {
    // Call the callable function with the defined array parameters
    // All the function called here will be catched if they throw exceptions
    result.data = callable_function.apply(this, params);
    result.statusCode = 200;
    result.operation = operation;
    result.errorMessage = ""
} catch (e) {
    result.data = [];
    result.statusCode = 500;
    result.errorMessage = e.toString();
    result.method = method;
    result.resource = resource;
}

// If everything went smooth, send back the result
// If context succeed is not called AWS Lambda will fire the function
// again because it is not successfully exited
context.succeed(result);

Use the consumer logic to handle different errors case logic, don't forget that you pay for the time your function is running...



回答3:

You should replace the use of your context.fail with context.done and use context.fail only for very serious Lambda function failures since it doesn't allow more than one output parameter. Integration Response is able to match mapping template by performing regex on the first parameter passed to context.done this also maps HTTP status code to the response. You can't pass this response status code directly from Lambda since it's the role of API Gateway Integration Response to abstract the HTTP protocol.

See the following:

context.done('Not Found:', <some object you can use in the model>);

and the Integration Response panel this setting:

You can replicate similar approach for any kind of error. You should also create and map the error model to your response.



回答4:

For those who tried everything put on this question and couldn't make this work (like me), check the thedevkit comment on this post (saved my day):

https://forums.aws.amazon.com/thread.jspa?threadID=192918

Reproducing it entirely below:

I've had issues with this myself, and I believe that the newline characters are the culprit.

foo.* will match occurrences of "foo" followed by any characters EXCEPT newline. Typically this is solved by adding the '/s' flag, i.e. "foo.*/s", but the Lambda error regex doesn't seem to respect this.

As an alternative you can use something like: foo(.|\n)*