HTTP 请求正文未通过 AWS API Gateway 访问 AWS lambda 函数
Posted
技术标签:
【中文标题】HTTP 请求正文未通过 AWS API Gateway 访问 AWS lambda 函数【英文标题】:HTTP request body not getting to AWS lambda function via AWS API Gateway 【发布时间】:2017-04-24 20:59:12 【问题描述】:我有一个用 Scala 编写的非常基本的 lambda 函数部署到 AWS Lambda。当我通过 AWS Lambda 控制台对其进行测试时,该函数运行良好。
这是为调试目的添加了一些额外日志记录的函数。
package com.spacecorpshandbook.ostium.lambda.handler
import java.util
import com.google.gson.Gson
import temp.ApiGatewayProxyResponse, Appointment, CancelResponse
/**
* Amazon Lambda handler adapter for the Cancellation application
*/
class CancellationHandler
def cancelAppointment(appointment: Appointment): ApiGatewayProxyResponse =
System.out.println("++++ appointmentId is: " + appointment.getAppointmentId)
val apiGatewayProxyResponse = new ApiGatewayProxyResponse
val cancelResponse = new CancelResponse
cancelResponse.setMessage("Cancelled appointment with id " + appointment.getAppointmentId)
val gson: Gson = new Gson
apiGatewayProxyResponse.setBody(gson.toJson(cancelResponse))
apiGatewayProxyResponse.setStatusCode("200")
val headerValues = new util.HashMap[String, String]
headerValues put("Content-Type", "application/json")
apiGatewayProxyResponse.setHeaders(headerValues)
System.out.println("+++++ message before returning: " + apiGatewayProxyResponse.getBody)
apiGatewayProxyResponse
我担心作为 Scala bean 的 POJO 输入/输出可能会导致问题,所以我暂时实施 Java 版本只是为了排除这种情况。
AWS API 网关上的集成请求默认设置为启用 Lambda 代理集成的 Resources ANY。请注意,在此配置中,当我从 AWS API Gateway 控制台进行测试时,数据会被转换并进入,但不会一直到达 lambda 函数
Execution log for request test-request
Fri Dec 09 11:14:40 UTC 2016 : Starting execution for request: test-invoke-request
Fri Dec 09 11:14:40 UTC 2016 : HTTP Method: PUT, Resource Path: /cancel-appointment
Fri Dec 09 11:14:40 UTC 2016 : Method request path:
Fri Dec 09 11:14:40 UTC 2016 : Method request query string:
Fri Dec 09 11:14:40 UTC 2016 : Method request headers: Content-Type= application/json
Fri Dec 09 11:14:40 UTC 2016 : Method request body before transformations:
"applicationId": "asdfsfa"
Fri Dec 09 11:14:40 UTC 2016 : Endpoint request headers: x-amzn-lambda-integration-tag=test-request, Authorization=****************************************************************************************************************************************************************************************************************************************************************************************************************************************5c044d, X-Amz-Date=20161209T111440Z, x-amzn-apigateway-api-id=l5tcmj0vlk, Accept=application/json, User-Agent=AmazonAPIGateway_l5tcmj0vlk, Host=lambda.us-east-1.amazonaws.com, X-Amz-Content-Sha256=857a062940a7fbb8134bad1c007e9975a10bd8323c39f6040e797a98e87ea1f6, X-Amzn-Trace-Id=Root=1-584a9220-9cd537954952cca7daee32bf, Content-Type=application/json
Fri Dec 09 11:14:40 UTC 2016 : Endpoint request body after transformations: "resource":"/cancel-appointment","path":"/cancel-appointment","httpMethod":"PUT","headers":"Content-Type":" application/json","queryStringParameters":null,"pathParameters":null,"stageVariables":null,"requestContext":"accountId":"456204981758","resourceId":"xznq3u","stage":"test-invoke-stage","requestId":"test-invoke-request","identity":"cognitoIdentityPoolId":null,"accountId":"456204981758","cognitoIdentityId":null,"caller":"456204981758","apiKey":"test-invoke-api-key","sourceIp":"test-invoke-source-ip","accessKey":"ASIAJ5D7KU524H7CTTTQ","cognitoAuthenticationType":null,"cognitoAuthenticationProvider":null,"userArn":"arn:aws:iam::456204981758:root","userAgent":"Apache-HttpClient/4.5.x (Java/1.8.0_102)","user":"456204981758","resourcePath":"/cancel-appointment","httpMethod":"PUT","apiId":"l5tcmj0vlk","body":"\n \"applicationId\": \"asdfsfa\"\n","isBase64Encoded":false
Fri Dec 09 11:14:40 UTC 2016 : Endpoint response body before transformations: "statusCode":"200","headers":"Content-Type":"application/json","body":"\"message\":\"Cancelled appointment with id null\""
Fri Dec 09 11:14:40 UTC 2016 : Endpoint response headers: x-amzn-Remapped-Content-Length=0, x-amzn-RequestId=adcadf25-be00-11e6-8855-75e96d772946, Connection=keep-alive, Content-Length=128, Date=Fri, 09 Dec 2016 11:14:39 GMT, Content-Type=application/json
Fri Dec 09 11:14:40 UTC 2016 : Method response body after transformations: "message":"Cancelled appointment with id null"
Fri Dec 09 11:14:40 UTC 2016 : Method response headers: Content-Type=application/json, X-Amzn-Trace-Id=Root=1-584a9220-9cd537954952cca7daee32bf
Fri Dec 09 11:14:40 UTC 2016 : Successfully completed execution
Fri Dec 09 11:14:40 UTC 2016 : Method completed with status: 200
如果我添加一个特定的方法,比如 POST 并且 not 将其设置为 Lambda 代理集成,我确实看到提供请求正文数据使其进入 lambda 函数,这是正确的- 序列化到我的 POJO 并返回
Execution log for request test-request
Fri Dec 09 11:22:02 UTC 2016 : Starting execution for request: test-invoke-request
Fri Dec 09 11:22:02 UTC 2016 : HTTP Method: POST, Resource Path: /cancel-appointment
Fri Dec 09 11:22:02 UTC 2016 : Method request path:
Fri Dec 09 11:22:02 UTC 2016 : Method request query string:
Fri Dec 09 11:22:02 UTC 2016 : Method request headers:
Fri Dec 09 11:22:02 UTC 2016 : Method request body before transformations:
"appointmentId" : "sfssdf"
Fri Dec 09 11:22:02 UTC 2016 : Endpoint request headers: x-amzn-lambda-integration-tag=test-request, Authorization=****************************************************************************************************************************************************************************************************************************************************************************************************************************************a8dc41, X-Amz-Date=20161209T112202Z, x-amzn-apigateway-api-id=l5tcmj0vlk, Accept=application/json, User-Agent=AmazonAPIGateway_l5tcmj0vlk, Host=lambda.us-east-1.amazonaws.com, X-Amz-Content-Sha256=875dad4d4e05f8c12a7ca8aeaf69046d4153fc7f910e1eff1959cb011e8313a0, X-Amzn-Trace-Id=Root=1-584a93da-f841704d9feb371b31e41cb9, Content-Type=application/json
Fri Dec 09 11:22:02 UTC 2016 : Endpoint request body after transformations:
"appointmentId" : "sfssdf"
Fri Dec 09 11:22:02 UTC 2016 : Endpoint response body before transformations: "statusCode":"200","headers":"Content-Type":"application/json","body":"\"message\":\"Cancelled appointment with id sfssdf\""
Fri Dec 09 11:22:02 UTC 2016 : Endpoint response headers: x-amzn-Remapped-Content-Length=0, x-amzn-RequestId=b4f5efce-be01-11e6-91c3-5b1e06f831e2, Connection=keep-alive, Content-Length=130, Date=Fri, 09 Dec 2016 11:22:02 GMT, Content-Type=application/json
Fri Dec 09 11:22:02 UTC 2016 : Method response body after transformations: "statusCode":"200","headers":"Content-Type":"application/json","body":"\"message\":\"Cancelled appointment with id sfssdf\""
Fri Dec 09 11:22:02 UTC 2016 : Method response headers: X-Amzn-Trace-Id=Root=1-584a93da-f841704d9feb371b31e41cb9, Content-Type=application/json
Fri Dec 09 11:22:02 UTC 2016 : Successfully completed execution
Fri Dec 09 11:22:02 UTC 2016 : Method completed with status: 200
所以现在一切看起来都很好,但是当我使用 HTTP 方法 POST 对来自 PostMan 的 AWS API URL 进行实际测试时,我得到了带有 null 作为约会 ID 的响应,我可以在 CloudWatch 日志中看到约会 ID 做了未在输入 Appointment 对象上设置。
我觉得我在这里缺少一些基本的东西。任何帮助将不胜感激。
源码可以找到here
更新
通过将 lambda 处理程序函数切换为使用 Stream 而不是尝试将 JSON 序列化/反序列化为 POJO 来解决此问题。使用 API Gateway Lambda 代理时,处理程序的输入是一个复杂的 JSON 结构,我不想尝试将其复制为 Java/Scala 类。将输入作为流处理,将其解析为 JsonObject,然后使用 Gson 或等效库将消息正文转换为我的 POJO 会更容易。下面的示例处理程序,您还可以看到更大的示例here
class CancellationHandler
def cancelAppointment(request: InputStream, response: OutputStream, context: Context): Unit =
val logger = context.getLogger
val parser: JsonParser = new JsonParser
var inputObj: JsonObject = null
val gson: Gson = new Gson
try
inputObj = parser.parse(IOUtils.toString(request, "UTF-8")).getAsJsonObject
catch
case e: IOException =>
logger.log("Error while reading request\n" + e.getMessage)
throw new RuntimeException(e.getMessage)
val body: String = inputObj.get("body").getAsString
val appointment: Appointment = gson.fromJson(body, classOf[Appointment])
val apiGatewayProxyResponse = new ApiGatewayProxyResponse
val cancelResponse = new CancelResponse
cancelResponse.setMessage("Cancelled appointment with id " + appointment.getAppointmentId)
apiGatewayProxyResponse.setBody(gson.toJson(cancelResponse))
apiGatewayProxyResponse.setStatusCode("200")
val headerValues = new util.HashMap[String, String]
headerValues put("Content-Type", "application/json")
apiGatewayProxyResponse.setHeaders(headerValues)
val output: String = gson.toJson(apiGatewayProxyResponse)
IOUtils.write(output, response, "UTF-8")
【问题讨论】:
【参考方案1】:Lambda 代理的输入形状将不同于常规非代理 Lambda 集成的形状。当然,这对您的用例很重要,因为您使用的是 Java/Scala,您必须在其中显式构建输入 POJO。
代理输入如下所示:
"resource": "\/pets",
"path": "\/pets",
"httpMethod": "POST",
"headers": null,
"queryStringParameters": null,
"pathParameters": null,
"stageVariables": null,
"requestContext":
...
"stage": "test-invoke-stage",
"requestId": "test-invoke-request",
"identity":
...
,
"resourcePath": "\/pets",
"httpMethod": "POST"
,
"body": "\n \"foo\":\"bar\"\n", <---- here's what you're looking for
"isBase64Encoded": false
文档:http://docs.aws.amazon.com/apigateway/latest/developerguide/api-gateway-create-api-as-simple-proxy-for-lambda.html
【讨论】:
谢谢。我现在正在玩这个,我想我需要将我的处理程序转换为使用流,以便我可以更轻松地将输入作为 JSON 处理。不过,这无疑将我推向了正确的方向。当我确定一个可行的解决方案时,我会更新这篇文章。以上是关于HTTP 请求正文未通过 AWS API Gateway 访问 AWS lambda 函数的主要内容,如果未能解决你的问题,请参考以下文章
AWS API Gateway 集成请求 Http 标头未传递给 lambda