AWS Lambda - Java-компоненты

у меня есть запрос, который выглядит следующим образом:

package pricing

import scala.beans.BeanProperty

class Request(@BeanProperty var name: String, @BeanProperty var surname: String) {
  def this() = this(name="defName", surname="defSurname")
}

обработчик выглядит следующим образом:

package pricing

import com.amazonaws.services.lambda.runtime.{Context, RequestHandler}
import scala.collection.JavaConverters
import spray.json._


class ApiGatewayHandler extends RequestHandler[Request, ApiGatewayResponse] {

  import DefaultJsonProtocol._

  def handleRequest(input: Request, context: Context): ApiGatewayResponse = {
    val headers = Map("x-foo" -> "coucou")
    val msg = "Hello " + input.name
    val message = Map[String, String]("message" -> msg )
    ApiGatewayResponse(
      200,
      message.toJson.toString(),
      JavaConverters.mapAsJavaMap[String, Object](headers),
      true
    )
  }
}

, который был зарегистрирован как:

functions:
  pricing:
    handler: pricing.ApiGatewayHandler
    events:
      - http:
          path: pricing/test
          method: get
          documentation:
            summary: "submit your name and surname, the API says hi"
            description: ".. well, the summary is pretty exhaustive"
            requestBody:
              description: "Send over name and surname"
            queryParams:
              - name: "name"
                description: "your 1st name"
              - name: "surname"
                description: ".. guess .. "
            methodResponses:
              - statusCode: "200"
                responseHeaders:
                  - name: "x-foo"
                    description: "you can foo in here"
                responseBody:
                  description: "You'll see a funny message here"
                responseModels:
                  "application/json": "HelloWorldResponse"

ну, это копия и вставка из одного из учебников. И это не работает. Я думаю, что BeanProperty относится к свойствам объекта тела; и это то, что я могу догадаться из примера here.

если я хотел бы иметь запрос струны?

попытка была:

package pricing

import scala.beans.BeanProperty
import spray.json._

abstract class ApiGatewayGetRequest(
                                     @BeanProperty httpMethod: String,
                                     @BeanProperty headers: Map[String, String],
                                     @BeanProperty queryStringParameters: Map[String, String])


abstract class ApiGatewayPostRequest(
                                     @BeanProperty httpMethod: String,
                                     @BeanProperty headers: Map[String, String],
                                     @BeanProperty queryStringParameters: Map[String, String])

class HelloWorldRequest(
                         @BeanProperty httpMethod: String,
                         @BeanProperty headers: Map[String, String],
                         @BeanProperty queryStringParameters: Map[String, String]
                       ) extends ApiGatewayGetRequest(httpMethod, headers, queryStringParameters) {

  private def getParam(param: String): String =
    queryStringParameters get param match {
      case Some(s) => s
      case None => "default_" + param
    }

  def name: String = getParam("name")
  def surname: String = getParam("surname")

  def this() = this("GET", Map.empty, Map.empty)

}

что приводит к:

 {
  "message":"Hello default_name"
 }

предполагая, что класс был инициализирован пустой картой вместо queryStringParameters который, однако, был представлен правильно

 Mon Sep 25 20:45:22 UTC 2017 : Endpoint request body after
 transformations:
 {"resource":"/pricing/test","path":"/pricing/test","httpMethod":"GET","headers":null,"queryStringParameters":{"name":"ciao", "surname":"bonjour"},"pathParameters":null,"stageVariables":null,
 ...

Примечание.: Я следую этому пути, потому что чувствую, что было бы удобно и выразительно заменить Map на @BeanProperty queryStringParameters: Map[String, String] С типом T, например

case class Person(@beanProperty val name: String, @beanProperty val surname: String)

однако код выше смотрит на {"name":"ciao", "surname":"bonjour"} как String, не выясняя, что он должен десериализовать эту строку.

редактировать

я также попытался заменить карту scala на java.util.Map[String, String] без успеха

1 ответов


по умолчанию Serverless включает интеграция прокси между лямбда и API Gateway. Для вас это означает, что API Gateway передаст объект, содержащий все метаданные о запросе, в ваш обработчик, как вы заметили:

Пн Сен 25 20: 45: 22 UTC 2017 : тело запроса конечной точки после преобразований: {"resource":"/pricing/test","path":"/pricing/test","httpMethod":"GET","headers":null,"queryStringParameters":{"name":"ciao", "фамилия":"bonjour"},"pathParameters":null, "stageVariables": null, ...

это явно не соответствует вашей модели, которая имеет только поля name и surname в нем. Есть несколько способов решить эту проблему.

1. Адаптируйте свою модель

ваша попытка с HelloWorldRequest класс действительно работает, если вы сделайте свой класс правильным POJO, сделав поля изменяемыми (и, таким образом, создав для них сеттеры):

class HelloWorldRequest(
                         @BeanProperty var httpMethod: String,
                         @BeanProperty var headers: java.util.Map[String, String],
                         @BeanProperty var queryStringParameters: java.util.Map[String, String]
                       ) extends ApiGatewayGetRequest(httpMethod, headers, queryStringParameters) {

документация AWS Lambda государства:

методы get и set необходимы для работы POJOs со встроенным в JSON-сериализатор AWS Lambda.

также имейте в виду, что карта Scala не поддерживается.

2. Используйте пользовательский шаблон запроса

Если вам не нужны метаданные, то вместо изменения модели вы можете сделать API Gateway передать только необходимые вам данные в лямбду, используя шаблоны отображения.

для этого вам нужно сказать Serverless использовать простую интеграцию лямбда (вместо прокси) и укажите пользовательский шаблон запроса.

документация Amazon API Gateway имеет пример шаблона запроса что почти идеально подходит для вашей проблемы. Пошив it a немного, мы получаем

functions:
  pricing:
    handler: pricing.ApiGatewayHandler
    events:
      - http:
          path: pricing/test
          method: get
          integration: lambda
          request:
            template:
              application/json: |
                #set($params = $input.params().querystring)
                {
                #foreach($paramName in $params.keySet())
                  "$paramName" : "$util.escapeJavaScript($params.get($paramName))"
                  #if($foreach.hasNext),#end
                #end
                }

этот шаблон сделает JSON из параметров строки запроса, и теперь это будет вход лямбда:

тело запроса конечной точки после преобразований: {"name": "ciao"}

который правильно сопоставляется с вашей моделью.

обратите внимание, что отключение интеграции прокси также изменяет формат ответа. Вы заметите, что теперь ваш API возвращает вашу модель ответа напрямую:

{"statusCode": 200, "body": "{\"message\":\"Hello ciao\"}", "headers": {"x-foo": "coucou"}, "base64Encoded": true}

вы можете исправить это, изменив код, чтобы вернуть только тело, или добавив пользовательский шаблон ответа:

          response:
            template: $input.path('$.body')

это превратит выход в то, что вы ожидаете, но будет явно игнорировать statusCode и headers. Вам нужно будет сделать более сложную конфигурацию ответа для обработки те.

3. Сделайте отображение самостоятельно

вместо расширения RequestHandler и позволить AWS Lambda сопоставить JSON с POJO,вместо этого вы можете расширить RequestStreamHandler, который предоставит вам InputStream и OutputStream, поэтому вы можете сделать сериализацию (de) с сериализатором JSON по вашему выбору.