有没有办法更改 Amazon API 网关返回的 http 状态代码?

Is there a way to change the http status codes returned by Amazon API Gateway?

例如,如果我想 return 一个针对无效参数的特定 400 错误,或者当 lambda 函数调用导致创建时可能是一个 201。

我想要不同的 http 状态代码,但看起来 api 网关总是 return200 状态代码,即使 lambda 函数 return 出错。

这是 return 自定义 HTTP 状态代码和自定义 errorMessage 的最快方法:

在 API 网关仪表板中,执行以下操作:

  1. 在您的资源方法中,点击方法响应
  2. HTTP 状态 table 中,单击 添加响应 并添加您要使用的每个 HTTP 状态代码.
  3. 在您的资源方法中,点击集成响应
  4. 为您之前创建的每个 HTTP 状态代码添加一个集成响应。确保选中 输入直通 。使用 lambda 错误正则表达式 来确定当您 return 来自 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 网关路由应该 return 这个:

    HTTP Status Code: 400
    JSON Error Response: 
        {
            errorMessage: "Bad Request: You submitted invalid input"
        }
    
  6. 我看不出有什么办法可以复制这些设置并将其重新用于不同的方法,所以我们有很多烦人的冗余手动输入要做!

我的集成响应如下所示:

对于那些在这个问题上尝试了所有方法却无法解决这个问题的人(比如我),请查看关于这个 post 的 thedevkit 评论(拯救了我的一天):

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

完全转载如下:

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)*

为了能够 return 自定义错误对象作为 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));
};

接下来,您要为每个想要 return 的状态代码设置正则表达式映射。使用我在上面定义的对象,您将为 400:

设置此正则表达式

.*"status":400.*

最后,您设置了一个映射模板以从 Lambda 编辑的 属性 return 错误消息中提取 JSON 响应。映射模板如下所示:

$input.path('$.errorMessage')

我写了一篇关于此的文章,更详细地解释了从 Lambda 到 API 网关的响应流程: http://kennbrodhagen.net/2016/03/09/how-to-return-a-custom-error-object-and-status-code-from-api-gateway-with-lambda/

每 20-9-2016 更新

Amazon 终于使用 Lambda Proxy integration 让这一切变得简单。这允许您的 Lambda 函数 return 正确的 HTTP 代码和 headers:

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

context.succeed(response);

告别request/response映射在API网关!

选项 2

使用 aws-serverless-express 将现有的 Express 应用与 Lambda/API 网关集成。

我正在使用无服务器 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;

如果使用 API 网关,这就是 AWS 计算博客上推荐的方式。检查集成是否适用于直接 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 的错误是正确的 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 的 GET:

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,它连接到上面的 lambda

使用 Advanced REST 客户端(或 Postman)chrome 插件,您将看到正确的 http 代码,例如服务器错误 (500) 或 400,而不是 [=] 中给出的所有请求的 200 http 响应代码41=].

从API的'Dashboard',在API网关中,我们可以看到如下http状态码:

1) 通过选中标记为 "Use Lambda Proxy integration"[= 的复选框,配置您的 API 网关资源以使用 Lambda Proxy Integration API 网关资源定义的 "Integration Request" 屏幕上的 47=]。 (或者在你的 cloudformation/terraform/serverless/etc 配置中定义它)

2) 以两种方式更改您的 lambda 代码

  • 适当处理传入的event(第一个函数参数)。它不再只是裸负载,它代表了整个 HTTP 请求,包括 headers、查询字符串和 body。下面的示例。关键是 JSON 主体将是需要显式 JSON.parse(event.body) 调用的字符串(不要忘记 try/catch 周围)。示例如下。
  • 通过使用 null 调用回调进行响应,然后响应 object 提供 HTTP 详细信息,包括 statusCodebodyheaders
    • body应该是一个字符串,所以JSON.stringify(payload)根据需要
    • statusCode可以是一个数字
    • headers 是 object 的 header 个名称到值

代理集成的示例 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 上的方法,例如 context.succeed() 已被弃用。尽管它们似乎仍然有效,但它们不再被记录在案。我认为对回调进行编码 API 是正确的做法。

最简单的方法是 use LAMBDA_PROXY integration。使用此方法,您不需要将任何特殊转换设置为 API 网关管道。

您的 return 对象必须类似于以下代码段:

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 网关端点;也就是说,如果您真的不打算在其他任何地方使用它,那也不是什么大问题。

如果您不想使用代理,可以使用此模板:

#set($context.responseOverride.status =  $input.path('$.statusCode'))

2021 年 2 月有效

设置自定义 HTTP 状态代码的最简单方法是在 API 网关中设置 Lambda 代理集成。

在 API 网关 > 资源 > 操作下拉菜单 > 创建方法 > 勾选 Lambda Proxy Integration 和 select 适当的 Lambda 函数。

API 网关

Lambda

对于异步函数,仅 return 具有 statusCodebody 的对象。对于同步功能,请使用 callback(null,obj);参考 full documentation.

export const dummyFunction = async (event, context, callback) => 
{
 // ... logic
   return {
   statusCode: 400,
   body: JSON.stringify({...data}),
   }
};

结果

自定义状态码 400。

我有一个 express 应用程序,并在其前面使用了 api 网关与 http 集成。对于来自我的应用程序的 return 状态代码而不是 200 OK ,我只是将由我的应用程序的错误处理程序编辑的 http 状态代码 return 添加到集成响应部分中的 http 状态正则表达式并且它工作正常.确保您的应用程序正确处理错误。