Search code examples
amazon-web-servicesaws-api-gateway

Why does API Gateway not pass through the request path parameters in proxy mode?


I need to create an API Gateway that acts as a proxy to a third party client API. I followed exactly what is in this AWS Tutorial. The API Gateway I created is configured to use a custom domain. For purposes of this question, I'll use api.mydomain.com to represent the API Gateway instance I've created in AWS.

My API has multiple resources defined, for example

  • api.mydomain.com/resource01
  • api.mydomain.com/resource02
  • api.mydomain.com/resource03

Each resource in API Gateway corresponds to a different backend service. For example a request to /resource01 should forward to https://api.companySite.com/service01/v1 and a request to /resource02 should forward to https://api.companySite.com/service02/v1 and so on for all the different backend services that the 3rd party has that we use.

Here is a screen shot demonstrating exactly how I have my API Gateway resources defined. All resources have CORS enabled and a proxy endpoint with ANY HTTP method defined.

AWS Resource Definitions Resource Definitions

AWS Method Definitions Method Definitions

The problem I am running into is when I test the methods I am getting a 404 response. The 3rd party API is definitely working and responds with 200 when called directly.

When I try to hit the endpoint in API Gateway via Postman https://api.mydomain.com/resource01/api/serviceName?value=12345 I get a 404.

When I use the test method in AWS the logs report back a result that are confusing to me. I'm not sure what it is trying to tell me, other than it seems like the URL API Gateway is constructing and sending to the client API is wrong. Here is what the logs say...

/resource01/{proxy+} - ANY method test results
Request
/resource01/api/serviceName?value=12345
Status
404
Response body
No data
Response headers
{
  "content-length": "0",
  "date": "Fri, 13 Oct 2023 21:48:41 GMT",
  "server": "istio-envoy"
}
**Log**
Execution log 
Fri Oct 13 21:48:41 UTC 2023 : HTTP Method: GET, Resource Path: /resource01/api/serviceName
Fri Oct 13 21:48:41 UTC 2023 : Method request path: {proxy=api/serviceName}
Fri Oct 13 21:48:41 UTC 2023 : Method request query string: {value=12345}
Fri Oct 13 21:48:41 UTC 2023 : Method request headers: {}
Fri Oct 13 21:48:41 UTC 2023 : Method request body before transformations:
Fri Oct 13 21:48:41 UTC 2023 : Endpoint request URI: https://api.companySite.com/service01/v1?value=12345
Fri Oct 13 21:48:41 UTC 2023 : Endpoint request body after transformations:
Fri Oct 13 21:48:41 UTC 2023 : Sending request to https://api.companySite.com/service01/v1?value=12345
Fri Oct 13 21:48:41 UTC 2023 : Successfully completed execution
Fri Oct 13 21:48:41 UTC 2023 : Method completed with status: 404

The logs clearly show Sending request to https://api.companySite.com/service01/v1?value=12345. Which is wrong and not what I need! API Gateway for some reason is dropping the path portion of the URL that I need api/serviceName and seems to only be sending the query parameters.

I have been spinning wheels trying to figure out what API Gateway is doing or I am misconfiguring to end up with that resulting URI. Any help or direction that anyone can provide on how to resolve this is very appreciated!

To give a clear example, the desired behavior that I require, is when a client hits my AWS API Gateway with a request like this, api.mydomain.com/resource01/api/serviceName?value=12345. The API Gateway should take everything after the resource01 and append it to the target API. So the resulting request that should be constructed and made would like like https://api.companySite.com/service01/v1/api/serviceName?value=12345


