APIGateway HTTP POST 请求主体验证模型显然没有被调用

问题描述 投票:0回答:1

我有一个用于 apigw HTTP POST 端点的 Cloudformation 模板,它使用模型验证 POST 请求正文 -

{
  "Outputs": {
    "AppRestApi": {
      "Value": {
        "Ref": "AppRestApi"
      }
    }
  },
  "Parameters": {
    "CertificateArn": {
      "Type": "String"
    },
    "DomainName": {
      "Type": "String"
    }
  },
  "Resources": {
    "AppBasePathMapping": {
      "DependsOn": [
        "AppDomainName"
      ],
      "Properties": {
        "DomainName": {
          "Ref": "DomainName"
        },
        "RestApiId": {
          "Ref": "AppRestApi"
        },
        "Stage": "prod"
      },
      "Type": "AWS::ApiGateway::BasePathMapping"
    },
    "AppDeployment": {
      "DependsOn": [
        "AppHelloPostPublicLambdaProxyMethod"
      ],
      "Properties": {
        "RestApiId": {
          "Ref": "AppRestApi"
        }
      },
      "Type": "AWS::ApiGateway::Deployment"
    },
    "AppDomainName": {
      "Properties": {
        "CertificateArn": {
          "Ref": "CertificateArn"
        },
        "DomainName": {
          "Ref": "DomainName"
        }
      },
      "Type": "AWS::ApiGateway::DomainName"
    },
    "AppHelloPostFunction": {
      "Properties": {
        "Code": {
          "ZipFile": "import json\ndef handler(event, context):\n    body=json.loads(event[\"body\"])\n    message=body[\"message\"]\n    return {\"statusCode\": 200,\n            \"headers\": {\"Content-Type\": \"text/plain\",\n                        \"Access-Control-Allow-Origin\": \"*\",\n                        \"Access-Control-Allow-Headers\": \"Content-Type,X-Amz-Date,Authorization,X-Api-Key,X-Amz-Security-Token,X-Amz-User-Agent\",\n                        \"Access-Control-Allow-Methods\": \"OPTIONS,POST\"},\n            \"body\": f\"you sent '{message}' via POST\"}"
        },
        "Handler": "index.handler",
        "MemorySize": 512,
        "Role": {
          "Fn::GetAtt": [
            "AppHelloPostRole",
            "Arn"
          ]
        },
        "Runtime": "python3.10",
        "Timeout": 5
      },
      "Type": "AWS::Lambda::Function"
    },
    "AppHelloPostModel": {
      "Properties": {
        "ContentType": "application/json",
        "Name": "AppHelloPostModel",
        "RestApiId": {
          "Ref": "AppRestApi"
        },
        "Schema": {
          "$schema": "http://json-schema.org/draft-04/schema#",
          "additionalProperties": false,
          "properties": {
            "message": {
              "type": "string"
            }
          },
          "required": [
            "message"
          ],
          "type": "object"
        }
      },
      "Type": "AWS::ApiGateway::Model"
    },
    "AppHelloPostPermission": {
      "Properties": {
        "Action": "lambda:InvokeFunction",
        "FunctionName": {
          "Ref": "AppHelloPostFunction"
        },
        "Principal": "apigateway.amazonaws.com",
        "SourceArn": {
          "Fn::Sub": "arn:aws:execute-api:${AWS::Region}:${AWS::AccountId}:${AppRestApi}/${AppStage}/POST/hello-post"
        }
      },
      "Type": "AWS::Lambda::Permission"
    },
    "AppHelloPostPublicLambdaProxyMethod": {
      "Properties": {
        "AuthorizationType": "NONE",
        "HttpMethod": "POST",
        "Integration": {
          "IntegrationHttpMethod": "POST",
          "Type": "AWS_PROXY",
          "Uri": {
            "Fn::Sub": [
              "arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${arn}/invocations",
              {
                "arn": {
                  "Fn::GetAtt": [
                    "AppHelloPostFunction",
                    "Arn"
                  ]
                }
              }
            ]
          }
        },
        "RequestModels": {
          "application/json": "AppHelloPostModel"
        },
        "RequestValidatorId": {
          "Ref": "AppHelloPostSchemaRequestValidator"
        },
        "ResourceId": {
          "Ref": "AppHelloPostResource"
        },
        "RestApiId": {
          "Ref": "AppRestApi"
        }
      },
      "Type": "AWS::ApiGateway::Method"
    },
    "AppHelloPostResource": {
      "Properties": {
        "ParentId": {
          "Fn::GetAtt": [
            "AppRestApi",
            "RootResourceId"
          ]
        },
        "PathPart": "hello-post",
        "RestApiId": {
          "Ref": "AppRestApi"
        }
      },
      "Type": "AWS::ApiGateway::Resource"
    },
    "AppHelloPostRole": {
      "Properties": {
        "AssumeRolePolicyDocument": {
          "Statement": [
            {
              "Action": [
                "sts:AssumeRole"
              ],
              "Effect": "Allow",
              "Principal": {
                "Service": "lambda.amazonaws.com"
              }
            }
          ],
          "Version": "2012-10-17"
        },
        "Policies": [
          {
            "PolicyDocument": {
              "Statement": [
                {
                  "Action": [
                    "logs:CreateLogGroup",
                    "logs:CreateLogStream",
                    "logs:PutLogEvents"
                  ],
                  "Effect": "Allow",
                  "Resource": "*"
                }
              ],
              "Version": "2012-10-17"
            },
            "PolicyName": {
              "Fn::Sub": "app-hello-post-role-policy-${AWS::StackName}"
            }
          }
        ]
      },
      "Type": "AWS::IAM::Role"
    },
    "AppHelloPostSchemaRequestValidator": {
      "Properties": {
        "RestApiId": {
          "Ref": "AppRestApi"
        },
        "ValidateRequestBody": true,
        "ValidateRequestParameters": false
      },
      "Type": "AWS::ApiGateway::RequestValidator"
    },
    "AppRecordSet": {
      "Properties": {
        "AliasTarget": {
          "DNSName": {
            "Fn::GetAtt": [
              "AppDomainName",
              "DistributionDomainName"
            ]
          },
          "EvaluateTargetHealth": false,
          "HostedZoneId": {
            "Fn::GetAtt": [
              "AppDomainName",
              "DistributionHostedZoneId"
            ]
          }
        },
        "HostedZoneName": {
          "Fn::Sub": [
            "${prefix}.${suffix}.",
            {
              "prefix": {
                "Fn::Select": [
                  1,
                  {
                    "Fn::Split": [
                      ".",
                      {
                        "Ref": "DomainName"
                      }
                    ]
                  }
                ]
              },
              "suffix": {
                "Fn::Select": [
                  2,
                  {
                    "Fn::Split": [
                      ".",
                      {
                        "Ref": "DomainName"
                      }
                    ]
                  }
                ]
              }
            }
          ]
        },
        "Name": {
          "Ref": "DomainName"
        },
        "Type": "A"
      },
      "Type": "AWS::Route53::RecordSet"
    },
    "AppRestApi": {
      "Properties": {
        "Name": {
          "Fn::Sub": "app-rest-api-${AWS::StackName}"
        }
      },
      "Type": "AWS::ApiGateway::RestApi"
    },
    "AppStage": {
      "Properties": {
        "DeploymentId": {
          "Ref": "AppDeployment"
        },
        "RestApiId": {
          "Ref": "AppRestApi"
        },
        "StageName": "prod"
      },
      "Type": "AWS::ApiGateway::Stage"
    }
  }
}

