I am developing a RESTful API in Symfony with the popular FOSRestBundle. The API is pretty much finished but it still doesn't support cross domain calls.
I've tried adding the NelmioCorsBundle, but I've failed to get it to work properly. I've started testing out different stuff like adding the
headers('Access-Control-Allow-Origin', '*');
manually as the first line of code in my app_dev.php. I know it probably isn't the best place to add it but I just wanted to know if it was actually doing anything.
Now here comes the funny part:
When I'm only sending GET-requests, the headers are being added. When I'm sending a successful POST-request, the headers also get added and I get a response which my AJAX call can read. However, when the API is throwing an exception suddenly the CORS headers are not being added in the response and thus my browser refuses to load the response. I don't know if this is normal behaviour but since my API throws exceptions for pretty much everything (missing parameters or parameters in wrong format, ...) this makes my API useless when using AJAX calls?
I've gone as far as setting up a proper listener to provide these headers:
namespace MyComp\MyBundle\Listeners;
use Symfony\Component\HttpKernel\Event\FilterResponseEvent;
class CorsListener
{
public function onKernelResponse(FilterResponseEvent $event)
{
$responseHeaders = $event->getResponse()->headers;
$responseHeaders->set('Access-Control-Allow-Headers', 'origin, content-type, accept');
$responseHeaders->set('Access-Control-Allow-Origin', '*');
$responseHeaders->set('Access-Control-Allow-Methods', 'POST, GET, PUT, DELETE, PATCH, OPTIONS');
}
}
and registered it in my services.yml file:
MyCompMyBundle.cors.listener:
class: MyComp\MyBundle\Listeners\CorsListener
tags:
- { name: kernel.event_listener, event: kernel.response, method: onKernelResponse }
When adding a
die("---");
in the CorsListener it correctly outputs this string when I try to make a request. It doesn't matter if the API throws an exception or not so I know for certain that this code block gets called every single request.
For example:
API._post("access-tokens", undefined, {"email": "xxx@hotmail.com", "password": "xxx"}, function(e) { console.log(e) })
Which calls:
_post: function(endpoint, id, dataObject, completionHandler) {
var url = this._baseUrl + endpoint;
url = typeof id !== "undefined" ? url + "/" + id : url;
$.ajax({
url: url,
type: 'POST',
// this._encodeData converts the data object to a x-www-form-encoded string
data: this._encodeData(dataObject),
crossDomain: true,
success: function(data) {
completionHandler({"success": data});
},
error: function(e) {
completionHandler({"error": e});
}
});
}
Returns a response with the following headers:
Access-Control-Allow-Headers:origin, content-type, accept
Access-Control-Allow-Methods:POST, GET, PUT, DELETE, PATCH, OPTIONS
Access-Control-Allow-Origin:*
Cache-Control:no-cache
Connection:close
Content-Type:application/json
Host:127.0.0.1:8000
(left out some unimportant bits like date and php version)
If I provide a correct email/password combination. If I enter rubbish or leave out the login parameters, the API should throw and return this error:
{
"code": 500,
"message": "Log in using either fb-id/access-token or email/password!"
}
However when the API does throw this error, even though the code from the CorsListener is still being called, the only response headers I get are:
Connection → close
Content-type → text/html; charset=UTF-8
Host → 127.0.0.1:8000
Which, again, blocks the AJAX calls.
When I send the same request in Postman with rubbish or forgotten parameters, it returns the a
500 Internal Server Error
With the correct headers
Access-Control-Allow-Headers → origin, content-type, accept
Access-Control-Allow-Methods → POST, GET, PUT, DELETE, PATCH, OPTIONS
Access-Control-Allow-Origin → *
Cache-Control → no-cache
Etc...
and the content
{
"code": 500,
"message": "Log in using either fb-id/access-token or email/password!"
}
Why is my server not handling the AJAX calls the same way as it handles Postman? I've read that Postman doesn't send out an Origin header but I don't think this should make such a big difference?
Am I doing something wrong? Or am I simply trying to achieve something which is not possible? If so, what is the correct way to handle my scenario? I'm not only trying fix my problem, I'm also trying to understand what the root is so I can learn from it.