Ajax跨域请求

632 阅读5分钟

1、Ajax 请求限制

Ajax 只能向自己的服务器发送请求

比如现在有一个A网站,一个B网站,A网站中的HTML文件只能向A网站服务器发送Ajax请求,B网站中的HTML文件只能向B网站服务器发送Ajax请求,但是A网站时不能向B网站发送Ajax请求的,同理,B网站不能向A网站发送Ajax请求的

A网站先B网站发送Ajax请求,我们称之为跨域请求

如:在本地打开AJax文件先服务器请求内容

img

上面报错熟不熟悉,这就是因为发生了跨域请求导致的错误

2、什么是同源

浏览器为了安全问题,做出了同源策略的限制,即跨域请求默认是不被允许的

这里所说的同源是指:协议名、主机号、端口号 这三部分是一样。相同域之间的请求是不受限制的,而不同域之间不能互相请求的,这就是Ajax的同源策略

img

如果两个页面拥有相同的协议、域名和端口,那么两个页面就属于同一个源,其中只要有一个不同,就是不同源

img

同源策略是为了保护用户信息的安全,防止恶意的网站窃取数据,最初的同源策略是指A网站在客户端设置Cookie,B网站则不能访问的

3、解决跨域请求

Ajax默认是不允许进行跨域请求的,但是我们却时常进行非同源请求,摆在我们面前的就是解决跨域请求

解决跨域请求有4种方法:

  1. JSONP 解决跨域请求
  2. CORS 跨域资源共享
  3. 代理服务器
  4. 基于 <iframe> 标签

主要了解前两种

3.1 JSONP 解决跨域请求

JSONP 不属于Ajax请求,但是它可以模拟 Ajax 请求

有没有想过,我们平时在HTML页面中的 <img><link><script> 等标签中的src属性不受跨域请求的,所以我们是否也可以让 <script> 去请求服务器的JS文件来获取数据吗?

of course 当然

分为三步走:

  1. 将不同源的服务器请求地址写在<script> 标签的src属性中
<script src="http://127.0.0.1:3000/test"></script>
  1. 服务端响应数据必须是一个函数的调用,真正要发送给客户端的数据需要作为函数调用的参数
const data = "getData({namer: "周深", song: "大鱼"})";
res.rend(data);  // 发送数据
  1. 全局作用域下定义函数(两函数名要一样)
function getData(options) {
	alert(options.namer + '唱了一首' + options.song)
}

栗子:

客户端:

<body>
    <script>
        function getData(data) {
            alert(data.namer + '唱了一首' + data.song)
        }
    </script>
    <script src="http://127.0.0.1:3000/test"></script>
</body>

服务器端:

app.get('/test', function(req, res) {
    let data = 'getData({namer: "周深", song: "大鱼"})'
    res.send(data)
})

JSONP 优化

第一种:把函数名通过URL传给服务端

我们可以把本地函数通过URL传参 ?callback=getData 的方式,让服务器知道本地函数的名字

这样的好处就是客户端修改函数名,服务器就不受影响

<script>
    function getData(data) {
    	alert(data.namer + '唱了一首' + data.song)
	}
</script>
<script src="http://127.0.0.1:3000/test?callback=getData></script>
app.get('/test', function(req, res) {
    res.jsonp({ namer: '薛之谦', song: '演员' })
})

jsonp 函数已经帮我们做好了处理,会自动获取参数中callback 的值作为函数,所以服务端只需要传递数据即可

第二种:将script请求的变成动态请求

<button>按钮</button>

<script>
    function getData(data) {
    	alert(data.namer + '唱了一首' + data.song)
	}
</script>

<script type="text/javascript">
    let btn = document.querySelector('button');
	btn.onclick = function() {
        let script = document.createElement('script');
        script.src = 'http://127.0.0.1:3000/test';
        document.body.appendChild(script);
        // 为script添加onload事件,过河拆桥
        script.onload = function() {
            document.body.removeChild(script)
        };
	};
</script>

发送请求的时候动态创建script标签发送,结束之后随即把它删除了

但是每次我们都要写这么一坨代码吗?说这句话的时候就知道又要疯转了

