Ajax的学习笔记2——跨域问题

Ajax请求限制

Ajax只能向自己的服务器发送请求,这是因为浏览器的同源策略限制。

同源

什么是同源

如果两个 URL 的 protocol、port (如果有指定的话)和 host 都相同的话,则这两个 URL是同源。这个方案也被称为“协议/主机/端口元组”,或者直接是 “元组”。
在这里插入图片描述

图文来源:https://developer.mozilla.org/zh-CN/docs/Web/Security/Same-origin_policy

同源的目的

同源政策是为了保护用户信息的安全,防止恶意的网站窃取数据。在不同源的情况下,其中一项规定就是无法向非同源地址发送Ajax请求,如果请求,浏览器就会报错。
【注意:非同源地址发送Ajax请求,请求发送出去了,但是浏览器拒绝接受响应,所以失败了。】

跨域问题解决方案

问题的提出

分别用Express搭建两个简单的服务

const express = require('express');
const path = require('path')
const app = express();
app.use(express.static(path.join(__dirname, 'public')))

服务器1:

app.get('/', (req, res) => {
    res.send('Hello Server1')
})
app.listen(3000)
console.log('服务器1启动成功');

服务器2:

app.get('/', (req, res) => {
    res.send('Hello Server2')
})
app.listen(3001)
console.log('服务器2启动成功');

结果如图:
在这里插入图片描述
现在想要在客户端1(对应服务器1)中使用Ajax来访问服务器2,会出现如下错误
在这里插入图片描述
该问题即为上述所说浏览器的同源(这里是端口号不同)策略限制,可通过以下方法来解决这个问题。

JSONP

什么是JSONP?

JSONP(json with padding),padding有填充的意思,翻译过来就是“将JSON数据作为填充内容”。
其核心主要是:在服务器端将JSON数据作为函数的参数,将JSON数据填充到函数当中。

JSONP的实现

可以使用JSONP来解决浏览器的同源策略限制。JSONP是绕过浏览器同源政策的限制,向非同源服务器端发送请求。
【tips:JSONP不属于Ajax请求,但它可以模拟Ajax请求】

实现JSONP步骤

  1. 将不同源的服务器请求地址写在script标签的src属性中(该属性不受同源策略的限制)
    <script src="http://localhost:3001/test"></script>
  1. 服务器端响应数据必须是一个函数的调用(真正要发送给客户端的数据需要作为函数调用的参数)
app.get('/test', (req, res) => {
    const result = 'fn({msg:"Hello Server2"})'
    res.send(result)
});
  1. 在客户端全局作用域下定义函数(script标签前)【目的是为了在函数调用时找到函数定义的部分】
        function fn(data) {}
  1. 在fn函数内部对服务器端返回的数据进行处理
        function fn(data) {
            console.log(data);
        }

简单实现

客户端
    <script>
        function fn(data) {
            console.log(data);
        }
    </script>
    <script src="http://localhost:3001/test"></script>
服务器
app.get('/test', (req, res) => {
    const result = 'fn({msg:"Hello Server2"})'
    res.send(result)
});
结果

客户端1成功向服务器端2发送请求,并对响应回来的数据进行输出处理。
在这里插入图片描述

优化实现

上述仅为JSONP的简单理解实现,在真正使用过程中是非常不便的,现针对上述简单实现中可能出现的问题,分步骤进行优化。

  • 函数名称的传递
  • 动态请求
  • 封装JSONP函数
  • 服务端优化
客户端
    <!-- 动态请求 -->
    <button id="btn">点我发送请求</button>
 // 获取按钮元素
        var btn = document.getElementById('btn');
        // 为按钮添加点击事件
        btn.onclick = function () {
            jsonp({
                url: 'http://localhost:3001/test',
                data: {},
                success: function (data) {
                    console.log(data)
                }
            })
        }

        function jsonp(options) {
            // 创建script标签
            var script = document.createElement('script');
            // 拼接字符串的变量
            var params = '';
            for (var attr in options.data) {
                params += '&' + attr + '=' + options.data[attr];
            }
            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 () {
                // 将body中的script标签删除调掉
                document.body.removeChild(script);
            }
        }
