Repository: localstack/localstack

Issue:
APIGateway AWS_PROXY integration failing for paths without parameters
I'm trying to run an AWS_PROXY integration between APIGateway and Lambda, and I'm hitting what looks like a fundamental logic bug in the APIGateway implementation.

https://github.com/localstack/localstack/blob/master/localstack/services/apigateway/apigateway_listener.py#L101

```
 File "/opt/code/localstack/localstack/services/apigateway/apigateway_listener.py", line 101, in forward_request
    'resourceId': resource.get('id'),
UnboundLocalError: local variable 'resource' referenced before assignment
```

Scanning up the file, it looks like `resource` is only be initialized within a `catch` block.  I assume this is a simple oversight rather than a complex bug.

This looks like a bug I can fix, but I'd appreciate any guidance you can offer.


Discussion:
@mhmoudgmal @whummer is this related to the APIGateway refactoring that you did recently?
@cspotcode Yes recently I added a feature for supporting the `request_context` object which the `resourceId` is one its fields, and I've been using it every day since it was applied  xD

This is one of my recent logs for a request I made a minute ago, as you can see the resourceId is there:
```js
>START RequestId: 72316791-29e5-1d0c-a064-f74b39a140e6 Version: $LATEST
> 2019-01-07T16:50:33.878Z      72316791-29e5-1d0c-a064-f74b39a140e6    Request:  { body: null,
>   headers:
>    { 'accept-encoding': 'gzip, deflate',
>      'X-Forwarded-For': '172.17.0.1, 0.0.0.0:4567',
>      connection: 'keep-alive',
>      accept: '*/*',
>      'user-agent': 'PostmanRuntime/7.4.2',
>      host: 'localhost:4567' },
>   resource: '/restapis/942A-ZA-Z62500/dev/_user_request_/v1.2.0/it/ab-tester/filter_learn_language_exclude_filter_ita?learn_language_alpha3=ITA',
>   requestContext:
>    { path: '/v1.2.0/it/ab-tester/filter_learn_language_exclude_filter_ita',
>      stage: 'dev',
>      resourceId: '4A-Z11157A-Z4A-Z',
>      identity:
>       { userAgent: 'PostmanRuntime/7.4.2',
>         sourceIp: '172.17.0.1',
>         accountId: '000000000000' },
>      accountId: '000000000000' },
>   queryStringParameters: { learn_language_alpha3: 'ITA' },
>   httpMethod: 'GET',
>   stageVariables: {},
>   path: '/v1.2.0/it/ab-tester/filter_learn_language_exclude_filter_ita',
>   pathParameters:
>    { locale: 'it',
>      target: 'filter_learn_language_exclude_filter_ita' },
>   isBase64Encoded: false }
> 2019-01-07T16:50:33.887Z      72316791-29e5-1d0c-a064-f74b39a140e6    requestParams:  { deviceUdid: undefined,
>   locale: 'it',
>   target: 'filter_learn_language_exclude_filter_ita',
>   trackingUuid: '633311d0deee1116e79466f03dfb21af',
>   uuid: undefined,
>   requestIp: '172.17.0.1',
>   userAgent: 'PostmanRuntime/7.4.2',
>   learnLanguageAlpha3: 'ITA' }
> END RequestId: 72316791-29e5-1d0c-a064-f74b39a140e6
```
Also how the tests are green then? Anyways I will have a look again tomorrow :) meanwhile please feel free to fix it and let me know if I can help you on something.

Resolving patch:
diff --git a/localstack/services/apigateway/apigateway_listener.py b/localstack/services/apigateway/apigateway_listener.py
--- a/localstack/services/apigateway/apigateway_listener.py
+++ b/localstack/services/apigateway/apigateway_listener.py
@@ -37,34 +37,28 @@ def forward_request(self, method, path, data, headers):
             search_match = re.search(regex2, path)
             api_id = search_match.group(1)
             stage = search_match.group(2)
-            relative_path = '/%s' % search_match.group(3)
+            relative_path_w_query_params = '/%s' % search_match.group(3)
+
+            relative_path, query_string_params = extract_query_string_params(path=relative_path_w_query_params)
+
+            path_map = get_rest_api_paths(rest_api_id=api_id)
             try:
-                integration = aws_stack.get_apigateway_integration(api_id, method, path=relative_path)
-                assert integration
+                extracted_path, resource = get_resource_for_path(path=relative_path, path_map=path_map)
             except Exception:
-                try:
-                    integration = aws_stack.get_apigateway_integration(api_id, 'ANY', path=relative_path)
-                    assert integration
-                except Exception:
-                    # if we have no exact match, try to find an API resource that contains path parameters
-                    path_map = get_rest_api_paths(rest_api_id=api_id)
-                    try:
-                        extracted_path, resource = get_resource_for_path(path=relative_path, path_map=path_map)
-                    except Exception:
-                        return make_error('Unable to find path %s' % path, 404)
+                return make_error('Unable to find path %s' % path, 404)
 
