利用CORS实现跨域请求--转
原文地址:http://newhtml.net/using-cors/
跨域请求一直是网页编程中的一个难题,在过去,绝大多数人都倾向于使用JSONP
来解决这一问题。不过现在,我们可以考虑一下W3C中一项新的特性——CORS(Cross-Origin Resource Sharing)了。
本文的所有代码均来自http://www.html5rocks.com/en/tutorials/cors/,如果您对其中的任何技术细节存在疑问,请以原文为准。
客户端
创建XmlHttpRequest
对象
对于CORS,Chrome、FireFox以及Safari,需要使用XmlHttpRequest2
对象;而对于IE,则需要使用XDomainRequest
;Opera目前还不支持这一特性,但很快就会支持。
因此,在对象的创建上,我们不得不首先针对不同的浏览器而进行一下预处理:
function createCORSRequest(method, url) {
var xhr = new XMLHttpRequest();
if ("withCredentials" in xhr) { // "withCredentials"属性是XMLHTTPRequest2中独有的
xhr.open(method, url, true); } else if (typeof XDomainRequest != "undefined") { // 检测是否XDomainRequest可用
xhr = new XDomainRequest();
xhr.open(method, url); } else { // 看起来CORS根本不被支持
xhr = null; }
return xhr;
} var xhr = createCORSRequest('GET', url);
if (!xhr) {
throw new Error('CORS not supported');
}
事件处理
原先的XmlHttpRequest
对象仅仅只有一个事件——onreadystatechange
,用以通知所有的事件,而现在,我们除了这个事件之外又多了很多新的。
事件 | 说明 |
---|---|
onloadstart* | 当请求发生时触发 |
onprogress | 读取及发送数据时触发 |
onabort* | 当请求被中止时触发,如使用abort()方法 |
onerror | 当请求失败时触发 |
onload | 当请求成功时触发 |
ontimeout | 当调用者设定的超时时间已过而仍未成功时触发 |
onloadend* | 请求结束时触发(无论成功与否) |
注:带星号的表示IE的XDomainRequest
仍不支持。
数据来自http://www.w3.org/TR/XMLHttpRequest2/#events。
绝大多数情况下,我们只需要和onload
及onerror
打交道,就像下面这样:
xhr.onload = function() {
var responseText = xhr.responseText;
console.log(responseText);
// 继续其它代码
}; xhr.onerror = function() {
console.log('There was an error!');
};
这儿有一点小异样。尽管我们可以通过onerror
得知请求发生了错误,但在事件处理时,我们无法从代码上获知失败的任何原因。比如,FireFox在失败时将responseText
置空并返回一个0值作为状态,这当中并不包含任何错误的具体情况。
withCredentials
标准的CORS请求不对cookies做任何事情,既不发送也不改变。如果希望改变这一情况,就需要将withCredentials
设置为true
。
xhr.withCredentials = true;
另外,服务端在处理这一请求时,也需要将Access-Control-Allow-Credentials
设置为true
。这一点我们稍后来说。
withCredentials
属性使得请求包含了远程域的所有cookies,但值得注意的是,这些cookies仍旧遵守“同域”的准则,因此从代码上你并不能从document.cookies
或者回应HTTP头当中进行读取。
发送请求
请求通过一个简单的send()
方法进行发送,如果请求当中需要包含任何内容,也只需要将其作为一个参数传递给send()
即可。一旦服务端配置OK,那么你将只需要处理后续的onload
事件,这正像我们平时所熟悉的XHR一样。
来看一段完整的小代码:
// 创建XHR对象
function createCORSRequest(method, url) {
var xhr = new XMLHttpRequest();
if ("withCredentials" in xhr) {
// 针对Chrome/Safari/Firefox.
xhr.open(method, url, true);
} else if (typeof XDomainRequest != "undefined") {
// 针对IE
xhr = new XDomainRequest();
xhr.open(method, url);
} else {
// 不支持CORS
xhr = null;
}
return xhr;
} // 辅助函数,用于解析返回的内容
function getTitle(text) {
return text.match('')[1];
} // 发送CORS请求
function makeCorsRequest() {
// bibliographica.org是支持CORS的
var url = 'http://bibliographica.org/'; var xhr = createCORSRequest('GET', url);
if (!xhr) {
alert('CORS not supported');
return;
} // 回应处理
xhr.onload = function() {
var text = xhr.responseText;
var title = getTitle(text);
alert('Response from CORS request to ' + url + ': ' + title);
}; xhr.onerror = function() {
alert('Woops, there was an error making the request.');
}; xhr.send();
}
服务端
一个CORS请求可能包含多个HTTP头,甚至有多个请求实际发送,这对于客户端的开发者来说通常是透明的。因为浏览器已经负责实现了CORS最关键的部分;但是服务端的后台脚本则需要我们自己进行处理,因此我们还需要了解到服务端到底从浏览器那里收到了怎样的内容。
先来看看流程图吧。
CORS分类
CORS可以分成两种:
- 简单请求
- 复杂请求
一个简单的请求大致如下:
- HTTP方法是下列之一
HEAD
GET
POST
- HTTP头包含
Accept
Accept-Language
Content-Language
Last-Event-ID
Content-Type
,但仅能是下列之一application/x-www-form-urlencoded
multipart/form-data
text/plain
任何一个不满足上述要求的请求,即被认为是复杂请求。一个复杂请求不仅有包含通信内容的请求,同时也包含预请求(preflight request)。
简单请求
为了搞清楚复杂请求与简单请求有何区别,我们首先来看看简单请求是怎样处理的。
JavaScript:
var url = 'http://alice.com/cors';
var xhr = createCORSRequest('GET', url);
xhr.send();
HTTP请求:
GET /cors HTTP/1.1
Origin: http://api.alice.com
Host: api.bob.com
Accept-Language: en-US
Connection: keep-alive
User-Agent: Mozilla/5.0...
简单请求的发送从代码上来看和普通的XHR没太大区别,但是HTTP头当中要求总是包含一个域(Origin)的信息。该域包含协议名、地址以及一个可选的端口。不过这一项实际上由浏览器代为发送,并不是开发者代码可以触及到的。
HTTP回应:
Access-Control-Allow-Origin: http://api.bob.com
Access-Control-Allow-Credentials: true
Access-Control-Expose-Headers: FooBar
Content-Type: text/html; charset=utf-8
在回应中,COR相关的项目全都是以“Access-Control-
”作为前缀的,其意义分列如下:
Access-Control-Allow-Origin
(必含)- 不可省略,否则请求按失败处理。该项控制数据的可见范围,如果希望数据对任何人都可见,可以填写“*”。Access-Control-Allow-Credentials
(可选) – 该项标志着请求当中是否包含cookies信息,只有一个可选值:true
(必为小写)。如果不包含cookies,请略去该项,而不是填写false
。这一项与XmlHttpRequest2
对象当中的withCredentials
属性应保持一致,即withCredentials
为true
时该项也为true
;withCredentials
为false
时,省略该项不写。反之则导致请求失败。Access-Control-Expose-Headers
(可选) – 该项确定XmlHttpRequest2
对象当中getResponseHeader()
方法所能获得的额外信息。通常情况下,getResponseHeader()
方法只能获得如下的信息:Cache-Control
Content-Language
Content-Type
Expires
Last-Modified
Pragma
当你需要访问额外的信息时,就需要在这一项当中填写并以逗号进行分隔。不过目前浏览器对这一项的实现仍然有一些问题,具体请见文尾的BUG一节。
复杂请求
如果仅仅是简单请求,那么即便不用CORS也没有什么大不了,但CORS的复杂请求就令CORS显得更加有用了。简单来说,任何不满足上述简单请求要求的请求,都属于复杂请求。比如说你需要发送PUT
、DELETE
等HTTP动作,或者发送Content-Type: application/json
的内容。
复杂请求表面上看起来和简单请求使用上差不多,但实际上浏览器发送了不止一个请求。其中最先发送的是一种“预请求”,此时作为服务端,也需要返回“预回应”作为响应。预请求实际上是对服务端的一种权限请求,只有当预请求成功返回,实际请求才开始执行。
JavaScript:
var url = 'http://alice.com/cors';
var xhr = createCORSRequest('PUT', url);
xhr.setRequestHeader(
'X-Custom-Header', 'value');
xhr.send();
预请求:
OPTIONS /cors HTTP/1.1
Origin: http://api.alice.com
Access-Control-Request-Method: PUT
Access-Control-Request-Headers: X-Custom-Header
Host: api.bob.com
Accept-Language: en-US
Connection: keep-alive
User-Agent: Mozilla/5.0...
预请求以OPTIONS
形式发送,当中同样包含域,并且还包含了两项CORS特有的内容:
Access-Control-Request-Method
– 该项内容是实际请求的种类,可以是GET、POST之类的简单请求,也可以是PUT
、DELETE
等等。Access-Control-Request-Headers
– 该项是一个以逗号分隔的列表,当中是复杂请求所使用的头部。
显而易见,这个预请求实际上就是在为之后的实际请求发送一个权限请求,在预回应返回的内容当中,服务端应当对这两项进行回复,以让浏览器确定请求是否能够成功完成。例如,刚才的预请求可能获得服务端如下的回应:
Access-Control-Allow-Origin: http://api.bob.com
Access-Control-Allow-Methods: GET, POST, PUT
Access-Control-Allow-Headers: X-Custom-Header
Content-Type: text/html; charset=utf-8
来看看预回应当中可能的项目:
Access-Control-Allow-Origin
(必含) – 和简单请求一样的,必须包含一个域。Access-Control-Allow-Methods
(必含) – 这是对预请求当中Access-Control-Request-Method
的回复,这一回复将是一个以逗号分隔的列表。尽管客户端或许只请求某一方法,但服务端仍然可以返回所有允许的方法,以便客户端将其缓存。Access-Control-Allow-Headers
(当预请求中包含Access-Control-Request-Headers
时必须包含) – 这是对预请求当中Access-Control-Request-Headers
的回复,和上面一样是以逗号分隔的列表,可以返回所有支持的头部。Access-Control-Allow-Credentials
(可选) – 和简单请求当中作用相同。Access-Control-Max-Age
(可选) – 以秒为单位的缓存时间。预请求的的发送并非免费午餐,允许时应当尽可能缓存。
一旦预回应如期而至,所请求的权限也都已满足,则实际请求开始发送。
实际请求:
PUT /cors HTTP/1.1
Origin: http://api.alice.com
Host: api.bob.com
X-Custom-Header: value
Accept-Language: en-US
Connection: keep-alive
User-Agent: Mozilla/5.0...
实际回应:
Access-Control-Allow-Origin: http://api.bob.com
Content-Type: text/html; charset=utf-8
如果预请求所要求的权限服务端不允许,那么服务端可以直接返回一个普通的HTTP回应,比如:
// ERROR - No CORS headers, this is an invalid request!
Content-Type: text/html; charset=utf-8
这样的返回因为不符合客户端的需求,因而客户端会直接将请求以失败计,虽然不是很美气,不过正符合我们的实际。此时如果客户端的onerror
事件有监听函数,那么将会触发,而浏览器的console窗口也会输出:
XMLHttpRequest cannot load http://api.alice.com. Origin http://api.bob.com is not allowed by Access-Control-Allow-Origin.
不过很可惜,浏览器并不会给出详细的错误情况,仅仅是告知我们出错而已。
安全问题
跨域请求始终是网页安全中一个比较头疼的问题,CORS提供了一种跨域请求方案,但没有为安全访问提供足够的保障机制,如果你需要信息的绝对安全,不要依赖CORS当中的权限制度,应当使用更多其它的措施来保障,比如OAuth2。
已知问题
CORS是W3C中一项较“新”的方案,以至于各大网页解析引擎还没有对其进行完美的实现。下面是截至2011年11月13日时的已知问题:
getAllResponseHeaders()
方法无法获取Access-Control-Expose-Headers
当中要求的信息。在Chrome/Safari当中,仅仅只有简单的头部能够读取,其他无法获取;在FireFox当中,无法获得任何信息。(FireFox Bugzilla/Webkit Bugzilla)- 在Safari当中,使用
GET
、POST
方法的复杂请求发送时没有发送预请求的环节。 onerror
触发时statusText
获取不到任何内容。- Opera截至11.60仍旧不支持CORS,但在12当中会支持(Opera Core Concerns – CORS goes mainline)。
阅读更多
利用CORS实现跨域请求--转的更多相关文章
- 利用CORS实现跨域请求(转载)
跨域请求一直是网页编程中的一个难题,在过去,绝大多数人都倾向于使用JSONP来解决这一问题.不过现在,我们可以考虑一下W3C中一项新的特性--CORS(Cross-Origin Resource Sh ...
- Java利用cors实现跨域请求
由于ajax本身实际上是通过XMLHttpRequest对象来进行数据的交互,而浏览器出于安全考虑,不允许js代码进行跨域操作,所以会警告 网站开发,在某些情况下需要用到跨域. 什么是跨域? 跨域,指 ...
- [转] 利用CORS实现跨域请求
[From] http://newhtml.net/using-cors/ 跨域请求一直是网页编程中的一个难题,在过去,绝大多数人都倾向于使用JSONP来解决这一问题.不过现在,我们可以考虑一下W3C ...
- Django使用jsonp和cors解决跨域请求问题
1.使用jsonp的方式解决跨域请求的问题 我启动两个django项目,然后使用的端口不一样,在项目1中通过ajax发请求给项目2,然后接受项目2发送过来的数据 先看项目1的ajax的代码 $(&qu ...
- 利用JSONP实现跨域请求
前言:有时候一忙起来就没了时间观念,原来我已经有十多天没写博客了.一直想做跨域方面的尝试,无奈最近准备校招没时间动动手.今天就先讲讲JSONP吧,昨晚还在研究QQ空间日志里面网络图片的问题呢,我发现日 ...
- 利用Jquery处理跨域请求
在项目制作过程中,可能会用到ajax来提高用户体验,这里终于研究出来,利用jquery来进行跨域请求,在用$.getJSON这个方法时,前台页面中需这样写 $.getJSON(“需要提交处理的url? ...
- 08: CORS实现跨域请求
目录: 1.1 cors跨域请求介绍 1.2 使用tornado实现 复杂请求 1.3 Django中使用django-cors-headers解决跨域问题 1.1 cors跨域请求介绍返回顶部 1. ...
- 利用Filter解决跨域请求的问题
1.为什么出现跨域. 很简单的一句解释,A系统中使用ajax调用B系统中的接口,此时就是一个典型的跨域问题,此时浏览器会出现以下错误信息,此处使用的是chrome浏览器. 错误信息如下: jquery ...
- SpringBoot:CORS处理跨域请求的三种方式
一.跨域背景 1.1 何为跨域? Url的一般格式: 协议 + 域名(子域名 + 主域名) + 端口号 + 资源地址 示例: https://www.dustyblog.cn:8080/say/Hel ...
随机推荐
- gRPC全局异常捕获
gRPC全局异常捕获 引 一般的.net项目比如ASP.NET.控制台程序.Windows服务.桌面程序等都会有framework自带的全局异常捕获机制.ASP.NET的ExceptionFilter ...
- Qt之QStackedLayout
简述 QStackedLayout继承自QLayout. QStackedLayout类提供了多页面切换的布局,一次只能看到一个界面. QStackedLayout可用于创建类似于QTabWidget ...
- ubuntu 14.04 桌面版关闭图形界面
ubuntu 14.04 桌面版关闭图形界面 问题: 怎样将ubuntu14.04设置为文本模式启动? 解决方式: 改动改GRUB 的配置文件(不建议直接改 grub.conf) $sudo vim ...
- IIS访问站点,出现connection refused
排查后,发现是因为使用了代理导致的. 需要设置 Don't use the proxy server for local addresses.
- spark 从RDD createDataFrame 的坑
Scala: import org.apache.spark.ml.linalg.Vectors val data = Seq( (7, Vectors.dense(0.0, 0.0, 18.0, 1 ...
- zzulioj--1746--三角形面积(几何水题)
1746: 三角形面积 Time Limit: 1 Sec Memory Limit: 128 MB Submit: 100 Solved: 31 SubmitStatusWeb Board De ...
- Linux基础04
** Linux基本操作常用命令(四) ** Linux系统管理命令 1.top:查看系统资源,每隔三秒刷新一次,按q:退出浏览状态 2.free:查看内存信息,-m,以MB单位显示 3.netsta ...
- PostgreSQL Replication之第三章 理解即时恢复(4)
3.4 重放事务日志 一旦我们创建了一个我们自己的初始基础备份,我们可以收集数据库创建的XLOG.当时间到时,我们可以使用所有这些XLOG 文件并执行我们所期望的恢复进程.这就像本节描述的一样工作. ...
- springMVC学习笔记_转载(一)-----springMVC原理
阅读目录 一.什么是springmvc 二.mvc在b/s系统下的应用 三.SpringMVC框架介绍 回到顶部 一.什么是springmvc springMVC是spring框架的一个模块,spri ...
- 51Nod 3的幂的和(扩展欧几里德求逆元)
求:3^0 + 3^1 +...+ 3^(N) mod 1000000007 Input 输入一个数N(0 <= N <= 10^9) Output 输出:计算结果 Input示例 3 O ...