服务端
app.get('/test', (req, res) => {
    // 接收客户端传递过来的函数名称
    const fnName = req.query.callback;
    // 将函数名称对应的函数调用代码返回给客户端
    const data = JSON.stringify({
        name: "kk",
        age: 20
    })
    const result = fnName + '(' + data + ')'
    res.send(result)
})

上述代码可以直接使用【res.jsonp():传送JSONP响应】来实现,两者效果是一样的

app.get('/test', (req, res) => {
    res.jsonp({
        name: "kk",
        age: 20
    })
})

【tips:由上述JSONP实现过程可以知道,JSOP仅支持GET方法。】

CORS

什么是CROS?

CROS(Cross-origin resource sharing),即跨域资源共享,它允许浏览器向跨域服务器发送Ajax请求,克服了Ajax只能同源使用的限制。

CROS的实现

 <button id="btn">点击发送请求</button>

客户端

        var btn = document.getElementById('btn')
        btn.onclick = function() {
            ajax({
                type: 'get',
                url: 'http://localhost:3001/cross',
                success: function(data) {
                    console.log(data)
                }
            })
        }

服务端

app.get('/cross', (req, res) => {
    // 1. 允许哪些客户端访问我
    // * 代表允许所有的客户端访问我
    res.header('Access-Control-Allow-Origin', '*')
    // 2. 允许客户端使用哪些请求方法访问我
    res.header('Access-Control-Allow-Methods', 'get,post')
    res.send('ok')
})

这样做会存在问题:真实的项目中,服务器端的路由是有很多个的,如果向上面这样做的话,那么就需要在每一个路由中都设置响应头,这样代码的重复率太高了。
现可以使用express提供的中间件来拦截所有的请求,再对所有请求设置这两个响应头。

// 拦截所有请求
app.use((req, res, next) => {
    res.header('Access-Control-Allow-Origin', '*')
    res.header('Access-Control-Allow-Methods', 'get,post')
    next();
})
app.get('/cross', (req, res) => {
    res.send('ok')
})

cookie

跨域的话cookie无法随着请求发送到服务器端,无法实现跨域登录。
可以使用withCredentials和Access-Control-Allow-Credentials来解决。

在使用Ajax技术发送跨域请求时,默认情况下不会在请求中携带cookie信息。
withCredentials:指定在涉及到跨域请求时,是否携带cookie信息,默认值为false
Access-Control-Allow-Credentials:true允许客户端发送请求时携带cookie

客户端

例如:在客户端1使用ajax的过程中加上

// 当发送跨域请求时,携带cookie信息
	xhr.withCredentials = true;

服务端

在服务器2拦截所有请求里添加

// 允许客户端发送跨域请求携带cookie信息
	res.header('Access-Control-Allow-Credentials',true)

Node代理

什么是Node代理?

同源政策是浏览器给予Ajax技术的限制,服务器端是不存在同源政策限制的。
客户端1想要通过Ajax对服务器2进行请求,Node代理的简单理解就是客户端1先向服务器1发送请求,服务器1再向服务器2发送请求(服务器不存在同源政策限制);然后服务器2响应给服务器1,服务器1再响应给客户端1。

Node代理的实现

客户端1

            ajax({
                type: 'get',
                url: 'http://localhost:3000',
                success: function(data) {
                    console.log(data)
                }
            })

服务端1

const express = require('express');
const path = require('path');
// 向其他服务器端请求数据的模块
const request = require('request');
const app = express();
app.use(express.static(path.join(__dirname, 'public')))
app.get('/', (req, res) => {
    request('http://localhost:3001', (err, response, body) => {
        res.send(body)
    })
})
app.listen(3000)
console.log('服务器1启动成功');

服务端2

const express = require('express');
const path = require('path')
const app = express();
app.use(express.static(path.join(__dirname, 'public')))
app.get('/', (req, res) => {
    res.send('ok')
})
app.listen(3001)
console.log('服务器2启动成功');

此篇为视频学习过程中的笔记。若有不足和错误,欢迎批评指正!

猜你喜欢

转载自blog.csdn.net/xicc1112/article/details/106104558