-                    integrations = resource.get('resourceMethods', {})
-                    integration = integrations.get(method, {})
-                    if not integration:
-                        integration = integrations.get('ANY', {})
-                    integration = integration.get('methodIntegration')
-                    if not integration:
+            integrations = resource.get('resourceMethods', {})
+            integration = integrations.get(method, {})
+            if not integration:
+                integration = integrations.get('ANY', {})
+            integration = integration.get('methodIntegration')
+            if not integration:
 
-                        if method == 'OPTIONS' and 'Origin' in headers:
-                            # default to returning CORS headers if this is an OPTIONS request
-                            return get_cors_response(headers)
+                if method == 'OPTIONS' and 'Origin' in headers:
+                    # default to returning CORS headers if this is an OPTIONS request
+                    return get_cors_response(headers)
 
-                        return make_error('Unable to find integration for path %s' % path, 404)
+                return make_error('Unable to find integration for path %s' % path, 404)
 
             uri = integration.get('uri')
             if method == 'POST' and integration['type'] == 'AWS':
@@ -89,8 +83,6 @@ def forward_request(self, method, path, data, headers):
                     account_id = uri.split(':lambda:path')[1].split(':function:')[0].split(':')[-1]
                     data_str = json.dumps(data) if isinstance(data, dict) else data
 
-                    relative_path, query_string_params = extract_query_string_params(path=relative_path)
-
                     source_ip = headers['X-Forwarded-For'].split(',')[-2]
 
                     # Sample request context:


Test patch:
diff --git a/tests/integration/test_api_gateway.py b/tests/integration/test_api_gateway.py
--- a/tests/integration/test_api_gateway.py
+++ b/tests/integration/test_api_gateway.py
@@ -38,15 +38,19 @@ class TestAPIGatewayIntegrations(unittest.TestCase):
     # endpoint paths
     API_PATH_DATA_INBOUND = '/data'
     API_PATH_HTTP_BACKEND = '/hello_world'
-    API_PATH_LAMBDA_PROXY_BACKEND = '/lambda/{test_param1}'
+    API_PATH_LAMBDA_PROXY_BACKEND = '/lambda/foo1'
+    API_PATH_LAMBDA_PROXY_BACKEND_WITH_PATH_PARAM = '/lambda/{test_param1}'
 
-    API_PATH_LAMBDA_PROXY_BACKEND_ANY_METHOD = '/lambda-any-method/{test_param1}'
+    API_PATH_LAMBDA_PROXY_BACKEND_ANY_METHOD = '/lambda-any-method/foo1'
+    API_PATH_LAMBDA_PROXY_BACKEND_ANY_METHOD_WITH_PATH_PARAM = '/lambda-any-method/{test_param1}'
 
     # name of Kinesis stream connected to API Gateway
     TEST_STREAM_KINESIS_API_GW = 'test-stream-api-gw'
     TEST_STAGE_NAME = 'testing'
     TEST_LAMBDA_PROXY_BACKEND = 'test_lambda_apigw_backend'
-    TEST_LAMBDA_PROXY_BACKEND_ANY_METHOD = 'test_lambda_apigw_backend_any_method'
+    TEST_LAMBDA_PROXY_BACKEND_WITH_PATH_PARAM = 'test_lambda_apigw_backend_path_param'
+    TEST_LAMBDA_PROXY_BACKEND_ANY_METHOD = 'test_ARMlambda_apigw_backend_any_method'
+    TEST_LAMBDA_PROXY_BACKEND_ANY_METHOD_WITH_PATH_PARAM = 'test_ARMlambda_apigw_backend_any_method_path_param'
 
     def test_api_gateway_kinesis_integration(self):
         # create target Kinesis stream
@@ -123,6 +127,16 @@ def forward_request(self, **kwargs):
         proxy.stop()
 
     def test_api_gateway_lambda_proxy_integration(self):
+        self._test_api_gateway_lambda_proxy_integration(
+            self.TEST_LAMBDA_PROXY_BACKEND,
+            self.API_PATH_LAMBDA_PROXY_BACKEND)
+
+    def test_api_gateway_lambda_proxy_integration_with_path_param(self):
+        self._test_api_gateway_lambda_proxy_integration(
+            self.TEST_LAMBDA_PROXY_BACKEND_WITH_PATH_PARAM,
+            self.API_PATH_LAMBDA_PROXY_BACKEND_WITH_PATH_PARAM)
+
+    def _test_api_gateway_lambda_proxy_integration(self, fn_name, path):
         # create lambda function
         zip_file = testutil.create_lambda_archive(
             load_file(TEST_LAMBDA_PYTHON),
@@ -131,20 +145,20 @@ def test_api_gateway_lambda_proxy_integration(self):
             runtime=LAMBDA_RUNTIME_PYTHON27
         )
         testutil.create_lambda_function(
-            func_name=self.TEST_LAMBDA_PROXY_BACKEND,
+            func_name=fn_name,
             zip_file=zip_file,
             runtime=LAMBDA_RUNTIME_PYTHON27
         )
 
         # create API Gateway and connect it to the Lambda proxy backend
