AWS Lambda Невозможно десериализовать экземпляр `java .lang.String` из токена START_OBJECT - PullRequest
0 голосов
/ 09 мая 2020

У меня очень простая Java 11 Lambda:

public class GetArticleHandler implements RequestHandler<APIGatewayV2ProxyRequestEvent, APIGatewayV2ProxyResponseEvent> {

    @Inject
    private GetArticleService getArticleService;

    @Override
    public APIGatewayV2ProxyResponseEvent handleRequest(APIGatewayV2ProxyRequestEvent req, Context context) {

        String path = req.getPath();

        Article article = getArticleService.get(path);

        return generateResponse(req, article);
    }

    private APIGatewayV2ProxyResponseEvent generateResponse(APIGatewayV2ProxyRequestEvent req, Article article) {
        APIGatewayV2ProxyResponseEvent res = new APIGatewayV2ProxyResponseEvent();
        res.setHeaders(Collections.singletonMap("timeStamp", String.valueOf(System.currentTimeMillis())));
        res.setStatusCode(200);
        res.setBody(article.toString());
        return res;
    }

}

Она подключена к AWS APIGateway через развертывание CloudFormation с использованием следующего шаблона (обратите внимание, что это выдержка из этого template):

Resources:
  UTableArticle:
    Type: AWS::DynamoDB::Table
    Properties:
      KeySchema:
        - AttributeName: id
          KeyType: HASH
      AttributeDefinitions:
        - AttributeName: id
          AttributeType: S
      ProvisionedThroughput:
        ReadCapacityUnits: 1
        WriteCapacityUnits: 1
      TableName: !Sub ${AWS::StackName}-Article
    UpdateReplacePolicy: Retain
    DeletionPolicy: Retain
    
  FunctionAssumeRoleRole:
    Type: AWS::IAM::Role
    Properties:
      AssumeRolePolicyDocument:
        Statement:
          - Action: sts:AssumeRole
            Effect: Allow
            Principal:
              Service: lambda.amazonaws.com
        Version: "2012-10-17"
      ManagedPolicyArns:
        - 'arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole'
                
  DynamoActionsPolicy:
    Type: AWS::IAM::Policy
    Properties:
      PolicyDocument:
        Statement:
          - Action:
              - dynamodb:BatchGetItem
              - dynamodb:GetRecords
              - dynamodb:GetShardIterator
              - dynamodb:Query
              - dynamodb:GetItem
              - dynamodb:Scan
              - dynamodb:BatchWriteItem
              - dynamodb:PutItem
              - dynamodb:UpdateItem
              - dynamodb:DeleteItem
            Effect: Allow
            Resource:
              - !GetAtt [ UTableArticle, Arn ]
              - !Ref AWS::NoValue
        Version: "2012-10-17"
      PolicyName: DynamoActionsPolicy
      Roles:
        - !Ref FunctionAssumeRoleRole
  BFunctionGetArticle:
    Type: AWS::Lambda::Function
    Properties:
      Code:
        S3Bucket: !Ref ArtefactRepositoryBucket
        S3Key: !Join [ '', [!Ref ArtefactRepositoryKeyPrefix, '.zip' ] ]
      Handler: !Ref 'GetArticleHandler'
      Role: !GetAtt [ FunctionAssumeRoleRole, Arn ]
      Runtime: java11
      Environment:
        Variables:
          TABLE_NAME: !Ref UTableArticle
          PRIMARY_KEY: id
    DependsOn:
      - DynamoActionsPolicy
      - FunctionAssumeRoleRole
  BFunctionGWPermissionGetIdArticle:
    Type: AWS::Lambda::Permission
    DependsOn:
      - BlogRestApi
      - BFunctionGetArticle
    Properties:
      Action: lambda:InvokeFunction
      FunctionName: !GetAtt [ BFunctionGetArticle, Arn ]
      Principal: apigateway.amazonaws.com
      SourceArn: !Join ['', ['arn:', !Ref 'AWS::Partition', ':execute-api:', !Ref 'AWS::Region', ':', !Ref 'AWS::AccountId', ':', !Ref BlogRestApi, '/*/GET/article/{id}'] ]
  BlogRestApi:
    Type: AWS::ApiGateway::RestApi
    Properties:
      Name: Article
  AGWDeploymentArticle:
    Type: AWS::ApiGateway::Deployment
    Properties:
      RestApiId: !Ref BlogRestApi
      Description: Automatically created by the RestApi construct
    DependsOn:
      - MethodArticleIdGet
      - MethodArticleIdPatch
      - ResourceArticleId
      - MethodArticleGet
      - MethodArticlePost
      - ResourceArticle
  BAGDeploymentStageProdArticle:
    Type: AWS::ApiGateway::Stage
    Properties:
      RestApiId: !Ref BlogRestApi
      DeploymentId: !Ref AGWDeploymentArticle
      StageName: prod
  ResourceArticle:
    Type: AWS::ApiGateway::Resource
    Properties:
      ParentId: !GetAtt [ BlogRestApi, RootResourceId ]
      PathPart: article
      RestApiId: !Ref BlogRestApi
  MethodArticleGet:
    Type: AWS::ApiGateway::Method
    Properties:
      HttpMethod: GET
      ResourceId: !Ref ResourceArticle
      RestApiId: !Ref BlogRestApi
      AuthorizationType: NONE
      Integration:
        IntegrationHttpMethod: POST
        Type: AWS_PROXY
        Uri: !Join [ "", ['arn:', !Ref 'AWS::Partition', ':apigateway:', !Ref 'AWS::Region', ':lambda:path/2015-03-31/functions/', !GetAtt [ BFunctionListArticles, Arn ], '/invocations' ] ]
  ResourceArticleId:
    Type: AWS::ApiGateway::Resource
    Properties:
      ParentId: !Ref ResourceArticle
      PathPart: "{id}"
      RestApiId: !Ref BlogRestApi
  MethodArticleIdGet:
    Type: AWS::ApiGateway::Method
    Properties:
      HttpMethod: GET
      ResourceId: !Ref ResourceArticleId
      RestApiId: !Ref BlogRestApi
      AuthorizationType: NONE
      Integration:
        IntegrationHttpMethod: POST
        Type: AWS_PROXY
        Uri: !Join [ "", ['arn:', !Ref 'AWS::Partition', ':apigateway:', !Ref 'AWS::Region', ':lambda:path/2015-03-31/functions/', !GetAtt [ BFunctionGetArticle, Arn ], '/invocations' ] ]

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

