我有一个用于 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'}
请让我知道我需要更改什么,以确保模型正确验证传入的请求正文。
啊 - 答案是除非请求包含
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"}%