繁体   English   中英

如何从与 C# lambda 集成(不是代理集成)的 Amazon API 网关获取正确的 http 状态代码?

[英]How to get proper http status codes from Amazon API gateway integrated with C# lambda (not proxy integration)?

我正在使用 C# lambda 与 API 网关集成。 我希望 API 网关返回正确的错误代码,如 400、404、500 等。

API 网关模块 tf 文件

provider "aws" {
  version = "<= 2.70.0"
  region = "${var.aws_region}"
  profile = "${var.aws_profile}"
}

terraform {
  # The configuration for this backend will be filled in by Terragrunt
  backend "s3" {}
}

data "terraform_remote_state" "api_state" {
  backend = "s3"

  config {
    region = "${var.aws_region}"
    profile = "${var.aws_profile}"
    bucket = "${var.s3_remote_state_bucket_name}"
    key = "${var.s3_remote_state_key_name_api}"
  }
}

data "terraform_remote_state" "resource_state"{
  backend = "s3"
  config {
    region = "${var.aws_region}"
    profile = "${var.aws_profile}"
    bucket = "${var.s3_remote_state_bucket_name}"
    key = "${var.s3_remote_state_key_name_resource}"
  }
}

data "terraform_remote_state" "lambda_alias"{
  backend = "s3"

  config {
    region = "${var.aws_region}"
    profile = "${var.aws_profile}"
    bucket = "${var.s3_remote_state_bucket_name}"
    key = "${var.s3_remote_state_key_name_lambda}"
  }
}

resource "aws_api_gateway_method" "http-method" {
  rest_api_id = "${data.terraform_remote_state.api_state.api_gateway_rest_api_id}"
  resource_id = "${data.terraform_remote_state.resource_state.api_resource_id}"
  http_method = "GET"
  authorization = "CUSTOM"
  authorizer_id = "${data.terraform_remote_state.api_state.Authorizers[var.Authorizer]}"
  request_parameters = "${var.api_request_params_required}"
}

resource "aws_api_gateway_integration" "integration_GET" {
  rest_api_id             = "${data.terraform_remote_state.api_state.api_gateway_rest_api_id}"
  resource_id             = "${data.terraform_remote_state.resource_state.api_resource_id}"
  http_method             = "${aws_api_gateway_method.http-method.http_method}"
  integration_http_method = "POST"
  type                    = "AWS"
  uri                     = "arn:aws:apigateway:${var.aws_region}:lambda:path/2015-03-31/functions/${data.terraform_remote_state.lambda_alias.alias_lambda_arn}/invocations"
  passthrough_behavior = "WHEN_NO_TEMPLATES"
  request_templates = {
    "application/json" = "${file("api_gateway_body_mapping.template")}"
  }
}

resource "aws_api_gateway_model" "error_response" {
  rest_api_id  = "${aws_api_gateway_rest_api.api_gateway_rest_api.id}"
  name         = "ErrorResponse"
  description  = "The error respone object for all endpoints"
  content_type = "application/json"

  schema = <<EOF
  {
    "$schema": "http://json-schema.org/draft-04/schema#",
    "type" : "object",
    "properties": {
          "body": {
              "type": "string"
          },
          "statusCode" : {
              "type": "number"
          }
      }
  }
EOF
}

resource "aws_api_gateway_method_response" "method_response" {
  depends_on = ["aws_api_gateway_method.http-method"]
  http_method = "${aws_api_gateway_method.http-method.http_method}"
  resource_id = "${data.terraform_remote_state.resource_state.api_resource_id}"
  rest_api_id = "${data.terraform_remote_state.api_state.api_gateway_rest_api_id}"
  status_code = "200"
}

resource "aws_api_gateway_method_response" "method_bad_request" {
  depends_on = ["aws_api_gateway_method.http-method"]
  http_method = "${aws_api_gateway_method.http-method.http_method}"
  resource_id = "${data.terraform_remote_state.resource_state.api_resource_id}"
  rest_api_id = "${data.terraform_remote_state.api_state.api_gateway_rest_api_id}"
  status_code = "400"
  response_models {
    "application/json" = "${aws_api_gateway_model.error_response}"
  }
}