这在快乐路径的情况下效果很好 -

(env) jhw@Justins-Air  % curl -i -X POST https://xxx.yyy.zzz/hello-post -d "{\"message\": \"Hello World\!\"}"
HTTP/2 200 
content-type: text/plain
content-length: 32
date: Fri, 15 Mar 2024 06:14:57 GMT
x-amzn-requestid: 7f7ad199-6bb7-4ea9-bcab-9046d5c8651e
access-control-allow-origin: *
access-control-allow-headers: Content-Type,X-Amz-Date,Authorization,X-Api-Key,X-Amz-Security-Token,X-Amz-User-Agent
x-amz-apigw-id: UqEXOEnZjoEEYmA=
access-control-allow-methods: OPTIONS,POST
x-amzn-trace-id: Root=1-65f3e761-2bf936e212c5205716168100;Parent=3218d4aa6fbde101;Sampled=0;lineage=3282c475:0
x-cache: Miss from cloudfront
via: 1.1 22e421a47e59010b5e8eb6ae4d4bd7e4.cloudfront.net (CloudFront)
x-amz-cf-pop: LHR61-P2
x-amz-cf-id: -fpNsR5L4xGvFf-GwO-aDw4D8TkfA1cc0B6fnoLLFDZC0gP_2vcFLQ==

