Amazon API Gateway是否有改变返回的HTTP状态码的方法?

123
例如,如果我想为无效参数返回特定的400错误码,或者当lambda函数调用导致创建时返回201。
我希望有不同的HTTP状态码,但是看起来API网关总是返回200状态码,即使lambda函数返回错误。

2
看起来我遇到的问题是我返回了一个自定义错误类型,这使得 errorMessage 正则表达式无法正确工作。从 lambda 返回一个标准字符串将使下面的解决方案起作用 - 但是返回自己的自定义错误对象则不行。 - MonkeyBonkey
我的解决方案是从Serveless版本0.5切换到1.0。此外,我正在使用Serveless文档中的响应,在响应对象中指定statusCode属性。希望能有所帮助。 - Relu Mesaros
12个回答

110

更新于2016年9月20日

通过使用Lambda Proxy集成,亚马逊终于让这变得更加容易。这使得您的Lambda函数能够返回正确的HTTP代码和标头:

let response = {
    statusCode: '400',
    body: JSON.stringify({ error: 'you messed up!' }),
    headers: {
        'Content-Type': 'application/json',
    }
};

context.succeed(response);

告别API Gateway中的请求/响应映射!

选项2

使用aws-serverless-express将现有的Express应用程序集成到Lambda/API Gateway。


9
对于任何想知道的人,这也可以通过使用新的“callback”风格来实现。只需执行callback(null, {statusCode: 200, body: 'whatever'})即可。 - Widdershin
1
@unclemeat 我有同样的问题。你解决了吗?如何在Python中实现这个? - Sushil
1
@Sushil,是的,你只需要像上面的响应变量一样返回JSON即可。 - unclemeat
9
@Sushil 我已经用LambdaProxyIntegration在Python中解决了这个问题,并返回以下内容:return { "isBase64Encoded": True, "statusCode": 200, "headers": { }, "body": "" } - Jithu R Jacob
1
这些不是有效的状态码... https://developer.mozilla.org/zh-CN/docs/Web/HTTP/Status - Ryan Schaefer
显示剩余6条评论

79

这是返回自定义HTTP状态码和自定义错误消息的最快方法:

在API Gateway仪表板中,按照以下步骤操作:

  1. 在您的资源的方法中,单击方法响应
  2. HTTP状态表中,单击添加响应并添加您想要使用的每个HTTP状态码。
  3. 在您的资源的方法中,单击集成响应
  4. 为您先前创建的每个HTTP状态码添加一个集成响应。确保选中输入透传。使用lambda error regex来识别当您从lambda函数返回错误消息时应使用哪个状态码。例如:

    // Return An Error Message String In Your Lambda Function
    
    return context.fail('Bad Request: You submitted invalid input');
    
    // Here is what a Lambda Error Regex should look like.
    // Be sure to include the period and the asterisk so any text
    // after your regex is mapped to that specific HTTP Status Code
    
    Bad Request: .*
    
  5. 您的API网关路由应返回以下内容:

  6. HTTP Status Code: 400
    JSON Error Response: 
        {
            errorMessage: "Bad Request: You submitted invalid input"
        }
    
  7. 我看不到任何方法可以复制这些设置并在不同的方法中重复使用它,因此我们需要进行许多烦人的冗余手动输入!

我的整合响应看起来像这样:

aws api gateway lambda error response handling


3
看起来我的问题是正则表达式的触发器从未工作,因为在失败方法中我返回了来自Lambda的错误对象,而不仅仅是一个字符串。例如:return context.fail(new Error('bad one')) - MonkeyBonkey
8
@kalisjoshua,我最近发表了一篇关于API Gateway/Lambda错误处理的详细文章:http://www.jayway.com/2015/11/07/error-handling-in-api-gateway-and-aws-lambda/。 - Carl
9
Python Lambda中的context.fail相当于什么? - routeburn
1
非错误响应中是否没有更改状态代码的方法?如果我想要发送“201 Created”以及创建的对象,该怎么办? - Ben Davis
1
@CoryMawhorter 那并不能真正解决问题。对资源进行PUT请求可能会导致200 OK201 CREATED的响应。 - Ben Davis
显示剩余8条评论

21
为了将自定义错误对象作为JSON返回,您需要跳过一些麻烦。首先,您必须使Lambda失败并传递一个字符串化的JSON对象:
exports.handler = function(event, context) {
    var response = {
        status: 400,
        errors: [
            {
              code:   "123",
              source: "/data/attributes/first-name",
              message:  "Value is too short",
              detail: "First name must contain at least three characters."
            },
            {
              code:   "225",
              source: "/data/attributes/password",
              message: "Passwords must contain a letter, number, and punctuation character.",
              detail: "The password provided is missing a punctuation character."
            },
            {
              code:   "226",
              source: "/data/attributes/password",
              message: "Password and password confirmation do not match."
            }
        ]
    }

    context.fail(JSON.stringify(response));
};

接下来,您需要为希望返回的每个状态码设置正则表达式映射。使用我上面定义的对象,您可以为400设置这个正则表达式:

