AWS CDK LambdaRestApi配置form/urlencoded及代理兼容问题
问题背景
使用form/urlencoded格式请求ApiGateway时存在适配异常,当前ApiGateway已从原DotnetLambda架构调整为LambdaRestApi架构,所有常规JSON格式POST请求均可正常运行。
此前可通过AWS Web控制台手动为ApiGateway配置application/x-www-form-urlencoded; charset=utf-8请求处理规则解决该类适配问题,但该手动配置方式无法满足基础设施即代码的自动化部署要求。
配置要求
- 其余所有
ANY类型端点需保持proxy: false整体配置下的原有逻辑,未单独配置integrationParms/LambdaIntegration的路径要保障其他本地API调用正常运行 - 核心目标:让ApiGateway既保留
proxy:true模式下的请求处理效果,又能支持需要proxy: false配置的自定义路径、自定义请求模型(处理form/urlencoded格式请求),解决当前配置抛出异常的问题
当前调试代码
export class DotNetRestApiCustom extends Construct { lambda: DotnetLambda; api: LambdaRestApi; constructor(scope: Construct, id: string, props: DotNetHttpLambdaApiProps) { super(scope, id); this.lambda = new DotnetLambda( this, `${props.name}ApiLambda`, `${props.name}Api`, props.lambdaPath, props.functionHandler, props.functionProps, props.extraHashCodePaths ); let domainName = props.fullDomainName ?? `${Utils.stage.toLowerCase()}.${props.domainTag}.internal.${props.dnsZone.zoneName}`; if (!props.fullDomainName && props.internalDomain === false) domainName = `${Utils.stage.toLowerCase()}.${props.domainTag}.${props.dnsZone.zoneName}`; this.api = new LambdaRestApi(this, `${props.name}Api4`, { proxy: false, handler: this.lambda, domainName: { domainName: domainName, certificate: new Certificate(this, "Certificate", { domainName: domainName, validation: CertificateValidation.fromDns(props.dnsZone), }), endpointType: EndpointType.REGIONAL, securityPolicy: SecurityPolicy.TLS_1_2, }, }); const msgResources = this.api.root.addResource("person"); const msgResource = msgResources.addResource("{personId}"); const methodResponse: MethodResponse = { statusCode: "200", responseModels: {"application/json": Model.EMPTY_MODEL} } const integrationResponse: IntegrationResponse = { statusCode: "200", contentHandling: ContentHandling.CONVERT_TO_TEXT } const requestTemplate = { "body": "$input.json('$')", } const integrationParams = new LambdaIntegration(this.lambda, { allowTestInvoke: true, proxy: false, integrationResponses: [integrationResponse], passthroughBehavior: PassthroughBehavior.WHEN_NO_TEMPLATES, requestTemplates: { "application/json": JSON.stringify(requestTemplate) }, }); this.api.root.addMethod("POST", integrationParams, { methodResponses: [methodResponse], }); new ARecord(this, "ApiDnsRecord", { zone: props.dnsZone, recordName: domainName, target: RecordTarget.fromAlias(new ApiGateway(this.api)), });
解决方案
现有配置报错的核心原因是requestTemplates仅配置了application/json类型的映射规则,form/urlencoded格式请求进入时没有匹配的模板,透传后Lambda无法正确解析请求体。按以下两步修改即可满足需求:
- 第一步:在
requestTemplates中新增application/x-www-form-urlencoded类型的映射模板,将表单参数自动解析为和proxy模式一致的Lambda事件结构,代码如下:const integrationParams = new LambdaIntegration(this.lambda, { allowTestInvoke: true, proxy: false, integrationResponses: [integrationResponse], passthroughBehavior: PassthroughBehavior.WHEN_NO_TEMPLATES, requestTemplates: { "application/json": JSON.stringify(requestTemplate), "application/x-www-form-urlencoded": `{ "body": $util.urlDecode($input.body), "formParams": { #foreach( $token in $input.path('$').split('&') ) #set( $keyVal = $token.split('=') ) #if( $keyVal.size() >=2 ) "$util.urlDecode($keyVal[0])": "$util.urlDecode($keyVal[1])"#if($foreach.hasNext),#end #end #end }, "headers": { #foreach($header in $input.params().header.keySet()) "$header": "$util.escapeJavaScript($input.params().header.get($header))" #if($foreach.hasNext),#end #end }, "queryStringParameters": { #foreach($queryParam in $input.params().querystring.keySet()) "$queryParam": "$util.escapeJavaScript($input.params().querystring.get($queryParam))" #if($foreach.hasNext),#end #end }, "pathParameters": { #foreach($pathParam in $input.params().path.keySet()) "$pathParam": "$util.escapeJavaScript($input.params().path.get($pathParam))" #if($foreach.hasNext),#end #end }, "requestContext": { "httpMethod": "$context.httpMethod", "path": "$context.path", "requestId": "$context.requestId", "stage": "$context.stage" } }` }, }); - 第二步:给所有非自定义处理的路径绑定默认代理集成,完全复现
proxy:true模式的处理效果,不影响原有其他接口的正常运行:// 默认代理集成,和原proxy:true逻辑完全一致 const defaultProxyIntegration = new LambdaIntegration(this.lambda, { proxy: true }); // 给根路径、person路径、personId路径绑定ANY方法的默认集成 this.api.root.addMethod("ANY", defaultProxyIntegration); msgResources.addMethod("ANY", defaultProxyIntegration); msgResource.addMethod("ANY", defaultProxyIntegration); // 后续新增其他资源路径时,同步绑定该默认集成即可
配置注意:表单映射模板里要完整透传头信息、查询参数、路径参数和上下文信息,保证传给后端Lambda的事件结构和proxy模式完全一致,避免业务代码读取参数报错。
内容的提问来源于stack exchange,提问作者MichaelEvanchik
相关产品推荐
相关产品推荐

