同源政策

Ajax请求限制:

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

什么是同源:

如果两个页面拥有相同的协议、域名和端口,那么这两个页面就属于同一个源,其中只要有一个不相同,就是不同源。
http://www.example.com/dir/page.html

http://www.example.com/dir2/other.html:同源

http://example.com/dir/other.html:不同源(域名不同)

http://v2.www.example.com/dir/other.html:不同源(域名不同)

http://www.example.com:81/dir/other.html:不同源(端口不同)

https://www.example.com/dir/page.html:不同源(协议不同)

同源政策的目的:

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

随着互联网的发展,同源政策也越来越严格,在不同源的情况下,其中有一项规定就是无法向非同源地址发送Ajax请求,如果请求,浏览器就会报错。

以下有几种跨域请求的方法:

1. 使用JSONP解决同源限制问题

jsonp是json with padding的缩写,它不属于Ajax请求,但它可以模拟Ajax请求。

注意:JSONP不是Ajax,只是模拟Ajax发送数据 

① 将不同源的服务器端请求地址写在script标签的src属性中

在<script>的src属性中是不受同源政策的限制的,也就是说它可以写非同源的网站

<script src="www.example.com"></script>
<script src=“https://cdn.bootcss.com/jquery/3.3.1/jquery.min.js"></script>

② 服务器端响应数据必须是一个函数的调用, 真正要发送给客户端的数据需要作为函数调用的参数

const data = ‘fn({name: "张三", age: "20"})‘;
res.send(data);

③ 在客户端全局作用域下定义函数fn

function fn (data) { }

④ 在fn函数内部对服务器端返回的数据进行处理

function fn (data) { console.log(data); }

注意:jsonp解决方案中的请求属于get请求,因为它是通过script标签中的src属性发送的请求,所以它传递的参数也是get请求参数,具体的参数拼接在请求地址的后面。 

JSONP代码优化:

(1)客户端需要将函数名称传递到服务器端。

客户端写的函数如何在服务器端调用呢?

注意,客户端的这个函数是全局函数,而且必须要写在最前面。

<script>
    function fn (data) {
        console.log(‘客户端的fn函数被调用了‘)
        console.log(data);
    }
</script>
<!-- 将非同源服务器端的请求地址写在script标签的src属性中 -->
<script src="http://localhost:3001/test"></script>
// 服务器端调用客户端的fn函数
app.get(‘/test‘, (req, res) => {
    const result = ‘fn()‘;
    res.send(result);
});

(2)将script请求的发送变成动态请求。

但是上面的代码有三个问题:

① 客户端函数是立即调用的,但我们想要的效果是动态请求发送,当点击按钮之后,创建一个script标签,然后再将函数名传递到服务器端。

② 添加这个点击按钮之后出现了另外一个问题:

点击一次按钮,新增一个script标签,多次点击就会创建很多个script标签,但是我们只需要一个就够了。

解决方案:当script标签将请求地址中的内容加载完成以后,需要将它从body内部删除掉

③ 服务器端返回的函数调用名称必须与客户端定义的函数名称保持一致。如果客户端的函数名称需要修改,则服务器端的函数名称也必须要跟着修改,开发人员的沟通成本就比较高。

解决方案:只需要将客户端函数的名字作为请求参数发送到服务器端,服务器端只需要接收到函数的名字,然后返回函数调用即可。

修改后的代码如下:

<button id="btn">点我发送请求</button>
<script>
    function fn2 (data) {
        console.log(‘客户端的fn函数被调用了‘)
        console.log(data);
    }
</script>
<script type="text/javascript">
    // 获取按钮
    var btn = document.getElementById(‘btn‘);
    // 为按钮添加点击事件
    btn.onclick = function () {
        // 创建script标签
        var script = document.createElement(‘script‘);
        // 设置src属性
        script.src = ‘http://localhost:3001/better?callback=fn2‘;
        // 将script标签追加到页面中
        document.body.appendChild(script);
        // 为script标签添加onload事件
        script.onload = function () {
            // 将body中的script标签删除掉
            document.body.removeChild(script);
        }
    }
