IE10 平台预览第四版支持用于 XMLHttpRequest (XHR) 的跨域资源共享 (CORS), 从而简化了可跨浏览器一致运行的跨网站方案的构建过程。CORS for XHR 使跨网站共享数据变得简单而灵活。在最基本的方案中,CORS 允许创建可从任意网站访问的数据源,并且只需稍做调整,即可限制允许的网站,支持数据修改,甚至允许身份验证。最重要的是,CORS 可通过要求服务器参与来保护现有网站的安全。
我们来看一下跨域 XHR 请求与同域请求相比有何区别。就脚本而言,唯一的区别在于传递到 open 方法的 URL。例如,假设我们要编写一个获取相册列表的脚本。
// Script running on http://photos.contoso.com
var xhr = new XMLHttpRequest();
xhr.onerror = _handleError;
xhr.onload = _handleLoad;
xhr.open("GET", "/albums", true);
xhr.send();
现在,我们希望从另一个域访问相册列表。另一个域可以是完全不同的域,也可以是具有相同基域的不同主机。无论是哪种情况,只需要从另一个网站指向完整 URL,浏览器就会自动发送 CORS 请求。
// Script running on http://www.contoso.com
var xhr = new XMLHttpRequest();
xhr.onerror = _handleError;
xhr.onload = _handleLoad;
xhr.open("GET", "http://photos.contoso.com/albums", true);
xhr.send();
网站可以通过在功能检测中包含对回退功能的检测,为旧浏览器提供回退功能。最佳做法是检查“withCredentials
”,因为它直接与 XHR 的 CORS 支持相关。
// Script running on http://www.contoso.com
var xhr = new XMLHttpRequest();
if ("withCredentials" in xhr) {
xhr.onerror = _handleError;
xhr.onload = _handleLoad;
xhr.open("GET", "http://photos.contoso.com/albums", true);
xhr.send();
} else {
// Fallback behavior for browsers without CORS for XHR
}
此时,我们的客户端代码直接向“http://photos.contoso.com”发出 CORS 请求,但该请求未返回任何数据。失败的原因在于服务器尚未参与其中。快速查看一下开发工具就可以发现是哪个环节出现问题。
我们可以从中看出服务器需要在响应中发送“Access-Control-Allow-Origin
”标头。在我们的方案中,我们没有打开相册供任何网站访问,而是希望只允许从“http://www.contoso.com
”进行访问。为此,需要允许服务器识别发出请求的位置。检查我们发出的请求可以发现,新标头中明确地包含有“Origin
”这一信息。
GET http://photos.contoso.com/albums HTTP/1.1
Origin: http://www.contoso.com
...
通过使用此信息,服务器可以选择将访问权限限制为任意一组网站。如果服务器始终添加值为“*”的“Access-Control-Allow-Origin
”标头,则所有网站均具有访问权限。在我们的方案中,我们会让服务器验证域,然后设置“Access-Control-Allow-Origin
”以便仅允许“http://www.contoso.com
”。
HTTP/1.1 200 OK
Access-Control-Allow-Origin: http://www.contoso.com
...
进行上述更新后,我们的“http://www.contoso.com
”客户端现在就可以从服务器访问位于“http://photos.contoso.com
”中的相册列表了。
到目前为止,我们所讨论的“简单”CORS 请求非常适合基本的只读方案,如下载相册。如需采取下一步操作以便跨网站修改数据,则要求在服务器上完成更多工作。例如,假设我们要在客户端中添加相应代码以创建一个新相册。
var xhr = new XMLHttpRequest();
xhr.onerror = _handleError;
xhr.onload = _handleLoad;
xhr.open("PUT", "http://photos.contoso.com/albums", true);
xhr.send(JSON.stringify({ name: "New Album" }));
按原样运行上述代码不起作用。通过检查网络流量可以发现,请求已发送,但不是我们预期的请求。
浏览器实际发送的内容称为预检请求。预检请求在可能导致服务器上发生数据修改的请求之前发送。可根据是否存在 CORS 规范中定义的非简单属性来识别此类请求。这些属性可以是诸如“PUT
”之类的特定 HTTP 方法,也可以是自定义的 HTTP 标头。浏览器发送预检请求的目的是要求服务器允许发送实际请求。在我们的示例中,浏览器验证是否允许“PUT
”请求。
OPTIONS http://photos.contoso.com/albums HTTP/1.1
Origin: http://www.contoso.com
Access-Control-Request-Method: PUT
...
让浏览器发送实际请求需要在服务器上进行一些更改。同样,我们可以看一下开发工具以了解详细信息。
第一步是让服务器意识到“OPTIONS
”预检请求与针对同一 URL 的其他请求有所不同。服务器通过确保“Access-Control-Request-Method
”是从允许的域请求“PUT
”来验证预检请求,之后,它会通过“Access-Control-Allow-Methods
”标头发送相应的批准信息。
HTTP/1.1 200 OK
Access-Control-Allow-Origin: http://www.contoso.com
Access-Control-Allow-Methods: PUT
...
预检完成并得到批准后,即会发出实际的请求。
PUT http://photos.contoso.com/albums HTTP/1.1
Origin: http://www.contoso.com
...
从技术层面上讲,添加相册的操作到此已经完成,不过,在服务器发出正确响应之前,我们的客户端代码并不知道这一信息。具体说就是,服务器仍然必须在响应中包含“Access-Control-Allow-Origin
”。
HTTP/1.1 200 OK
Access-Control-Allow-Origin: http://www.contoso.com
...
这样一来,客户端就可以跨域添加新的相册,并识别该操作是否成功完成。
将 CORS 与其他新增平台功能搭配使用可创建引人入胜的方案。例如,跨网站上载体验可以利用 CORS、XHR、FileAPI 和进程事件来跟踪跨域文件上载操作。
—Internet Explorer 项目经理 Tony Ross
英文原文:CORS for XHR in IE10