同源策略
同源要求
- 协议相同
- 域名相同
- 端口相同
1 | http://www.example.com/dir2/other.html:同源 |
限制范围
- cookie、localstorage和 indexDB无法读取
- DOM 无法获得
- Ajax 请求不能发送
1.JSONP
JSONP(JSON with Padding) 是一种跨域请求方式。主要原理是利用了script标签可以跨域请求的特性,由其 src 属性发送请求到服务器,服务器返回 JavaScript代码,浏览器接受响应,然后就直接执行了,这和通过 script 标签引用外部文件的原理是一样的。
JSONP由两部分组成:回调函数和数据,回调函数一般是在浏览器控制,作为参数发往服务器端(当然,你也可以固定回调函数的名字,但客户端和服务器端的名称一定要一致)。当服务器响应时,服务器端就会把该函数和数据拼成字符串返回。
JSONP的请求过程:
- 请求阶段:浏览器创建一个
script标签,并给其src赋值(类似http://example.com/api/?callback=jsonpCallback)。 - 发送请求:当给
script的src赋值时,浏览器就会发起一个请求。 - 数据响应:服务端将要返回的
数据作为参数和函数名称拼接在一起(格式类似”jsonpCallback({name: 'abc'})”)返回。当浏览器接收到了响应数据,由于发起请求的是script,所以相当于直接调用jsonpCallback方法,并且传入了一个参数。
1 | // foo 函数将会被调用 传入后台返回的数据 |
允许用户传递一个callback参数给服务端,然后服务端返回数据时会将这个callback参数作为函数名来包裹住JSON数据,这样客户端就可以随意定制自己的函数来自动处理返回数据了。
使用注意
基于JSONP的实现原理,所以JSONP只能是“GET”请求,不能进行较为复杂的POST和其它请求,所以遇到那种情况,就得参考下面的CORS解决跨域了(所以如今它也基本被淘汰了)

所以JSONP的原理就是:
- 创建一个script标签,这个script标签的src就是请求的地址;
- 这个script标签插入到DOM中,浏览器就根据src地址访问服务器资源
- 返回的资源是一个文本,但是因为是在script标签中,浏览器会执行它
- 而这个文本恰好是函数调用的形式,即函数名(数据),浏览器会把它当作JS代码来执行即调用这个函数
- 只要提前约定好这个函数名,并且这个函数存在于window对象中,就可以把数据传递给处理函数。
2.Hash
场景:当前页面 A 通过iframe或frame嵌入了跨域的页面 B
1 | // 在A中伪代码如下: |
在A页面中改变B的url中的hash值,B不会刷新,但是B可以用过window.onhashchange事件监听到hash变化。
3.postMessage
HTML5
1 | // 窗口A(http:A.com)向跨域的窗口B(http:B.com)发送信息 |
Bwindow其他窗口的一个引用,比如iframe的contentWindow属性
通过窗口的origin属性来指定哪些窗口能接收到消息事件。
4.WebSocket跨域通信
1 | var ws = new WebSocket('wss://echo.websocket.org'); |
5.document.domain
浏览器有一个同源策略,其限制之一是不能通过ajax的方法去请求不同源中的文档。第二个限制是浏览器中不同域的框架之间是不能进行js的交互操作的。不同的框架之间是可以获取window对象的,但却无法获取相应的属性和方法。
这个时候,document.domain就可以派上用场了,我们只要把http://www.zj.cn/1.html 和 zj.cn/2.html 这两个页面的document.domain都设成相同的域名就可以了。但要注意的是,document.domain的设置是有限制的,我们只能把document.domain设置成自身或更高一级的父域,且主域必须相同。
修改document.domain的方法只适用于不同子域的框架间的交互。
6.CORS
CORS是一个W3C标准,全称是”跨域资源共享”(Cross-origin resource sharing)。 它允许浏览器向跨源服务器,发出XMLHttpRequest请求,从而克服了AJAX只能同源使用的限制。
CORS需要浏览器和服务器同时支持。目前,所有浏览器都支持该功能,IE浏览器不能低于IE10。IE8+:IE8/9需要使用XDomainRequest对象来支持CORS。
浏览器一旦发现AJAX请求跨源,就会自动添加一些附加的头信息,有时还会多出一次附加的请求,但用户不会有感觉。 因此,实现CORS通信的关键是服务器。只要服务器实现了CORS接口,就可以跨源通信。
两种请求:
简单请求
- 请求方式为HEAD、POST或者GET
- http头信息不超出以下字段:Accept、Accept-Language、Content-Language、Last-Event-ID、 Content-Type(限于三个值:application/x-www-form-urlencoded、multipart/form-data、text/plain)
非简单请求
需要通过预请求验证后然后才能发送
简单请求
在头信息之中,增加一个Origin字段。
Origin字段用来说明,本次请求来自哪个源(协议 + 域名 + 端口)。服务器根据这个值,决定是否同意这次请求。
如果Origin指定的域名在许可范围内,服务器返回的响应,会多出几个头信息字段。
- Access-Control-Allow-Origin :该字段是必须的。它的值要么是请求时Origin字段的值,要么是一个*,表示接受任意域名的请求
- Access-Control-Allow-Credentials: 该字段可选。它的值是一个布尔值,表示是否允许发送Cookie。默认情况下,Cookie不包括在CORS请求之中。设为true,即表示服务器明确许可,Cookie可以包含在请求中,一起发给服务器。这个值也只能设为true,如果服务器不要浏览器发送Cookie,删除该字段即可。
- Access-Control-Expose-Headers:该字段可选。CORS请求时,XMLHttpRequest对象的getResponseHeader()方法只能拿到6个基本字段:Cache-Control、Content-Language、Content-Type、Expires、Last-Modified、Pragma。如果想拿到其他字段,就必须在Access-Control-Expose-Headers里面指定。
如果要把cookie发送到服务器,一方面要服务器同意,指定Access-Control-Allow-Credentials字段,另一方面,开发者必须在AJAX请求中打开withCredentials属性。
非简单请求
非简单请求是那种对服务器有特殊要求的请求,比如请求方法是PUT或DELETE,或者Content-Type字段的类型是application/json。
非简单请求的CORS请求,会在正式通信之前,增加一次HTTP查询请求,称为”预检”请求(preflight)。
浏览器先询问服务器,当前网页所在的域名是否在服务器的许可名单之中,以及可以使用哪些HTTP动词和头信息字段。只有得到肯定答复,浏览器才会发出正式的XMLHttpRequest请求,否则就报错。
1 | var url = 'http://api.alice.com/cors'; |
浏览器发现,这是一个非简单请求,就自动发出一个”预检”请求,要求服务器确认可以这样请求。下面是这个”预检”请求的HTTP头信息。
1 | OPTIONS /cors HTTP/1.1 |
“预检”请求用的请求方法是OPTIONS,表示这个请求是用来询问的。头信息里面,关键字段是Origin,表示请求来自哪个源。
除了Origin字段,”预检”请求的头信息包括两个特殊字段。
- Access-Control-Request-Method:该字段是必须的,用来列出浏览器的CORS请求会用到哪些HTTP方法,上例是PUT。
- Access-Control-Request-Headers:该字段是一个逗号分隔的字符串,指定浏览器CORS请求会额外发送的头信息字段,上例是X-Custom-Header
服务器收到”预检”请求以后,检查了Origin、Access-Control-Request-Method和Access-Control-Request-Headers字段以后,确认允许跨源请求,就可以做出回应。
1 | HTTP/1.1 200 OK |
一旦服务器通过了”预检”请求,以后每次浏览器正常的CORS请求,就都跟简单请求一样,会有一个Origin头信息字段。服务器的回应,也都会有一个Access-Control-Allow-Origin头信息字段。
Koa-parser
- URL编码 application/x-www-form-urlencoded
- JSON编码 application/json
URL编码方式适合处理简单的键值对数据,并且很多框架的Ajax中的Content-Type默认值都是它,但是对于复杂的嵌套对象就不太好处理了,这时就需要JSON编码方式大显身手了。通过客户端JSON.stringify编码和服务端JSON.parse解码。除了上述两种字符串编码方式,koa-bodyparser还支持不采用任何字符串编码方式的普通字符串。
三种字符串编码的处理方式由【co-body】模块提供,koa-bodyparser中通过判断当前Content-Type类型,调用不同的处理方式,将获取到的结果挂载在ctx.request.body。
如果需要支持 cookies
- Access-Control-Allow-Origin 不能设置为 *
- 并且 Access-Control-Allow-Credentials 需要设置为 true
- (注意前端请求需要设置 withCredentials = true)
当 method = OPTIONS 时, 属于预检(复杂请求), 当为预检时, 可以直接返回空响应体, 对应的 http 状态码为 204
通过 Access-Control-Max-Age 可以设置预检结果的缓存, 单位(秒)
通过 Access-Control-Allow-Headers 设置需要支持的跨域请求头
通过 Access-Control-Allow-Methods 设置需要支持的跨域请求方法