.*"status":400.*

最后,您需要设置一个映射模板,从Lambda返回的errorMessage属性中提取JSON响应。映射模板如下所示:

$input.path('$.errorMessage')

我在这篇文章中写了更详细的内容,并解释了从Lambda到API Gateway的响应流程:http://kennbrodhagen.net/2016/03/09/how-to-return-a-custom-error-object-and-status-code-from-api-gateway-with-lambda/


@kennbrodhagen 你了解API网关和Java Lambdas吗?我正在使用一种类似的正则表达式,但它对我不起作用。我使用 .*statusCode":422.*。 - Perimosh
@Perimosh 请查看这篇文章,它解释了如何使用Java异常来实现此功能:https://aws.amazon.com/blogs/compute/redirection-in-a-serverless-api-with-aws-lambda-and-amazon-api-gateway/ - kennbrodhagen

11

1) 配置API Gateway资源,通过在API Gateway资源定义的"Integration Request"屏幕上选中标有"使用Lambda代理集成"的复选框来使用Lambda代理集成。(或在您的cloudformation/terraform/serverless/etc配置文件中定义)

2) 更改您的Lambda代码两个方面:

  • 适当处理传入的event(第一个函数参数)。它不再仅是裸载荷,而是表示包括标头、查询字符串和正文在内的整个HTTP请求。如下所示。关键点是JSON正文将是需要显式调用JSON.parse(event.body)的字符串(不要忘记在其周围加上try/catch)。示例如下。
  • 通过使用null然后提供HTTP细节的响应对象调用回调来响应,包括statusCode, bodyheaders
    • body 应该是一个字符串,所以根据需要执行JSON.stringify(payload)
    • statusCode 可以是数字
    • headers 是一个包含标头名称和值的对象

用于代理集成的示例Lambda事件参数

{
    "resource": "/example-path",
    "path": "/example-path",
    "httpMethod": "POST",
    "headers": {
        "Accept": "*/*",
        "Accept-Encoding": "gzip, deflate",
        "CloudFront-Forwarded-Proto": "https",
        "CloudFront-Is-Desktop-Viewer": "true",
        "CloudFront-Is-Mobile-Viewer": "false",
        "CloudFront-Is-SmartTV-Viewer": "false",
        "CloudFront-Is-Tablet-Viewer": "false",
        "CloudFront-Viewer-Country": "US",
        "Content-Type": "application/json",
        "Host": "exampleapiid.execute-api.us-west-2.amazonaws.com",
        "User-Agent": "insomnia/4.0.12",
        "Via": "1.1 9438b4fa578cbce283b48cf092373802.cloudfront.net (CloudFront)",
        "X-Amz-Cf-Id": "oCflC0BzaPQpTF9qVddpN_-v0X57Dnu6oXTbzObgV-uU-PKP5egkFQ==",
        "X-Forwarded-For": "73.217.16.234, 216.137.42.129",
        "X-Forwarded-Port": "443",
        "X-Forwarded-Proto": "https"
    },
    "queryStringParameters": {
        "bar": "BarValue",
        "foo": "FooValue"
    },
    "pathParameters": null,
    "stageVariables": null,
    "requestContext": {
        "accountId": "666",
        "resourceId": "xyz",
        "stage": "dev",
        "requestId": "5944789f-ce00-11e6-b2a2-dfdbdba4a4ee",
        "identity": {
            "cognitoIdentityPoolId": null,
            "accountId": null,
            "cognitoIdentityId": null,
            "caller": null,
            "apiKey": null,
            "sourceIp": "73.217.16.234",
            "accessKey": null,
            "cognitoAuthenticationType": null,
            "cognitoAuthenticationProvider": null,
            "userArn": null,
            "userAgent": "insomnia/4.0.12",
            "user": null
        },
        "resourcePath": "/example-path",
        "httpMethod": "POST",
        "apiId": "exampleapiid"
    },
    "body": "{\n  \"foo\": \"FOO\",\n  \"bar\": \"BAR\",\n  \"baz\": \"BAZ\"\n}\n",
    "isBase64Encoded": false
}

示例回调响应格式

callback(null, {
  statusCode: 409,
  body: JSON.stringify(bodyObject),
  headers: {
    'Content-Type': 'application/json'
  }
})

注意事项 - 我认为像context.succeed()这样的context方法已经被弃用了。虽然它们似乎仍然可以工作,但它们不再有文档记录。我认为编写回调API是正确的做法。


2
这个不起作用。我仍然得到200状态返回,带有整个响应输出。无法设置API以实际返回409状态。 - Andy N
使用这种设置时,4xx和5xx的问题通常与CORS有关。这是解决方法 - Ricardo Nolde

10

我希望Lambda返回的错误能够是一个合适的500错误,经过多次研究,我想出了以下方法:

在Lambda中

为了得到良好的响应,我将返回结果如下:

exports.handler = (event, context, callback) => {
    // ..

    var someData1 =  {
        data: {
            httpStatusCode: 200,
            details: [
                {
                    prodId: "123",
                    prodName: "Product 1"
                },
                {
                    "more": "213",
                    "moreDetails": "Product 2"
                }
            ]
        }
    };
    return callback(null, someData1);
}