you sent 'Hello World!' via POST%

但是当我使用无效的请求正文发送它时,它返回 HTTP 5XX 而不是 HTTP 4XX -

(env) jhw@Justins-Air  % curl -i -X POST https://xxx.yyy.zzz/hello-post -d "{\"_message\": \"Hello World\!\"}"
HTTP/2 502 
content-type: application/json
content-length: 36
date: Fri, 15 Mar 2024 06:15:02 GMT
x-amzn-requestid: 9113503e-79c7-4948-bb3d-9cb3698d2ff9
x-amzn-errortype: InternalServerErrorException
x-amz-apigw-id: UqEYCFd2joEED1Q=
x-cache: Error from cloudfront
via: 1.1 8ba281782b2b20f7db8f5372bc06a3a2.cloudfront.net (CloudFront)
x-amz-cf-pop: LHR61-P2
x-amz-cf-id: UpUa11Il2x7lEdu7l7SboDyuB9TekNAsnY_fo8INHX7LDlgal81nDw==

{"message": "Internal server error"}%

此错误来自 Lambda 函数,我可以在 Lambda 日志中看到该错误。这表明 apigw 模型没有被调用,或者如果它被调用,它没有配置为返回 4XX 响应。

为什么会这样以及需要更改什么,以便如果使用无效的请求正文调用端点,apigw 将因模型验证失败而返回 4XX,并且请求将永远不会命中 Lambda 函数?

我已经测试过架构格式正确 -

import json, jsonschema

if __name__ == "__main__":
    template=json.loads(open("template.json").read())
    schema=template["Resources"]["AppHelloPostModel"]["Properties"]["Schema"]
    for instance in [{"message": "hello"},
                     {"_message": "hello"}]:
        print(f"--- {instance} ---")
        try:        
            jsonschema.validate(instance=instance,
                                schema=schema)
            print("OK")
        except jsonschema.exceptions.ValidationError as error:
            print(str(error))

env) jhw@Justins-Air  % python test_model_validation.py 
--- {'message': 'hello'} ---
OK
--- {'_message': 'hello'} ---
Additional properties are not allowed ('_message' was unexpected)

Failed validating 'additionalProperties' in schema:
    {'$schema': 'http://json-schema.org/draft-04/schema#',
     'additionalProperties': False,
     'properties': {'message': {'type': 'string'}},
     'required': ['message'],
     'type': 'object'}

On instance:
    {'_message': 'hello'}

请让我知道我需要更改什么,以确保模型正确验证传入的请求正文。

aws-api-gateway
1个回答
0
投票

啊 - 答案是除非请求包含

Content-Type
标头,否则模型不会被调用:/

env) jhw@Justins-Air 3aa9d4f85d9709fc7cb44e886ba0808f % curl -i -H "Content-Type: application/json" -X POST https://xxx.yyy.zzz/hello-post -d "{\"message_\": \"Hello World\!\"}"
HTTP/2 400 
content-type: application/json
content-length: 35
date: Fri, 15 Mar 2024 15:11:16 GMT
x-amzn-requestid: 8418b102-b1f5-432c-9e42-308947c28bce
access-control-allow-origin: *
access-control-allow-headers: *
x-amzn-errortype: BadRequestException
x-amz-apigw-id: UrS7THMvDoEEhzg=
x-cache: Error from cloudfront
via: 1.1 47c1b2a882ab8226b0b44cb0c042b982.cloudfront.net (CloudFront)
x-amz-cf-pop: LHR50-P8
x-amz-cf-id: i9TQrVxbc7fRLoC93F3o5Y_HRupAAux7f7B_TjySyddpBvpw_n3TNA==

{"message": "Invalid request body"}%          
© www.soinside.com 2019 - 2024. All rights reserved.