An error occurred during JSON parsing: java.lang.RuntimeException
java.lang.RuntimeException: An error occurred during JSON parsing
Caused by: java.io.UncheckedIOException: com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.exc.MismatchedInputException: Cannot deserialize instance of `java.lang.String` out of START_OBJECT token
 at [Source: (ByteArrayInputStream); line: 1, column: 1]
    at com.amazonaws.services.lambda.runtime.serialization.factories.JacksonFactory$InternalSerializer.fromJson(JacksonFactory.java:182)
Caused by: com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.exc.MismatchedInputException: Cannot deserialize instance of `java.lang.String` out of START_OBJECT token
 at [Source: (ByteArrayInputStream); line: 1, column: 1]
    at com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.exc.MismatchedInputException.from(MismatchedInputException.java:59)
    at com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.DeserializationContext.reportInputMismatch(DeserializationContext.java:1442)
    at com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.DeserializationContext.handleUnexpectedToken(DeserializationContext.java:1216)
    at com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.DeserializationContext.handleUnexpectedToken(DeserializationContext.java:1126)
    at com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.deser.std.StringDeserializer.deserialize(StringDeserializer.java:63)
    at com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.deser.std.StringDeserializer.deserialize(StringDeserializer.java:10)
    at com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.ObjectReader._bindAndClose(ObjectReader.java:1719)
    at com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.ObjectReader.readValue(ObjectReader.java:1228)
    at com.amazonaws.services.lambda.runtime.serialization.factories.JacksonFactory$InternalSerializer.fromJson(JacksonFactory.java:180)

Эта ошибка, кажется, говорит мне, что Джексон пытается для десериализации события API Gateway в виде строки (что, конечно, не так). Учитывая, что я указал лямбда как: GetArticleHandler implements RequestHandler<APIGatewayV2ProxyRequestEvent, APIGatewayV2ProxyResponseEvent>, я ожидал, что Джексон попытается десериализовать событие шлюза API в APIGatewayV2ProxyRequestEvent. Но независимо от того, как я указываю RequestHandler (например, я пробовал указать вместо него Map<String,Object>), он продолжает попытки десериализовать событие, как если бы это была строка. Кто-нибудь может сказать мне, что здесь происходит? Что-то мне не хватает?

1 Ответ

0 голосов
/ 26 мая 2020

Было сложно отследить это, но все сводится к необходимости поставить RequestTemplate на AWS::ApiGateway::Method. Я сделал это в шаблоне CloudFormation:

  MethodArticleIdGet:
    Type: AWS::ApiGateway::Method
    Properties:
      HttpMethod: GET
      ResourceId: !Ref ResourceArticleId
      RestApiId: !Ref BlogRestApi
      AuthorizationType: NONE
      Integration:
        IntegrationHttpMethod: POST
        Type: AWS_PROXY
        RequestTemplates:
          application/json: !Ref ParamRequestMappingTemplate
        Uri: !Join [ "", ['arn:', !Ref 'AWS::Partition', ':apigateway:', !Ref 'AWS::Region', ':lambda:path/2015-03-31/functions/', !GetAtt [ BFunctionGetArticle, Arn ], '/invocations' ] ]

, а затем добавил ParamRequestMappingTemplate:

Parameters:

  <snip>

  ParamRequestMappingTemplate:
    Type: String
    Description: 'Read from resources/templates'

  <snip>

, чтобы я мог ввести параметр через --parameter-overrides в вызове cloudformation deploy со ссылкой на файл .vsl, содержащий:

#set($allParams = $input.params())
{
    #foreach($type in $allParams.keySet())
    #set($params = $allParams.get($type))
    "$type" : {
      #foreach($paramName in $params.keySet())
      "$paramName" : "$util.escapeJavaScript($params.get($paramName))"
      #if($foreach.hasNext),#end
      #end
    }
    #if($foreach.hasNext),#end
    #end
}

, который является модификацией сценария AWS, который передает все заголовки, аргументы пути и параметры запроса как сопоставленные элементы .

Затем я смоделировал параметры запроса в следующем классе:

public class RequestParams {
    String path;

    Map<String, String> header;

    Map<String, String> queryString;
}

, а затем переделал метод Lamabda и обработчика:

public class GetArticleHandler implements RequestHandler<RequestParams, Response<Article>> {

    Injector injector = Guice.createInjector(new GetArticleHandlerModule());

    private GetArticleService getArticleService = injector.getInstance(GetArticleService.class);

    public void setGetArticleService(GetArticleService getArticleService) {
        this.getArticleService = getArticleService;
    }

    @Override
    public Response<Article> handleRequest(RequestParams params, Context context) {
        LOGGER.init(context, "GetArticle", null);

        LOGGER.info(this, params.getPath());

        Article article = getArticleService.get(params.getPath());

        return new Response<>(article);
    }
}

С предоставлением этого , ошибка исчезла.

Хотя следует отметить, что уровень шлюза API также требует, чтобы ответ был смоделирован как:

public class Response<B> {
    @JsonProperty("isBase64Encoded")
    boolean isBase64Encoded;
    int statusCode;
    Map<String, String> headers;
    B body;

    public Response(B body) {
        this.setBase64Encoded(false);
        this.setStatusCode(200);
        this.setHeaders(Map.of("Access-Control-Allow-Origin", "*"));
        this.setBody(body);
    }
}

У меня все еще возникают проблемы с этим, поскольку ответ после того, как Джексон сериализации упорно выходов:

Tue May 26 08:25:04 UTC 2020 : Endpoint response body before transformations: {"statusCode":200,"headers":{"Access-Control-Allow-Origin":"*"},"body":{"tags":[]},"base64Encoded":false}

другими словами, это всегда serialises isBase64Encoded в base64Encoded независимо от того, что я делаю

1035 * и это приводит к ошибке:.
Tue May 26 08:25:04 UTC 2020 : Execution failed due to configuration error: Malformed Lambda proxy response
Tue May 26 08:25:04 UTC 2020 : Method completed with status: 502

о человечество!

...