下面我们来封装一个jsonp函数吧

function jsonp(options) {
    // 创建标签
    let script = document.createElement('script')

    // 拼接字符串
    let params = '';
    for (let key in options.data) {
        params += '&' + key + '=' + options.data[key]
    }

    // 创建随机函数,toString将数字转换字符串去除小数点.
    let fnName = 'myFn' + Math.random().toString().replace('.', '');
    // fnName不是全局函数,利用window将其变成函数
    window[fnName] = options.success;

    script.src = options.url + '?callback=' + fnName + params;
    document.body.appendChild(script)

    // 为script标签添加onload事件
    script.onload = function() {
        // 删除掉script标签
        document.body.removeChild(script)
    }
}

调用jsonp(需要自行导入jsonp函数)

<body>
    <button id="xzq">薛之谦</button>
    <button id="zs">周深</button>
    <script type="text/javascript">
        let btn1 = document.querySelector('#xzq');
        let btn2 = document.querySelector('#zs');
        
        btn1.onclick = function() {
            jsonp({
                url: 'http://127.0.0.1:3000/test',
                data: {
                    namer: '薛之谦',
                    song: '刚刚好'
                },
                success: function(data) {
                    alert(data.namer + '--' + data.song);
                }
            })
        };
        
        btn2.onclick = function() {
            jsonp({
                url: 'http://127.0.0.1:3000/test',
                data: {
                    namer: '周深',
                    song: '大鱼'
                },
                success: function(data) {
                    alert(data.namer + '--' + data.song);
                }
            })
        };
    </script>
</body>

后台:

app.get('/test', function(req, res) {
    res.jsonp(req.query)
})

3.2 CORS 跨域资源共享

CORS 全称为 Cross-origin Rescourse Sharing,即跨域资源共享,是W3C推出的一种新的机制。

它允许浏览器向跨域服务器发送Ajax请求,克服了Ajax 只能同源使用的限制

img

所以,当我们在使用CORS处理跨域请求时,浏览器判断这是一个跨域请求,会自动帮我们处理好相应的跨域请求配置,添加一些附加头部信息,而我们要做的仅仅是在服务器端判断是否允许这个域访问

img

1、简单请求

简单请求必须满足三个请求:

  1. 请求方式为GETPOSTHEAD
  2. 数据类型Content-Type只能是application/x-www-form-urlencodedmultipart/form-datatext/plain
  3. 不使用自定义的请求头

所以,如果是简单请求,可以设置一个允许跨域请求的头信息

在服务端设置:

app.post('/cache', function(req, res) {
    res.setHeader('Access-Control-Allow-Origin', 'http://localhost:8080')
    res.send('你要一直走,直到灯火通明')
})

img

允许这个 http://localhost:8080 域请求,如果我们想要任何域能够请求,只需要换成 * 即可

res.setHeader('Access-Control-Allow-Origin', '*')

现在任何所属域的Ajax来请求这个服务器,都会被赋予访问权限,都可以正常响应数据了

2、预请求

域请求是一种相对比较复杂的一些的请求,当出现以下条件时,就会被当做预请求

  1. 请求方式是 GETPOSTHEAD以外的方式,比如:PUTDELETE
  2. 使用 POST请求,但数据类型是 application/xml 或者 text/xml 的XML数据类型
  3. 使用自定义的请求头信息

3、附带凭证信息的请求

XMLHttpRequest 对象在发送请求的同时会发送凭证(Cookie 和 验证信息),但是跨域请求并不会发送

所以想要传递Cookie给服务器,就要在请求头里面设置允许发送凭证信息。客户端和服务端都需要设置

感兴趣的话可以继续探索......

总结

  1. Ajax的同源策略就是说只有协议、主机号、端口的都一样的域才能进行请求,而这样做的目的就是为了安全,防止网站信息被窃取
  2. 解决跨域请求的方式有很多,JSONP 请求的是可执行脚本,适合用于请求我们自己的服务器
  3. CORS 是H5的一种新特性,基本支持所有的请求,也是我们常用的,缺点就是低版本的浏览器会有兼容性问题