使用 Symfony、jQuery、FOSRestBundle 和 NelmioCorsBundle 的 CORS
CORS with Symfony, jQuery, FOSRestBundle and NelmioCorsBundle
⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️
TL;DR:编辑(阅读全文之前):
This issue is solved because I made a very stupid mistake in my code, not related to CORS or anything.
If you want to read this issue anyway, just note that it has a working CORS configuration, if you might want to take a good example.
⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️
(注释结束)
情况
我有一个 multi-domain Symfony 应用程序,由于某些原因,back-end 部分使用 web-services 而非经典形式更新数据。
Back-end: back.mydomain.dev
Web 服务域:api.mydomain.dev
我正在使用 jQuery 对这些网络服务进行 AJAX 调用,如果我想修改或创建 object,我也会发送 AJAX 请求, objects 与 Doctrine 实体合并并持续存在。
我整整一年都在努力让 GET、PUT、POST 和 DELETE 请求能够在这个应用程序上正常工作,只是因为它们在不同的域中,我被迫设置CORS 在我的不同环境中。
设置
所有 jQuery AJAX 请求如下所示:
ajaxObject = {
url: 'http://api.mydomain.dev/' + uri,
type: method, // Can be GET, PUT, POST or DELETE only
dataType: 'json',
xhrFields: {
withCredentials: true
},
crossDomain: true,
contentType: "application/json",
jsonp: false,
data: method === 'GET' ? data : JSON.stringify(data) // Here, "data" is ALWAYS containing a plain object. If empty, it equals to "{}"
};
// ... Add callbacks depending on requests
$.ajax(ajaxObject);
后面,路由是用 Symfony 管理的。
对于 CORS 配置,我使用 NelmioCorsBundle 和此配置:
nelmio_cors:
paths:
"^/":
allow_credentials: true
origin_regex: true
allow_origin:
- "^(https?://)?(back|api)\.mydomain.dev/?"
allow_headers: ['Origin','Accept','Content-Type']
allow_methods: ['POST','GET','DELETE','PUT','OPTIONS']
max_age: 3600
hosts:
- "^(https?://)?(back|api)\.mydomain.dev/?"
使用的控制器是FOSRestBundle的扩展,具有一定的安全性(例如,当你没有正确的角色时,不能POST/PUT/DELETE),可以更新object s 和 returns 只有 JSON 数据(有一个侦听器)。
理想行为
理想情况下,我想要这个:
- 运行 jQuery AJAX POST/PUT/DELETE 请求
- 它必须发送带有所有 CORS 的 OPTIONS 请求 headers
- NelmioCorsBundle 应该 return 正确的 CORS headers 接受要完成的请求,甚至在 运行 应用程序内的任何控制器(由捆绑包的请求侦听器创建)之前
- 如果被接受,正确的 HTTP 请求将作为序列化的 JSON 字符串(在请求负载中)将所有请求数据发送到控制器,Symfony 检索它并解释正确的 JSON字符串作为数组 object
- 控制器然后获取数据,执行它的操作,然后 return 一个
application/json
响应。
问题
但是在这里,我尝试了 maaaaany 组合,但我似乎无法让它发挥作用。
点 n°3 和 4 完全或部分失败。
已尝试解决方法
当我不用JSON.stringify
序列化data
时(参见设置 上面的部分),发送了 OPTIONS 请求,但是 FOSRestBundle 发送了一个 BadRequestHttpException
说 Invalid json message received
,这是完全正常的,因为 "request payload"(如 Chrome 中所见开发人员工具)是经典的 application/x-www-form-urlencoded
内容,即使我在 jQuery AJAX 请求中指定了 contentType: "application/json"
,而它应该是序列化的 JSON 字符串。
但是,如果我做序列化data
var,"request payload"是有效的,但是OPTIONS请求没有发送,由于缺少 CORS 接受,导致整个请求失败。
如果我将contentType: "application/json"
替换为application/x-www-form-urlencoded
或multipart/form-data
,并且不序列化数据,则请求有效负载有效,但OPTIONS请求未发送。应该也是正常的,在contentType参数下解释in jQuery's docs:
Note: For cross-domain requests, setting the content type to anything other than application/x-www-form-urlencoded, multipart/form-data, or text/plain will trigger the browser to send a preflight OPTIONS request to the server.
但是,如何发送正确的 CORS 请求?
问题
- 这个问题从何而来?
- 这是我的设置问题吗?用 jQuery?
- 与AJAX本身?
- 解决这个该死的问题有哪些不同的解决方案?
编辑(评论后)
2015-06-29 17:39
条件:
在AJAX中,contentType
设置为application/json
。
data
选项设置为序列化的 JSON 字符串。
预检请求headers
OPTIONS http://api.mydomain.dev/fr/object/1 HTTP/1.1
Access-Control-Request-Method: POST
Origin: http://back.mydomain.dev
Access-Control-Request-Headers: accept, content-type
Referer: http://back.mydomain.dev/...
预检响应headers
HTTP/1.1 200 OK
Access-Control-Allow-Credentials: true
Access-Control-Allow-Methods: POST, GET, DELETE, PUT, OPTIONS
Access-Control-Allow-Headers: origin, accept, content-type
Access-Control-Max-Age: 3600
Access-Control-Allow-Origin: http://back.mydomain.dev
POST
REQUEST headers(预检后)
POST http://api.mydomain.dev/fr/object/1 HTTP/1.1
Origin: http://back.mydomain.dev
Content-Type: application/json
Referer: http://back.mydomain.dev/...
Cookie: mydomainPortal=v5gjedn8lsagt0uucrhshn7ck1
Accept: application/json, text/javascript, */*; q=0.01
请求负载(原始):
{"json":{"id":1,"name":"object"}}
必须注意,这会引发 javascript 错误:
XMLHttpRequest cannot load http://api.mydomain.dev/fr/object/1. No 'Access-Control-Allow-Origin' header is present on the requested resource. Origin 'http://back.mydomain.dev' is therefore not allowed access.
POST
响应 header 秒(预检后)
HTTP/1.1 200 OK
Date: Mon, 29 Jun 2015 15:35:07 GMT
Server: Apache/2.4.12 (Unix) OpenSSL/1.0.2c Phusion_Passenger/5.0.11
Keep-Alive: timeout=5, max=91
Connection: Keep-Alive
Transfer-Encoding: chunked
Content-Type: text/html; charset=UTF-8
2015-06-29 17:39
我也试过使用 vanilla javascript 来制作 XMLHttpRequest,问题还是一样:
var xhr = new XMLHttpRequest;
xhr.open('POST', 'http://api.mydomain.dev/fr/objects/1', true);
xhr.setRequestHeader('Content-type', 'application/json');
xhr.withCredentials = true; // Sends cookie
xhr.onreadystatechange = function(e) {
// A simple callback
console.info(JSON.parse(e.target.response));
};
// Now send the request with the serialized payload:
xhr.send('{"id":1,"name":"Updated test object"}');
然后,浏览器发送OPTIONS请求:
OPTIONS http://api.mydomain.dev/fr/objects/1 HTTP/1.1
Connection: keep-alive
Access-Control-Request-Headers: content-type
Access-Control-Request-Method: POST
Origin: http://back.mydomain.dev
哪个return是正确的回答header:
Access-Control-Allow-Credentials: true
Access-Control-Allow-Methods: POST, GET, DELETE, PUT, OPTIONS
Access-Control-Allow-Headers: origin, accept, content-type
Access-Control-Max-Age: 3600
Access-Control-Allow-Origin: http://back.mydomain.dev
但是,浏览器发送 POST 请求时没有任何 "Access-Control-*" header.
实际上,我的设置运行良好。
绝对完美,如果我可以说的话。
这只是某种...愚蠢。
exit;
被隐藏在 PHP 文件的深处。
抱歉打扰了。我想这是 SO 上 text/quality 比率的记录。
编辑:我仍然希望这个问题能够成为一个完全兼容 CORS 的 Symfony 项目的适当示例。
⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️
TL;DR:编辑(阅读全文之前):
This issue is solved because I made a very stupid mistake in my code, not related to CORS or anything.
If you want to read this issue anyway, just note that it has a working CORS configuration, if you might want to take a good example.
⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️
(注释结束)
情况
我有一个 multi-domain Symfony 应用程序,由于某些原因,back-end 部分使用 web-services 而非经典形式更新数据。
Back-end: back.mydomain.dev Web 服务域:api.mydomain.dev
我正在使用 jQuery 对这些网络服务进行 AJAX 调用,如果我想修改或创建 object,我也会发送 AJAX 请求, objects 与 Doctrine 实体合并并持续存在。
我整整一年都在努力让 GET、PUT、POST 和 DELETE 请求能够在这个应用程序上正常工作,只是因为它们在不同的域中,我被迫设置CORS 在我的不同环境中。
设置
所有 jQuery AJAX 请求如下所示:
ajaxObject = {
url: 'http://api.mydomain.dev/' + uri,
type: method, // Can be GET, PUT, POST or DELETE only
dataType: 'json',
xhrFields: {
withCredentials: true
},
crossDomain: true,
contentType: "application/json",
jsonp: false,
data: method === 'GET' ? data : JSON.stringify(data) // Here, "data" is ALWAYS containing a plain object. If empty, it equals to "{}"
};
// ... Add callbacks depending on requests
$.ajax(ajaxObject);
后面,路由是用 Symfony 管理的。
对于 CORS 配置,我使用 NelmioCorsBundle 和此配置:
nelmio_cors:
paths:
"^/":
allow_credentials: true
origin_regex: true
allow_origin:
- "^(https?://)?(back|api)\.mydomain.dev/?"
allow_headers: ['Origin','Accept','Content-Type']
allow_methods: ['POST','GET','DELETE','PUT','OPTIONS']
max_age: 3600
hosts:
- "^(https?://)?(back|api)\.mydomain.dev/?"
使用的控制器是FOSRestBundle的扩展,具有一定的安全性(例如,当你没有正确的角色时,不能POST/PUT/DELETE),可以更新object s 和 returns 只有 JSON 数据(有一个侦听器)。
理想行为
理想情况下,我想要这个:
- 运行 jQuery AJAX POST/PUT/DELETE 请求
- 它必须发送带有所有 CORS 的 OPTIONS 请求 headers
- NelmioCorsBundle 应该 return 正确的 CORS headers 接受要完成的请求,甚至在 运行 应用程序内的任何控制器(由捆绑包的请求侦听器创建)之前
- 如果被接受,正确的 HTTP 请求将作为序列化的 JSON 字符串(在请求负载中)将所有请求数据发送到控制器,Symfony 检索它并解释正确的 JSON字符串作为数组 object
- 控制器然后获取数据,执行它的操作,然后 return 一个
application/json
响应。
问题
但是在这里,我尝试了 maaaaany 组合,但我似乎无法让它发挥作用。
点 n°3 和 4 完全或部分失败。
已尝试解决方法
当我不用
JSON.stringify
序列化data
时(参见设置 上面的部分),发送了 OPTIONS 请求,但是 FOSRestBundle 发送了一个BadRequestHttpException
说Invalid json message received
,这是完全正常的,因为 "request payload"(如 Chrome 中所见开发人员工具)是经典的application/x-www-form-urlencoded
内容,即使我在 jQuery AJAX 请求中指定了contentType: "application/json"
,而它应该是序列化的 JSON 字符串。但是,如果我做序列化
data
var,"request payload"是有效的,但是OPTIONS请求没有发送,由于缺少 CORS 接受,导致整个请求失败。如果我将
contentType: "application/json"
替换为application/x-www-form-urlencoded
或multipart/form-data
,并且不序列化数据,则请求有效负载有效,但OPTIONS请求未发送。应该也是正常的,在contentType参数下解释in jQuery's docs:Note: For cross-domain requests, setting the content type to anything other than application/x-www-form-urlencoded, multipart/form-data, or text/plain will trigger the browser to send a preflight OPTIONS request to the server.
但是,如何发送正确的 CORS 请求?
问题
- 这个问题从何而来?
- 这是我的设置问题吗?用 jQuery?
- 与AJAX本身?
- 解决这个该死的问题有哪些不同的解决方案?
编辑(评论后)
2015-06-29 17:39
条件:
在AJAX中,contentType
设置为application/json
。
data
选项设置为序列化的 JSON 字符串。
预检请求headers
OPTIONS http://api.mydomain.dev/fr/object/1 HTTP/1.1
Access-Control-Request-Method: POST
Origin: http://back.mydomain.dev
Access-Control-Request-Headers: accept, content-type
Referer: http://back.mydomain.dev/...
预检响应headers
HTTP/1.1 200 OK
Access-Control-Allow-Credentials: true
Access-Control-Allow-Methods: POST, GET, DELETE, PUT, OPTIONS
Access-Control-Allow-Headers: origin, accept, content-type
Access-Control-Max-Age: 3600
Access-Control-Allow-Origin: http://back.mydomain.dev
POST
REQUEST headers(预检后)
POST http://api.mydomain.dev/fr/object/1 HTTP/1.1
Origin: http://back.mydomain.dev
Content-Type: application/json
Referer: http://back.mydomain.dev/...
Cookie: mydomainPortal=v5gjedn8lsagt0uucrhshn7ck1
Accept: application/json, text/javascript, */*; q=0.01
请求负载(原始):
{"json":{"id":1,"name":"object"}}
必须注意,这会引发 javascript 错误:
XMLHttpRequest cannot load http://api.mydomain.dev/fr/object/1. No 'Access-Control-Allow-Origin' header is present on the requested resource. Origin 'http://back.mydomain.dev' is therefore not allowed access.
POST
响应 header 秒(预检后)
HTTP/1.1 200 OK
Date: Mon, 29 Jun 2015 15:35:07 GMT
Server: Apache/2.4.12 (Unix) OpenSSL/1.0.2c Phusion_Passenger/5.0.11
Keep-Alive: timeout=5, max=91
Connection: Keep-Alive
Transfer-Encoding: chunked
Content-Type: text/html; charset=UTF-8
2015-06-29 17:39
我也试过使用 vanilla javascript 来制作 XMLHttpRequest,问题还是一样:
var xhr = new XMLHttpRequest;
xhr.open('POST', 'http://api.mydomain.dev/fr/objects/1', true);
xhr.setRequestHeader('Content-type', 'application/json');
xhr.withCredentials = true; // Sends cookie
xhr.onreadystatechange = function(e) {
// A simple callback
console.info(JSON.parse(e.target.response));
};
// Now send the request with the serialized payload:
xhr.send('{"id":1,"name":"Updated test object"}');
然后,浏览器发送OPTIONS请求:
OPTIONS http://api.mydomain.dev/fr/objects/1 HTTP/1.1
Connection: keep-alive
Access-Control-Request-Headers: content-type
Access-Control-Request-Method: POST
Origin: http://back.mydomain.dev
哪个return是正确的回答header:
Access-Control-Allow-Credentials: true
Access-Control-Allow-Methods: POST, GET, DELETE, PUT, OPTIONS
Access-Control-Allow-Headers: origin, accept, content-type
Access-Control-Max-Age: 3600
Access-Control-Allow-Origin: http://back.mydomain.dev
但是,浏览器发送 POST 请求时没有任何 "Access-Control-*" header.
实际上,我的设置运行良好。
绝对完美,如果我可以说的话。
这只是某种...愚蠢。
exit;
被隐藏在 PHP 文件的深处。
抱歉打扰了。我想这是 SO 上 text/quality 比率的记录。
编辑:我仍然希望这个问题能够成为一个完全兼容 CORS 的 Symfony 项目的适当示例。