对于一个坏的响应,返回如下

exports.handler = (event, context, callback) => {
    // ..

    var someError1 = {
        error: {
            httpStatusCode: 500,
            details: [
                {
                    code: "ProductNotFound",
                    message: "Product not found in Cart",
                    description: "Product should be present after checkout, but not found in Cart",
                    source: "/data/attributes/product"
                },
                {
                    code: "PasswordConfirmPasswordDoesntMatch",
                    message: "Password and password confirmation do not match.",
                    description: "Password and password confirmation must match for registration to succeed.",
                    source: "/data/attributes/password",
                }
            ]
        }
    };

    return callback(new Error(JSON.stringify(someError1)));
}

关于API网关

对于GET方法,例如获取/res1/service1:

Through Method Response > Add Response, added 3 responses:
- 200
- 300
- 400

然后,

Through 'Integration Response' > 'Add integration response', create a Regex for 400 errors (client error):

Lambda Error Regex    .*"httpStatusCode":.*4.*

'Body Mapping Templates' > Add mapping template as:  
    Content-Type                 application/json  
    Template text box*           $input.path('$.errorMessage')  


Similarly, create a Regex for 500 errors (server error):

Lambda Error Regex    .*"httpStatusCode":.*5.*

'Body Mapping Templates' > Add mapping template as:  
    Content-Type                 application/json  
    Template text box*           $input.path('$.errorMessage')  

现在,发布 /res1/service1 ,访问已发布的 URL,该 URL 连接到上面提到的 Lambda。

使用高级 REST 客户端(或 Postman)Chrome 插件,您将看到正确的 HTTP 状态码,例如服务器错误(500)或 400,而不是所有请求都给出“httpStatusCode”中的 200 HTTP 响应代码。

从 API Gateway 的“仪表板”中,我们可以查看以下 HTTP 状态码:

400 & 500 错误


8

最简单的方法是使用LAMBDA_PROXY集成。使用这种方法,您不需要设置任何特殊的转换到API Gateway管道中。

您的返回对象必须类似于下面的片段:

module.exports.lambdaHandler = (event, context, done) => {
    // ...
    let response = {
        statusCode: 200, // or any other HTTP code
        headers: {       // optional
             "any-http-header" : "my custom header value"
        },
        body: JSON.stringify(payload) // data returned by the API Gateway endpoint
    };
    done(null, response); // always return as a success
};

它确实有一些缺点:需要特别注意错误处理,并将您的Lambda函数与API Gateway端点耦合; 话虽如此,如果您不打算在其他地方使用它,那么这并不是太大的问题。

7

对于那些尝试了所有方法但仍然无法解决这个问题的人(例如我),请查看此帖子中的thedevkit评论(拯救了我的一天):

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

以下是完全复制的内容:

我自己也遇到了这个问题,我认为换行符是罪魁祸首。

foo.*将匹配跟在“foo”后面的任何字符,但不包括换行符。通常通过添加'/s'标志来解决这个问题,即“foo.*/s”,但Lambda错误正则表达式似乎不尊重这一点。

作为替代方案,您可以使用类似于:foo(.|\n)*


太神奇了!它刚刚让我省下了数小时的头痛!而且这个方法远非显而易见。 - Mirko Vukušić
Mirko,我很高兴它对你有帮助! - Carlos Ballock

5
如果您不想使用代理,可以使用此模板:
#set($context.responseOverride.status =  $input.path('$.statusCode'))

这值得20个小时的投票。谢谢。 - pete_a_dunham

2
这是在使用API Gateway时AWS Compute Blog推荐的方式。检查直接调用Lambda函数是否与集成正常工作。
var myErrorObj = {
    errorType : "InternalServerError",
    httpStatus : 500,
    requestId : context.awsRequestId,
    message : "An unknown error has occurred. Please try again."
}
callback(JSON.stringify(myErrorObj));

对于直接Lambda调用,这似乎是在客户端进行解析的最佳解决方案。

如果示例是Lambda到Lambda的调用怎么办。被调用的Lambda仍然返回吗?我如何在调用的Lambda上读取该httpStatus? - Rod

1
我正在使用无服务器0.5版本。对于我的情况,它是如何工作的。

s-function.json:

{
  "name": "temp-err-test",
  "description": "Deployed",
  "runtime": "nodejs4.3",
  "handler": "path/to/handler.handler",
  "timeout": 6,
  "memorySize": 1024,
  "endpoints": [
    {
      "path": "test-error-handling",
      "method": "GET",
      "type": "AWS_PROXY",
      "responses": {
        "default": {
          "statusCode": "200"
        }
      }
    }
  ]
}

handler.js:

'use strict';
function serveRequest(event, context, cb) {
  let response = {
    statusCode: '400',
    body: JSON.stringify({ event, context }),
    headers: {
      'Content-Type': 'application/json',
    }
  };
  cb(null, response);
}
module.exports.handler = serveRequest;

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接