brefphp / bref

Serverless PHP on AWS Lambda
https://bref.sh
MIT License
3.15k stars 365 forks source link

Support for custom HTTP invocations #1185

Open jasonmccallister opened 2 years ago

jasonmccallister commented 2 years ago

Bref web functions expect to be invoked by either an API Gateway or ALB. It would be nice if Bref was able to handle HTTP events from different types of sources. However, there are many other invocation methods, including direct invocation using the AWS CLI, but invoking the lambda function without an API Gateway or ALB event type results in the following error (which is currently the expected behavior):

{
    "errorType": "Bref\\Event\\InvalidLambdaEvent",
    "errorMessage": "This handler expected to be invoked with a API Gateway or ALB event. Instead, the handler was invoked with invalid event data: null",
    "stackTrace": [
        "#0 \/var\/task\/vendor\/bref\/bref\/src\/Event\/Http\/HttpHandler.php(23): Bref\\Event\\Http\\HttpRequestEvent->__construct()",
        "#1 \/var\/task\/vendor\/bref\/bref\/src\/Runtime\/Invoker.php(29): Bref\\Event\\Http\\HttpHandler->handle()",
        "#2 \/var\/task\/vendor\/bref\/bref\/src\/Runtime\/LambdaRuntime.php(91): Bref\\Runtime\\Invoker->invoke()",
        "#3 \/var\/runtime\/bootstrap(43): Bref\\Runtime\\LambdaRuntime->processNextEvent()",
        "#4 {main}"
    ]
}

One tool that has Lambda integration is Envoy Proxy which requires the Lambda to return an API Gateway V2 type response but the incoming request/event looks like this:

{
    "rawPath": "/path/to/resource",
    "method": "GET|POST|HEAD|...",
    "headers": {"header-key": "header-value", ... },
    "queryStringParameters": {"key": "value", ...},
    "body": "...",
    "isBase64Encoded": true|false
}

I think there are some things to consider if this was to be implemented.

Accepting the event type We could update the HttpHandler to also accept the payload above, but then the response (as the code is right now) is looking for a version 2 in the payload to return the API Gateway V2 response.

Force returning V2 response That could be resolved by setting some kind of variable BREF_RESPONSE_V2 (naming leaves a lot for desire) and when that is set the response will always be return as V2 compatible.

Wanted to start the discussion before making a PR to add this feature.

Example with Envoy

If you wanted to try this with Envoy, you can use this Envoy configuration file (change ACCOUNT, FUNCTION, and REGION to the correct values) and running envoy -c envoy.yaml.

Note: hitting port 10000 in a browser will return an auth error, you must use something like Insomnia and send a Host header to make it work.

static_resources:
  listeners:
    - name: ingress
      address:
        socket_address:
          protocol: TCP
          address: 0.0.0.0
          port_value: 10000
      filter_chains:
        - filters:
            - name: envoy.filters.network.http_connection_manager
              typed_config:
                "@type": type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager
                stat_prefix: http_ingress
                http_filters:
                  - name: envoy.filters.http.aws_lambda
                    typed_config:
                      "@type": type.googleapis.com/envoy.extensions.filters.http.aws_lambda.v3.Config
                      arn: arn:aws:lambda:REGION:ACCOUNT:function:FUNCTION
                      payload_passthrough: false
                  - name: envoy.filters.http.router
                route_config:
                  name: local_route
                  virtual_hosts:
                    - name: local_service
                      domains: ["*"]
                      routes:
                        - match:
                            prefix: "/"
                          route:
                            cluster: lambda_egress_gateway
  clusters:
    - name: lambda_egress_gateway
      connect_timeout: 10s
      type: LOGICAL_DNS
      dns_lookup_family: V4_ONLY
      lb_policy: ROUND_ROBIN
      metadata:
        filter_metadata:
          com.amazonaws.lambda:
            egress_gateway: true
      load_assignment:
        cluster_name: lambda_egress_gateway
        endpoints:
          - lb_endpoints:
              - endpoint:
                  address:
                    socket_address:
                      address: lambda.REGION.amazonaws.com
                      port_value: 443
      transport_socket:
        name: envoy.transport_sockets.tls
        typed_config:
          "@type": type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.UpstreamTlsContext
          sni: "*.amazonaws.com"
mnapoli commented 2 years ago

Just to clarify, the Envoy proxy payload is not following the API Gateway v2 payload?

deleugpn commented 2 years ago

Something that will be on the new runtime i BREF-specific input that can be understood as an HTTP request, such as BREF_METHOD and BREF_BODY

jasonmccallister commented 2 years ago

@mnapoli you can define payload_passthrough as false and Envoy will transform the HTTP request into the following JSON payload:

{
    "rawPath": "/path/to/resource",
    "method": "GET|POST|HEAD|...",
    "headers": {"header-key": "header-value", ... },
    "queryStringParameters": {"key": "value", ...},
    "body": "...",
    "isBase64Encoded": true|false
}

@deleugpn would that mean we could set configure the handler to check for BREF_RESPONSE_V2 to force it to return API Gateway Version 2 responses?

mnapoli commented 2 years ago

@jasonmccallister ok so what is not working? Can you clarify what the problem is here?

and Envoy will transform the HTTP request into the following JSON payload:

Is that compatible with API Gateway payload v2?

jasonmccallister commented 2 years ago

@mnapoli there are two issues.

  1. The Envoy request payload is not v2 compatible and is throwing an exception here.

  2. The Envoy response is v2 compatible and only V2 compatible. However, Bref checks if the event is for format v2 and there is no way to override the response.

As far as #1, we could add another if statement that checks for $event['method'] that is sent by Envoy, but that won't resolve #2 where it has to return a v2 response.

We could resolve #2 by updating the code here to something like this:

public function isFormatV2(): bool
{
    return $this->payloadVersion === 2.0 || array_key_exists('method', $this->event);
}

Diving through the existing code and talking it out here, it seems like it might not be a large lift to add support for this type of payload. Want me to make a PR with those proposed changes? I'm sure there are some other methods that will probably need updates.