resource "aws_api_gateway_method_response" "method_not_found" {
  depends_on = ["aws_api_gateway_method.http-method"]
  http_method = "${aws_api_gateway_method.http-method.http_method}"
  resource_id = "${data.terraform_remote_state.resource_state.api_resource_id}"
  rest_api_id = "${data.terraform_remote_state.api_state.api_gateway_rest_api_id}"
  status_code = "404"
  response_models {
    "application/json" = "${aws_api_gateway_model.error_response}"
  }
}

resource "aws_api_gateway_method_response" "method_error" {
  depends_on = ["aws_api_gateway_method.http-method"]
  http_method = "${aws_api_gateway_method.http-method.http_method}"
  resource_id = "${data.terraform_remote_state.resource_state.api_resource_id}"
  rest_api_id = "${data.terraform_remote_state.api_state.api_gateway_rest_api_id}"
  status_code = "500"
  response_models {
    "application/json" = "${aws_api_gateway_model.error_response}"
  }
}

resource "aws_api_gateway_integration_response" "get_integration_response_success" {
  depends_on = ["aws_api_gateway_method_response.method_response", "aws_api_gateway_integration.integration_GET"]
  http_method = "${aws_api_gateway_method.http-method.http_method}"
  resource_id = "${data.terraform_remote_state.resource_state.api_resource_id}"
  rest_api_id = "${data.terraform_remote_state.api_state.api_gateway_rest_api_id}"
  status_code = "${aws_api_gateway_method_response.method_response.status_code}"
  response_templates {
    "application/json" = ""
  }
}

resource "aws_api_gateway_integration_response" "get_integration_response_error" {
  depends_on = ["aws_api_gateway_method_response.method_error", "aws_api_gateway_integration.integration_GET"]
  http_method = "${aws_api_gateway_method.http-method.http_method}"
  resource_id = "${data.terraform_remote_state.resource_state.api_resource_id}"
  rest_api_id = "${data.terraform_remote_state.api_state.api_gateway_rest_api_id}"
  status_code = "${aws_api_gateway_method_response.method_error.status_code}"
  selection_pattern = ".*statusCode['\"]\\s*:\\s*['\"]?500.*"
  response_templates {
    "application/json"="${file("api_gateway_exception_mapping.template")}"
  }
}

resource "aws_api_gateway_integration_response" "get_integration_response_bad_request" {
  depends_on = ["aws_api_gateway_method_response.method_bad_request", "aws_api_gateway_integration.integration_GET"]
  http_method = "${aws_api_gateway_method.http-method.http_method}"
  resource_id = "${data.terraform_remote_state.resource_state.api_resource_id}"
  rest_api_id = "${data.terraform_remote_state.api_state.api_gateway_rest_api_id}"
  status_code = "${aws_api_gateway_method_response.method_bad_request.status_code}"
  selection_pattern = ".*statusCode['\"]\\s*:\\s*['\"]?400.*"
  response_templates {
    "application/json"="${file("api_gateway_exception_mapping.template")}"
  }
}

resource "aws_api_gateway_integration_response" "get_integration_response_not_found" {
  depends_on = ["aws_api_gateway_method_response.method_not_found", "aws_api_gateway_integration.integration_GET"]
  http_method = "${aws_api_gateway_method.http-method.http_method}"
  resource_id = "${data.terraform_remote_state.resource_state.api_resource_id}"
  rest_api_id = "${data.terraform_remote_state.api_state.api_gateway_rest_api_id}"
  status_code = "${aws_api_gateway_method_response.method_not_found.status_code}"
  selection_pattern = ".*statusCode['\"]\\s*:\\s*['\"]?404.*"
  response_templates {
    "application/json"="{}"
  }
}

api_gateway_exception_mapping.template:

#set($inputRoot = $util.parseJson($input.path('$.errorMessage')))
{
"Error":"$inputRoot.body"
}

