上一篇: http请求下一篇: js总结以及一些经典代码片段

常见的跨域解决方案

一、跨域问题的产生与常见的跨域类型

1.跨域问题产生

为了隔离潜在恶意文件,浏览器的同源策略不允许在不同域名下进行数据交换,这是跨域问题产生的根本原因.

2.常见的跨域类型

协议不同,域名不同,端口不同,满足任意一种条件都属于跨域

以下表格列举了其他域名对比http://abc.com是否跨域

协议 域名 端口 路径 是否跨域
http abc.com 80 test.js 否(请求资源路径不影响是否跨域)
https abc.com 80 test.js 是(协议不同,跨域)
http def.com 80 test.js 是(域名不同,跨域)
http abc.com 8080 test.js 是(端口不同,跨域)
http a.abc.com 80 test.js 是(域名不同,跨域)
http abc.com 80 a/test.js 否(请求资源路径不影响是否跨域)

二、跨域问题的解决方案

常见的解决方案

  1. jsonp
  2. cors
  3. nginx
  4. iframe
  5. postMessage

1.jsonp

由于同源策略的影响,ajax请求受到浏览器的限制不能跨域交换数据.在实际开发过程中我们发现,有些标签加载资源不受浏览器的限制,比如img标签,我们可以给src设置任意的路径(前提是目标资源没有设置域名来源过滤),就可以在页面中加载图片,script标签也是如此,jsonp就是运用了script设置src属性加载资源不受浏览器限制的原理.以下是jsonp的具体实现:

​ 1.在当前html中声明一个cb函数;

​ 2.创建一个script标签,给script设置src属性,值为目标资源位置,并且将步骤1声明的cb作为请求参数;

​ 3.服务器接收到来自步骤2的请求,把数据data作为cb的第一个参数,将cb(data)返回;

​ 4.html接收到返回的文件资源,因为是js脚本,将其执行(此时执行的就是cb函数,第一个参数为返回的数据data),至此,jsonp跨域结束.

简单的jsonp请求前端示例代码

//假设当前页面的网址为http://abc.com
<html>
    <head></head>
    <body>
        <script>
            var cb = function (p) {
                alert(p)
            }
        </script>
        <script src="http://def.com?callback=cb"><script>
    </body>
</html>

node服务端代码

var express = require('express');
var app = express();
var fs = require('fs');

app.get('/a.js', function (req, res) {
    var str = req.query.callback + '(' + 123+ ')'
    res.send(str)
})
app.listen(80, function () {
    console.log('listening:80')
})

评价:jsonp跨域是一个兼容性很好的解决方案,不足在于,只支持get方式请求,无法满足在实际开发复杂的业务.

2.cors

cors是cross origin resource sharing的简写,是一种常用的跨域解决方案.cors分为简单请求和非简单请求两种.

同时满足以下两个条件的请求为简单请求

2.1简单请求

(1)请求方法是以下三种

(2)请求头的信息不超过以下几种

2.2非简单请求

​ 非简单请求和简单请求最重要的区别在于,非简单请求在正式发出cors请求前会有一次预检请求(即option请求).

哪些是非简单请求?

答:比如请求方法:put,delete,或者Content-Type:application/json,就是非简单请求,更直接点,除了简单请求的cors,剩下的就是非简单请求.

为什么需要一次预检请求

答:浏览器先询问服务器,当前网页的域名是否在服务器允许访问的白名单内,以及浏览器可以携带哪些头信息,如果当前网页不在服务器的白名单内,就会抛出错误.

非简单请求的测试情况

​ 如果额外设置了请求头,服务端必须在Access-Control-Request-Headers中设置该请求头,否则预检请求时浏览器将抛出错误,示例:

//前端代码 当前网址http://abc.com
var param = {a: 'a'}
ajax({
    url: 'http://def.com/cors/simple',
    params: param,
    method: 'POST',
    contentType: 'application/json',    //设置非简单请求
    success: function (res) {
        console.log(123);
    }
})
//node代码  http://def.com
var express = require('express')
var app = express()
app.use(function (req, res, next) {
    res.set({
        'Access-Control-Allow-Origin': 'http://abc.com'
        // 'Access-Control-Allow-Headers': 'Content-Type'
    })
    next()
})
app.post('/cors/simple', function (req, res) {
    res.send('miki')
})

​ 如果请求采用的是特殊方法,服务器必须在Access-Control-Request-Method中允许相应的方法,否则预检请求时浏览器将抛出错误,此处代码和上例相似,就不贴代码了.

相关字段说明:

Access-Control-Allow-Origin:和简单请求的规则一样.

Access-Control-Allow-Credentials:和简单请求的规则一样.

Access-Control-Allow-Methods:它的值是用逗号分隔的字符串,表明服务器支持的所有请求方法.

Access-Control-Allow-Headers:它的值是用逗号分隔的字符串,表明服务器支持的所有头信息(POST,GET方法默认支持的,可以不写).

