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.
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
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:
Add a path parameter at the end of your https://api.companySite.com/service01/v1/api
URL
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}
.
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