集成响应映射如下图所示

API 的集成响应

我们已经在 python 中创建了带有 lambda 集成的 API,我在其中抛出了一个自定义 APIException,如下所示,它起作用了。

class ApiException(Exception):
    """Our custom APIException class which derives from the built-in Exception class"""

    def __init__(self, status_code, message: str, **kwargs):
        self.status_code = status_code
        self.message = message
        kwargs["statusCode"] = status_code
        kwargs["body"] = message
        super().__init__(json.dumps(kwargs))

在 lambda 处理程序内部:

from .utils import ApiException
def lambda_handler(event, context):
    try:
        """
        CODE FOR LAMBDA HANDLER
        """
    except Exception:
        ex = ApiException(status_code=HTTPStatus.INTERNAL_SERVER_ERROR,
                           message='Internal Error')
        print("exception string: %s", ex)
        raise ApiException(
            status_code=500,
            message='Internal server error')

当我记录异常时,我得到以下输出

{
    "statusCode": 500,
    "body": "Internal Server Error"
}

我还参考了这个关于如何在 API Gateway 响应中获取正确错误代码的 stackoverflow 答案 我稍微修改了一下以引发异常,而不是只返回带有statusCode 、响应bodyheaders的 json,因为我没有使用 AWS_PROXY 集成类型

APIException.cs

public class APIException : Exception
{
    public int statusCode;
    public string body;
    public APIException() : base() {}
    public APIException(int statusCode, string message): base(message) {
        this.statusCode = statusCode;
        JObject json = JObject.Parse(message);
        this.body = json["body"].ToString();
    }
}

Lambda 处理程序:

namespace LambdaFunction
{
    public class Function
    {
        public async Task<JObject> FunctionHandler(JObject events, ILambdaContext context)
        {
           try
            {
                ValidateQueryParams(events, context);
                JObject response = JObject.Parse(@"{
                    'mesage': 'success',
                }");

                return response;
            }
            catch(HttpListenerException ex)
            {
                string err = (new JObject(
                    new JProperty("statusCode", ex.ErrorCode),
                    new JProperty("body", ex.Message)
                )).ToString();
                return new APIException(ex.ErrorCode, err);
            }
            catch(Exception ex)
            {
                int err_code = (int)HttpStatusCode.InternalServerError
                string err = (new JObject(
                    new JProperty("statusCode", err_code),
                    new JProperty("body", "Internal Server Error")
                )).ToString();
                var err_ex = new APIException(err_code, err);
                context.Logger.LogLine("Unhandled exception occurred: " + ex.ToString());
                return err_ex;
            }
        }
    }
}

在抛出异常之前我已经记录了异常,看看我们得到了什么,这就是我得到的

{
    "statusCode": 500,
    "body": "Internal Server Error",
    "StackTrace": null,
    "Message": "{\n  \"statusCode\": 500,\n  \"body\": \"Internal Server Error\"\n}",
    "Data": {},
    "InnerException": null,
    "HelpLink": null,
    "Source": null,
    "HResult": -2146233088
}

但是使用上面的代码,我仍然只是得到响应代码为 200 和下面的响应正文

{
    "errorType": "APIException",
    "errorMessage": "{\n  \"statusCode\": 500,\n  \"body\": \"Internal Server Error\"\n}",
    "stackTrace": [
        "..."
    ]
}

我不确定我哪里出错了。 任何帮助将不胜感激。 谢谢。

我已经弄清楚了这个问题。 C# 的.ToString()函数将JObject转换为字符串,默认情况下会格式化字符串并添加换行符\n 但是 API 网关中用于识别错误代码的正则表达式不考虑换行符。 修复非常简单。 我们需要告诉.ToString()函数它不应该进行任何格式化。 所以,而不是

new JObject(
    new JProperty("statusCode", err_code),
    new JProperty("body", "Internal Server Error")
)).ToString();

你需要做

new JObject(
    new JProperty("statusCode", err_code),
    new JProperty("body", "Internal Server Error")
)).ToString(Formatting.None);

它奏效了。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM