quarkusio / quarkus

Quarkus: Supersonic Subatomic Java.
https://quarkus.io
Apache License 2.0
13.49k stars 2.6k forks source link

Access AwsProxyRequest object with amazon-lambda-http #6376

Open iain-raidiam opened 4 years ago

iain-raidiam commented 4 years ago

Description We should be able to access the AwsProxyRequest object when usng the amazon-lambda-http extension. This could be to retrieve context information from a lambda authorizer https://docs.aws.amazon.com/apigateway/latest/developerguide/api-gateway-lambda-authorizer-output.html or cognito https://aws.amazon.com/blogs/compute/authorizing-access-through-a-proxy-resource-to-amazon-api-gateway-and-aws-lambda-using-amazon-cognito-user-pools/

Implementation ideas Micronaut provides this by making the request available to the controllers https://github.com/micronaut-projects/micronaut-aws/blob/master/function-aws-api-proxy/src/test/java/io/micronaut/function/aws/proxy/EchoMicronautController.java#L164

keinproblem commented 4 years ago

I am currently looking for a way to access the context information of an Lambda HTTP Integration Request. There are currently a few model implementations available: https://github.com/quarkusio/quarkus/tree/master/extensions/amazon-lambda-http/runtime/src/main/java/io/quarkus/amazon/lambda/http/model

Generally it might be reasonable to separate Context and Request. If you deal with the amazon-lambda-http extension, you do not need to explicitly deal with the AwsProxyRequest object itself. It will be handled by the following implementation and made available in your application implementation. https://github.com/quarkusio/quarkus/blob/master/extensions/amazon-lambda-http/runtime/src/main/java/io/quarkus/amazon/lambda/http/LambdaHttpHandler.java

You would then just implement your JAX-RS resources as usual:

@Path("/")
public class ApiResource {

  @POST
  public Response handlePostRequest(final MyRequestEntity myRequestEntity) {
    //...
    return Response.status(HttpStatus.SC_ACCEPTED).build();
  }
}

Unfortunately I did not figure out how to access context information during the request processing.

There is also no current indication that it is even possible when using the JAX-RS integration.

oztimpower commented 4 years ago

Any reason why you cannot just use the Amazon Lambda extension where you have access to this?

Noted on the problem you've raised. Any interesting one. I couldn't discern a solution from the Micronaut code.

marcoblos commented 4 years ago

Maybe, but it's just a big maybe... @iain-raidiam is trying to use HTTP API instead of Rest API. If I'm right then the mapping is a little bit different.

From this AWS Doc: Working with AWS Lambda proxy integrations for HTTP APIs

Version 1 - AWS::Serverless::Api - current quarkus implementation

{
      version: '1.0',
      resource: '/my/path',
      path: '/my/path',
      httpMethod: 'GET',
      headers: {
        'Header1': 'value1',
        'Header2': 'value2'
      },
      multiValueHeaders: {
        'Header1': [ 'value1' ],
        'Header2': [ 'value1', 'value2' ]
      },
      queryStringParameters: { parameter1: 'value1', parameter2: 'value' },
      multiValueQueryStringParameters: { parameter1: [ 'value1', 'value2' ], paramter2: [ 'value' ] },
      requestContext: {
        accountId: '123456789012',
        apiId: 'id',
        authorizer: { claims: null, scopes: null },
        domainName: 'id.execute-api.us-east-1.amazonaws.com',
        domainPrefix: 'id',
        extendedRequestId: 'request-id',
        httpMethod: 'GET',
        identity: {
          accessKey: null,
          accountId: null,
          caller: null,
          cognitoAuthenticationProvider: null,
          cognitoAuthenticationType: null,
          cognitoIdentityId: null,
          cognitoIdentityPoolId: null,
          principalOrgId: null,
          sourceIp: 'IP',
          user: null,
          userAgent: 'user-agent',
          userArn: null
        },
        path: '/my/path',
        protocol: 'HTTP/1.1',
        requestId: 'id=',
        requestTime: '04/Mar/2020:19:15:17 +0000',
        requestTimeEpoch: 1583349317135,
        resourceId: null,
        resourcePath: '/my/path',
        stage: '$default'
      },
      pathParameters: null,
      stageVariables: null,
      body: 'Hello from Lambda!',
      isBase64Encoded: true
    }

Version 2 - AWS::Serverless::HttpApi

