Repository: localstack/localstack

Issue:
Enhance request forwarding for non-existent Lambdas
As an enhancement to https://github.com/localstack/localstack/pull/1194

It would be really helpful to send the Lambda name, either as data, a header, or as part of the URL.

At code location: https://github.com/localstack/localstack/blob/03c6f4bf250edd18eb818ed65090f508636b0bff/localstack/services/awslambda/lambda_api.py#L696

I can likely open a PR to handle this given some feedback on the feature and potential design.

Right now all I see is the following (using `npx http-echo-server`):
```
[socket#8] event: data
--> POST / HTTP/1.1
--> Host: 192.168.1.x:3000
--> User-Agent: python-requests/2.22.0
--> Accept-Encoding: gzip, deflate
--> Accept: */*
--> Connection: keep-alive
--> Content-Length: 9
--> Content-Type: application/x-www-form-urlencoded
-->
-->
[socket#8] event: data
--> Name=test
```


Discussion:
Great idea @sjmatta - would you be able to create a small pull request with this functionality? Shouldn't be too hard to add - I'd be happy to provide guidance if needed. Thanks!

Resolving patch:
diff --git a/localstack/services/awslambda/lambda_api.py b/localstack/services/awslambda/lambda_api.py
--- a/localstack/services/awslambda/lambda_api.py
+++ b/localstack/services/awslambda/lambda_api.py
@@ -793,19 +793,23 @@ def forward_to_fallback_url(func_arn, data):
         Lambda to the configured URL. """
     if not config.LAMBDA_FALLBACK_URL:
         return None
+
+    lambda_name = aws_stack.lambda_function_name(func_arn)
     if config.LAMBDA_FALLBACK_URL.startswith('dynamodb://'):
         table_name = urlparse(config.LAMBDA_FALLBACK_URL.replace('dynamodb://', 'http://')).netloc
         dynamodb = aws_stack.connect_to_service('dynamodb')
         item = {
             'id': {'S': short_uid()},
             'timestamp': {'N': str(now_utc())},
-            'payload': {'S': str(data)}
+            'payload': {'S': str(data)},
+            'function_name': {'S': lambda_name}
         }
         aws_stack.create_dynamodb_table(table_name, partition_key='id')
         dynamodb.put_item(TableName=table_name, Item=item)
         return ''
     if re.match(r'^https?://.+', config.LAMBDA_FALLBACK_URL):
-        response = safe_requests.post(config.LAMBDA_FALLBACK_URL, data)
+        headers = {'lambda-function-name': lambda_name}
+        response = safe_requests.post(config.LAMBDA_FALLBACK_URL, data, headers=headers)
         return response.content
     raise ClientError('Unexpected value for LAMBDA_FALLBACK_URL: %s' % config.LAMBDA_FALLBACK_URL)
 
@@ -1144,14 +1148,14 @@ def invoke_function(function):
     # Default invocation type is RequestResponse
     invocation_type = request.environ.get('HTTP_X_AMZ_INVOCATION_TYPE', 'RequestResponse')
 
-    def _create_response(result, status_code=200):
+    def _create_response(result, status_code=200, headers={}):
         """ Create the final response for the given invocation result """
         if isinstance(result, Response):
             return result
         details = {
             'StatusCode': status_code,
             'Payload': result,
-            'Headers': {}
+            'Headers': headers
         }
         if isinstance(result, dict):
             for key in ('StatusCode', 'Payload', 'FunctionError'):
@@ -1187,7 +1191,7 @@ def _create_response(result, status_code=200):
         not_found = not_found_error('{0}:{1}'.format(arn, qualifier))
 
     if not_found:
-        forward_result = forward_to_fallback_url(func_arn, data)
+        forward_result = forward_to_fallback_url(arn, data)
         if forward_result is not None:
             return _create_response(forward_result)
         return not_found


Test patch:
diff --git a/tests/integration/test_lambda.py b/tests/integration/test_lambda.py
--- a/tests/integration/test_lambda.py
+++ b/tests/integration/test_lambda.py
@@ -120,7 +120,7 @@ def num_items():
     def test_forward_to_fallback_url_http(self):
         class MyUpdateListener(ProxyListener):
             def forward_request(self, method, path, data, headers):
-                records.append(data)
+                records.append({'data': data, 'headers': headers})
                 return 200
 
         records = []
@@ -130,9 +130,26 @@ def forward_request(self, method, path, data, headers):
         items_before = len(records)
         _run_forward_to_fallback_url('%s://localhost:%s' % (get_service_protocol(), local_port))
         items_after = len(records)
+        for record in records:
+            self.assertIn('non-existing-lambda', record['headers']['lambda-function-name'])
+
         self.assertEqual(items_after, items_before + 3)
         proxy.stop()
 
+    def test_adding_fallback_function_name_in_headers(self):
+
+        lambda_client = aws_stack.connect_to_service('lambda')
+        ddb_client = aws_stack.connect_to_service('dynamodb')
+
+        db_table = 'lambda-records'
+        config.LAMBDA_FALLBACK_URL = 'dynamodb://%s' % db_table
+
+        lambda_client.invoke(FunctionName='non-existing-lambda',
+                             Payload=b'{}', InvocationType='RequestResponse')
+
+        result = run_safe(ddb_client.scan, TableName=db_table)
+        self.assertEqual(result['Items'][0]['function_name']['S'], 'non-existing-lambda')
+
     def test_dead_letter_queue(self):
         sqs_client = aws_stack.connect_to_service('sqs')
         lambda_client = aws_stack.connect_to_service('lambda')