It is easy to wrap an existing API with API Gateway to hide the host and support HTTPS. I followed the official tutorial and set up my API.
To be specific, assume my DRF (Django REST Framework) project is served at my.server.host:12345
.
I created an ANY
method with Integration type
of HTTP Proxy
and
Endpoint URL
of http://my.server.host:12345/{proxy}
.
Then I save the setup and deploy the API
(it is very important to deploy API everytime you do any edition!).
Having API Gateway set up, I attempted to send a GET
request to the API from my front-end website,
but I received the following 403
response.
Failed to load resource: the server responded with a status of 403 ()
which is followed by a CORS error.
Access to XMLHttpRequest at 'https://[API_ID].execute-api.ap-northeast-1.amazonaws.com/[API_ROUTE]/' (redirected from 'https://[API_ID].execute-api.ap-northeast-1.amazonaws.com/[API_STAGE]/[API_ROUTE]') from origin 'https://my.front-end.com' has been blocked by CORS policy: Response to preflight request doesn't pass access control check: No 'Access-Control-Allow-Origin' header is present on the requested resource.
However, I already set up CORS in my DRF project using django-cors-headers, and direct access to the API did not cause any CORS error.
It is more confusing that I did not receive any request in my backend, except a single 301 response:
"GET /[API_ROUTE] HTTP/1.1" 301 0
it seems that the request was redirected once, but the redirected request did not ever reach my server. Therefore the response to the redirected request were instead made by API Gateway.
When I send a POST
request this time, I got a 500
error.
This time, the request reached my server, and I saw the following error message:
RuntimeError: You called this URL via POST, but the URL doesn't end in a slash and you have APPEND_SLASH set. Django can't redirect to the slash URL while maintaining POST data. Change your form to point to my.server.host:12345/[API_ROUTE]/ (note the trailing slash), or set APPEND_SLASH=False in your Django settings.
In short, it turned out that API Gateway will ignore the trailing slash of all incoming request (see this thread),
so changing the Endpoint URL
to http://my.server.host:12345/{proxy}/
solve the problem.
The reason why the CORS error was thrown lies in the fact that a successful cross-site request is composed of a preflight request and an actual request.
This graph from MDN web docs illustrates what is happening under the hood for a successful cross-site request.
In our case, since the route missed the trailing slash,
the response to our preflight request was a 301
made by API Gateway.
However, API Gateway did not add [API_STAGE]
to the redirect response,
and pass the 301
response back to the front-end as-is.
The front-end then send another preflight request, this time with the trailing slash, but missing the API_STAGE.
As a result, API Gateway response a 403
directly,
which, of course, contained no Access-Control-Allow-Origin
header.
As a result, the actual request was blocked by the browser.
The preflight request cannot reach the server because API Gateway does not proxy for routes that do not exist.
Notice that if we shut down our DRF server, the preflight request will receive a 504 Network error communicating with endpoint
error,
which is of course also generated by API Gateway.