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

AWS API Gateway 集成请求正文映射模板

如何在 AWS ELB 日志中获取完整的 POST 正文?

axios请求中未发送正文数据

请求正文未从 aws-serverless-express 模块进入 app.js 文件

从帖子正文读取时,AWS API 网关 CORS 错误