Access-Control-Max-Age:这个响应首部表示 preflight request (预检请求)的返回结果(即 Access-Control-Allow-MethodsAccess-Control-Allow-Headers 提供的信息) 可以被缓存多久.

cors跨域总结:

(1)Access-Control-Allow-Origin是cors跨域必须要设置的字段,只能设置'*'或者具体的域名;

(2)Access-Control-Allow-Credentials用于设置是否携带cookie,前端配合设置XMLHttpRequest的withCredentials属性为true,此时携带的cookie是与服务器域名一致的cookie,忽略网页域名下的cookie;

(3)预检请求是非简单请求的一个重要特点,浏览器根据服务器返回的响应头字段决定是发起正式请求还是抛出错误;

(4)含有特殊方法或者特殊请求头的非简单请求必须设置相应的Access-Control-Allow-Methods或者Access-Control-Allow-Headers.

3.nginx

运用代理服务器处理跨域问题,首先要明白,跨域问题是浏览器的同源策略导致的,服务器本身不存在跨域的说法,所以,我们可以设想,如果请求越过浏览器,那么就不会有跨域的限制.nginx解决跨域问题就是基于这样的想法.

比如,当前网页的地址为http://abc.com,请求http://def.com:8888/nginx,代码如下:

//前端 
$.ajax({
    url: '/nginx',
    data: {a: 'a'},
    method: 'post',
    dataType: 'json',
    success: function (res) {
        console.log(res)
    }
})
//nginx配置
server {
    listen          80;
    server_name     abc.com;
    root            /Users/xingxiaoqi/Documents/project/study/miki-test/nginx;
    index           index.html index.htm;

    location /nginx {
            proxy_pass  http://def.com:8888;
    }

}
//node服务
var express = require('express')
var app = express()
app.post('/nginx', function (req, res) {
    res.send('nginx+++')
})
app.listen(8888, function () {
    console.log('listening:8888')
})

关于cookie和请求参数,nginx不更改请求携带的任何数据,会按照原样将cookie和参数传到目标服务器.

4.iframe

4.1 document.domain

假设我的当前页面地址是:http://abc.com,内嵌一个iframe,src=http://www.abc.com,iframe和其父元素跨域,那么此时,利用contentWindow得到的iframe的window对象不可操作.如果需要交换数据,因为主域相同,可以考虑通过document.domain使父页面和iframe的domain一样,这样就可以自由地交换数据,要特别注意,domain不能随意设置,只能把document.domain设置成自身或更高一级的父域.

5.postMessage

targetWindow.postMessage(message,targetOrigin,[transfer]) 方法是html5新引进的特性,可以使用它来向其它的window对象发送消息.

targetWindow:接受消息的window对象;

message:发送到其他window的数据;

targetOrigin:指定哪些窗口能接收到消息事件可以是'*',或者url;

transfer:是一串和message 同时传递的 Transferable 对象. 这些对象的所有权将被转移给消息的接收方,而发送一方将不再保有所有权;

使用postMessage方法,还必须给targetWindow注册message事件

window.addEventListener('message', function (e) {
    alert(e.data)
})

回调函数的第一参数是事件对象,有以下几个属性:

data:从其他window对象传递过来的对象;

origin:调用 postMessage 时消息发送方窗口的origin;

source:对发送消息的窗口对象的引用; 可以使用此属性在具有不同origin的两个窗口之间建立双向通信

页面http://abc.com,内嵌一个src=http://www.abc.com的iframe,使用postMessage传递数据,代码:

//页面
var inner = document.getElementById('inner')   //假设iframe的id="inner"
var win = inner.contentWindow
inner.onload = function () {
    win.postMessage('this is from http://abc.com', '*')
}

//iframe
window.addEventListener('message', function (e) {
    alert(e.data)
})

//------建立双向数据传输-----
//页面
var inner = document.getElementById('inner')   //假设iframe的id="inner"
var win = inner.contentWindow
window.addEventListener('message', function (e) {
    alert(e.data)
})
inner.onload = function () {
    win.postMessage('this is from http://abc.com', '*')
}
//iframe
window.addEventListener('message', function (e) {
    alert(e.data)
    e.source.postMessage('this is from http://www.abc.com ')
})

至此.跨域常见的几种解决方案写完了,整理以上5种解决方案,总结得到以下几点:

(1)jsonp是一种兼容性良好的解决方案,但是只能用get方法,并且没法知道请求的到哪个阶段,局限性大;

(2)简单请求的cors后端配置即可,非简单请求前后端都要配置,请求携带的请求头,请求方法,cookie传递都有限制;

(3)nginx代理最方便,没有传输数据限制,是一种极佳的方案;

(4)iframe跨域局限于主域名相同;

(5)postMessage跨域只能在已知window对象的情况下才能跨域发送数据,局限性大.

上一篇: http请求下一篇: js总结以及一些经典代码片段