Solution

  • When using {proxy+}, the request path is 'stored' as proxy in the method's request path.

    You can see this from the logs:

    Method request path: {proxy=api/serviceName}

    To pass through the request path e.g. /api/serviceName, you must include a path parameter in your integration endpoint URL for Amazon API Gateway to map this proxy request path to.

    Without this, as you can see in the logs, your endpoint will be called without the path parameters.

    There's 2 steps:

    1. Add a path parameter at the end of your https://api.companySite.com/service01/v1/api URL

    2. Map the path parameter to method.request.path.proxy

    In this case, I’ve named the path parameter {proxy} as it is commonly referred to in AWS documentation, but you can name the path parameter anything you like e.g. {mySpecialParam}.

    editing endpoint URL to add '/{proxy}' to the end of the URL & then mapping a URL path parameter with 'Name' set to 'proxy' and 'Mapped from` set to 'method.request.path.proxy' in the 'URL path parameters' section

    demonstrating the final result of the above change applied with the endpoint URL changed and the URL path parameter mapping showing up in the 'URL path parameters' section

    You'll then see that {proxy} is replaced by API Gateway in the logs:

    (xxx) Endpoint request URI: https://api.companySite.com/service01/v1/to/be/concatenated?myParam=5555

    (xxx) Starting execution for request: xxx-xxx-xxx-xxx-xxx
    (xxx) HTTP Method: GET, Resource Path: /resource01/to/be/concatenated
    (xxx) Method request path: {proxy=to/be/concatenated}
    (xxx) Method request query string: {myParam=5555}
    (xxx) Method request headers: {User-Agent=curl/8.1.2, X-Forwarded-Proto=https, X-Forwarded-For=xxx.xxx.xxx.xxx, Host=xxx.execute-api.eu-west-1.amazonaws.com, X-Forwarded-Port=443, X-Amzn-Trace-Id=Root=1-xxx-xxx, accept=*/*}
    (xxx) Method request body before transformations: 
    (xxx) Endpoint request URI: https://api.companySite.com/service01/v1/to/be/concatenated?myParam=5555
    (xxx) Endpoint request headers: {x-amzn-apigateway-api-id=xxx, User-Agent=curl/8.1.2, X-Forwarded-Proto=https, X-Forwarded-For=xxx.xxx.xxx.xxx, X-Forwarded-Port=443, X-Amzn-Trace-Id=Root=1-xxx-xxx, accept=*/*}
    (xxx) Endpoint request body after transformations: 
    (xxx) Sending request to https://api.companySite.com/service01/v1/to/be/concatenated?myParam=5555
    ...
    

    Here's a (very rough) end to end 'copy-and-paste' example using the AWS CLI v2, that details the above behaviour creating an API called my-special-api in the eu-west-1 region:

    api_name="my-special-api"
    region="eu-west-1"
    stage_name="my-stage"
    
    aws_response=$(aws apigateway create-rest-api --name $api_name --region $region --endpoint-configuration ' { "types": ["REGIONAL"] }')
    api_id=$(echo $aws_response | jq -r '.id')
    
    aws_response=$(aws apigateway get-resources --rest-api-id $api_id --region $region)
    root_id=$(echo $aws_response | jq -r '.items[] | select(.path == "/") | .id')
    
    aws_response=$(aws apigateway create-resource --rest-api-id $api_id --parent-id $root_id --path-part 'resource01' --region $region)
    resource01_id=$(echo $aws_response | jq -r '.id')
    
    aws_response=$(aws apigateway create-resource --rest-api-id $api_id --parent-id $resource01_id --path-part '{proxy+}' --region $region)
    resource01_proxy_id=$(echo $aws_response | jq -r '.id')
    
    aws apigateway put-method --rest-api-id $api_id --resource-id $resource01_proxy_id --http-method ANY --authorization-type "NONE" --request-parameters method.request.path.proxy=true --region $region --no-cli-pager
    
    aws apigateway put-integration --rest-api-id $api_id --resource-id $resource01_proxy_id --http-method ANY --type HTTP_PROXY --integration-http-method ANY --uri 'https://api.companySite.com/service01/v1/{proxy}' --request-parameters integration.request.path.proxy=method.request.path.proxy --region $region --no-cli-pager
    
    aws apigateway create-deployment --rest-api-id $api_id --stage-name $stage_name --region $region --no-cli-pager
    
    url="https://$api_id.execute-api.$region.amazonaws.com/$stage_name/resource01/to/be/concatenated?myParam=5555"
    curl $url