-        lambda_uri = aws_stack.lambda_function_arn(self.TEST_LAMBDA_PROXY_BACKEND)
+        lambda_uri = aws_stack.lambda_function_arn(fn_name)
         invocation_uri = 'arn:aws:apigateway:%s:lambda:path/2015-03-31/functions/%s/invocations'
         target_uri = invocation_uri % (DEFAULT_REGION, lambda_uri)
 
         result = self.connect_api_gateway_to_http_with_lambda_proxy(
             'test_gateway2',
             target_uri,
-            path=self.API_PATH_LAMBDA_PROXY_BACKEND
+            path=path
         )
 
         api_id = result['id']
@@ -152,7 +166,7 @@ def test_api_gateway_lambda_proxy_integration(self):
         _, resource = get_resource_for_path('/lambda/foo1', path_map)
 
         # make test request to gateway and check response
-        path = self.API_PATH_LAMBDA_PROXY_BACKEND.replace('{test_param1}', 'foo1')
+        path = path.replace('{test_param1}', 'foo1')
         path = path + '?foo=foo&bar=bar&bar=baz'
 
         url = INBOUND_GATEWAY_URL_PATTERN.format(
@@ -174,7 +188,6 @@ def test_api_gateway_lambda_proxy_integration(self):
         parsed_body = json.loads(to_str(result.content))
         self.assertEqual(parsed_body.get('return_status_code'), 203)
         self.assertDictEqual(parsed_body.get('return_headers'), {'foo': 'bar123'})
-        self.assertDictEqual(parsed_body.get('pathParameters'), {'test_param1': 'foo1'})
         self.assertDictEqual(parsed_body.get('queryStringParameters'), {'foo': 'foo', 'bar': ['bar', 'baz']})
 
         request_context = parsed_body.get('requestContext')
@@ -192,6 +205,16 @@ def test_api_gateway_lambda_proxy_integration(self):
         self.assertEqual(result.status_code, 404)
 
     def test_api_gateway_lambda_proxy_integration_any_method(self):
+        self._test_api_gateway_lambda_proxy_integration_any_method(
+            self.TEST_LAMBDA_PROXY_BACKEND_ANY_METHOD,
+            self.API_PATH_LAMBDA_PROXY_BACKEND_ANY_METHOD)
+
+    def test_api_gateway_lambda_proxy_integration_any_method_with_path_param(self):
+        self._test_api_gateway_lambda_proxy_integration_any_method(
+            self.TEST_LAMBDA_PROXY_BACKEND_ANY_METHOD_WITH_PATH_PARAM,
+            self.API_PATH_LAMBDA_PROXY_BACKEND_ANY_METHOD_WITH_PATH_PARAM)
+
+    def _test_api_gateway_lambda_proxy_integration_any_method(self, fn_name, path):
         # create lambda function
         zip_file = testutil.create_lambda_archive(
             load_file(TEST_LAMBDA_PYTHON),
@@ -200,24 +223,24 @@ def test_api_gateway_lambda_proxy_integration_any_method(self):
             runtime=LAMBDA_RUNTIME_PYTHON27
         )
         testutil.create_lambda_function(
-            func_name=self.TEST_LAMBDA_PROXY_BACKEND_ANY_METHOD,
+            func_name=fn_name,
             zip_file=zip_file,
             runtime=LAMBDA_RUNTIME_PYTHON27
         )
 
         # create API Gateway and connect it to the Lambda proxy backend
-        lambda_uri = aws_stack.lambda_function_arn(self.TEST_LAMBDA_PROXY_BACKEND_ANY_METHOD)
+        lambda_uri = aws_stack.lambda_function_arn(fn_name)
         target_uri = aws_stack.apigateway_invocations_arn(lambda_uri)
 
         result = self.connect_api_gateway_to_http_with_lambda_proxy(
             'test_gateway3',
             target_uri,
             methods=['ANY'],
-            path=self.API_PATH_LAMBDA_PROXY_BACKEND_ANY_METHOD
+            path=path
         )
 
         # make test request to gateway and check response
-        path = self.API_PATH_LAMBDA_PROXY_BACKEND_ANY_METHOD.replace('{test_param1}', 'foo1')
+        path = path.replace('{test_param1}', 'foo1')
         url = INBOUND_GATEWAY_URL_PATTERN.format(
             api_id=result['id'],
             stage_name=self.TEST_STAGE_NAME,