</script>
// 服务器端调用客户端的fn函数
app.get(‘/better‘, (req, res) => {
    // 接收客户端传递过来的函数的名称
    const fnName = req.query.callback;
    // 将函数名称对应的函数调用代码返回给客户端
    const result = fnName + ‘({name: "张三"})‘;
    res.send(result);
});

(3)封装jsonp函数,方便请求发送。

function jsonp (options) {
    // 动态创建script标签
    var script = document.createElement(‘script‘);
    // 为script标签添加src属性
    script.src = options.url;
    // 将script标签追加到页面中
    document.body.appendChild(script);
    // 为script标签添加onload事件, 等待script标签加载完之后再删除
    script.onload = function() {
        // 将body中的script标签删除掉
        document.body.removeChild(script);
    }     
}

封装jsonp方法有两个问题:

① 虽然上面已经封装了jsonp函数用于发送请求,但是在客户端,jsonp函数的其他地方,还需要另外定义一个全局函数,用于接收服务器端返回的数据,现在是发送一个请求要用到两个函数,而且两个函数是独立的,这样的话就破坏了jsonp函数的封装性,我们不能一眼就看出来哪个请求跟哪个函数是关联的。如果可以像Ajax封装函数一样,将用于接收服务器端返回来的数据的函数当作参数传递过去,即将处理请求函数变成success函数,这样的话函数的封装性就比较好。

但是这样就出现了另外两个问题:

  • 这个函数就不是全局函数了,服务器端在返回调用函数的时候就找不到这个函数了

   解决方案:要想办法把它变成一个全局函数,只需要将该函数挂载在window全局对象下面就可以了。

  • 这个函数就变成了匿名函数了,这样我们在向服务器端传递名字的时候该传递什么呢?

   解决方法:函数名字的问题同下面的问题②的解决方案,注意:函数名字不能是纯数字

② 在真实的情况中可能要发送多次请求,每一次请求都要对应自己的函数处理返回的结果,函数取名字也变成一个问题。如何解决函数名字的问题呢?只需要让函数的名字随机产生就可以了。

代码修改如下:

function jsonp (options) {
    // 动态创建script标签
    var script = document.createElement(‘script‘);
    // 拼接字符串的变量
    var params = ‘‘;

    for (var attr in options.data) {
        params += ‘&‘ + attr + ‘=‘ + options.data[attr];
    }
    
    // myJsonp0124741
    var fnName = ‘myJsonp‘ + Math.random().toString().replace(‘.‘, ‘‘);
    // 它已经不是一个全局函数了
    // 我们要想办法将它变成全局函数
    window[fnName] = options.success;
    // 为script标签添加src属性
    script.src = options.url + ‘?callback=‘ + fnName + params;
    // 将script标签追加到页面中
    document.body.appendChild(script);
    // 为script标签添加onload事件
    script.onload = function () {
        document.body.removeChild(script);
    }
}
// 获取按钮
var btn = document.getElementById(‘btn‘);
// 为按钮添加点击事件
btn.onclick = function () {
    jsonp({
        // 请求地址
        url: ‘http://localhost:3001/better‘,
        data: {
            name: ‘lisi‘,
            age: 30
        },
        success: function (data) {
            console.log(data)
        }
    })
}

(4)服务器端代码优化之res.jsonp方法。

express框架中提供了一个jsonp方法,jsonp方法内部干的其实就是注释的那些事情:

接收客户端传递过来的参数,将真实的数据转换为字符串再把它拼接起来,最终返回给客户端。

app.get(‘/better‘, (req, res) => {
    // 接收客户端传递过来的函数名称
    // const fnName = req.query.callback;
    // 将函数名称对应的函数调用代码返回给客户端
    // const data = JSON.stringify({name: "张三"});
    // const result = fnName + ‘(‘ + data + ‘)‘;
    // setTimeout(() => {
    //     res.send(result);
    // }, 1000);
    res.jsonp({name: ‘lisi‘, age: 20});
});

相关推荐