{
      version: '2.0',
      routeKey: '$default',
      rawPath: '/my/path',
      rawQueryString: 'parameter1=value1&parameter1=value2&parameter2=value',
      cookies: [ 'cookie1', 'cookie2' ],
      headers: {
        'Header1': 'value1',
        'Header2': 'value2'
      },
      queryStringParameters: { parameter1: 'value1,value2', parameter2: 'value' },
      requestContext: {
        accountId: '123456789012',
        apiId: 'api-id',
        authorizer: { jwt: {
            claims: {'claim1': 'value1', 'claim2': 'value2'},
            scopes: ['scope1', 'scope2']
            }
        },
        domainName: 'id.execute-api.us-east-1.amazonaws.com',
        domainPrefix: 'id',
        http: {
          method: 'POST',
          path: '/my/path',
          protocol: 'HTTP/1.1',
          sourceIp: 'IP',
          userAgent: 'agent'
        },
        requestId: 'id',
        routeKey: '$default',
        stage: '$default',
        time: '12/Mar/2020:19:03:58 +0000',
        timeEpoch: 1583348638390
      },
      body: 'Hello from Lambda',
      pathParameters: {'parameter1': 'value1'},
      isBase64Encoded: false,
      stageVariables: {'stageVariable1': 'value1', 'stageVariable2': 'value2'}
    }

Hope it helps a little.

sorin-costea commented 4 years ago

Any ideas so far? I'm also using the JAX-RS handlers on AWS Lambda and I'd love to be able to check stuff from the Lambda execution context (request context, actually). I asked this also in Zulip and StackOverflow and nobody answered :(

ghost commented 3 years ago

Same question here, couldn't find any answer

drissamri commented 3 years ago

@patriot1burke since you've been working on the 2.0 payload, maybe you know how it's possible to access the requestContext or other parts of the original API Gateway request? ? (stageVariables, requestContext, routeKey, ...)

requestContext: {
        accountId: '123456789012',
        apiId: 'api-id',
        authorizer: { jwt: {
            claims: {'claim1': 'value1', 'claim2': 'value2'},
            scopes: ['scope1', 'scope2']
            }
        }

Is it the same way in JAXRS and Vert.x (which I am trying out right now)

patriot1burke commented 3 years ago

If you are using master, then just use the JAX-RS @Context inject with what you want, i.e.

com.amazonaws.services.lambda.runtime.Context; APIGatewayV2HTTPEvent APIGatewayV2HTTPEvent.RequestContext

If you are using the currently released amazon-lambda-http extension, you can still inject aws Context, but the gateway requests are a custom quarkus class.

io.quarkus.amazon.lambda.http.model.AwsProxyRequest; io.quarkus.amazon.lambda.http.model.AwsProxyRequestContext;

drissamri commented 3 years ago

This only works for JAX-RS though right? Is there any similar way for Quarkus Vert.x?

        <dependency>
            <groupId>io.quarkus</groupId>
            <artifactId>quarkus-vertx-web</artifactId>
        </dependency>
        <dependency>
            <groupId>io.quarkus</groupId>
            <artifactId>quarkus-amazon-lambda-http</artifactId>
        </dependency>
  @Route(path = "/modes", methods = POST)
    void getModes(RoutingContext context) throws IOException {
patriot1burke commented 3 years ago

works with Vert.x Web, Servlet, JAX-RS, Funqy, anything that is based on Quarkus + Netty.

patriot1burke commented 3 years ago

Oh, you mean, getting the context stuff. You can get it in a vertx handler but its a bit internal API and convoluted. getContextObjects is keyed by the lambda context classses.

MultiMap qheaders = routingContext.request().headers(); if (qheaders instanceof QuarkusHttpHeaders) { for (Map.Entry<Class<?>, Object> entry : ((QuarkusHttpHeaders) qheaders).getContextObjects().entrySet()) { } }

drissamri commented 3 years ago

Ouch, is this worth a ticket or really how it should be exposed?

        MultiMap headers =  context.request().headers();
        if(headers instanceof  QuarkusHttpHeaders) {
            Map<Class<?>, Object> contextObjects1 = ((QuarkusHttpHeaders) headers).getContextObjects();
            contextObjects1.forEach((key, value) ->    {

                if(value instanceof AwsProxyRequestContext) {
                //  Key: "class io.quarkus.amazon.lambda.http.model.AwsProxyRequestContext"
                   System.out.println("Key: " + key);
                    System.out.println("Account: " + ((AwsProxyRequestContext) value).getAccountId());
                }
                if(value instanceof Context) {
                   // Key: "interface com.amazonaws.services.lambda.runtime.Context"
                   System.out.println("Key: " + key);
                    System.out.println("Function Name: " + ((Context) value).getFunctionName());
                }
            });

Another question would be, with a unit test (@Quarkus) how do you trigger this? Since even when posting a full AWS API Gateway event JSON this still doesn't get filled in, only when deployed?

The main reason why I'd choose for Vert.x over JAXRS because the cold starts seems quite a bit faster

jul14n-n commented 3 years ago

Is there a way to inject AwsProxyRequestContext into a JAX-RS Filter?