1. Ajax请求限制
Ajax只能向自己的服务器发送请求
。- 比如现在有一个
A网站
、有一个B网站
,A网站中的HTML文件只能向A网站服务器中发送Ajax请求
,B网站中的 HTML 文件只能向 B网站服务器中发送Ajax请求。 - 但是 A网站不能向B网站服务器发送Ajax请求的,同理B网站也不能像A网站服务器发送Ajax请求
- 这是因为浏览器有
同源政策的限制
2. 什么是同源
- 同:相同的意思。源:来源的意思。
- 如果两个页面拥有相同的协议、域名、端口号,那么这两个页面就属于同一个源,其中只要有一个不相同,就不是同源。
3. 同源政策的目的
- 同源政策是为了保证用户信息的安全,防止恶意的网站窃取数据。
- 最初的同源政策是指A网站在客户端设置的Cookie,B网站是不能访问的。
- 随着互联网的发展,同源政策也越来越太严格,在不同源的情况下,其中有一项规定就是无法向非同源地址发送
Ajax 请求,如果请求,浏览器就会报错
4. 使用JSONP
解决同源限制问题
4.1 JSONP
是什么
jsonp
是 json with padding 的缩写,它不属于Ajax请求,但它可以模拟Ajax请求,实现无刷新页面请求数据。- 说白了就是 jsonp 就是绕过了浏览器同源政策的限制,向非同源服务器端发送请求。
4.2 JSONP
实现请求的步骤
- 实现:将不同源的服务器端请求地址写在
script标签的src属性中
<script src="www.example.com"></script> // 比如,引入服务端的 jquery 文件,就是不同源的地址的请求 // script 标签的 src 属性,不受同源政策影响 <script src="https://cdn.bootcss.com/jquery/3.3.1/jquery.min.js"></script>
- 服务器端响应数据必须是一个
函数的调用
,真正要发送给客户端的数据需要作为函数调用的参数
// script 标签通过src属性发送的请求,响应回来的结果会在客户端当做javascript代码立即执行 // 所以响应回来是一个函数的调用,响应的数据是函数调用的参数 // 这个函数的调用以字符串的形式返回 const data = 'fn({name: "张三", age: "20"})' res.send(data)
- 在客户端全局作用域下定义函数fn
// 因为服务器端响应的是一个函数的调用,所以必须定义一个函数 // 这个函数定义在客户端全局作用域下,而且必须定义在 script 请求标签的前面 function fn (data) {}
- 在 fn 函数内部对服务器端返回的数据进行处理
function fn(data) { // 在这里处理服务器端响应回来的数据 }
4.3 JSONP
代码优化
-
问题:我们写在页面中的 script 标签在页面加载的时候就发送了请求,无法做到动态发送请求
解决:我们可以通过比如点击按钮然后创建 script 标签,再来发送请求// 比如点击按钮发送 script 请求 // 1. 创建按钮后,获取按钮的 DOM 对象 var btn = document.querySelector('button') // 2. 按钮注册点击事件 btn.addEventListener('click', function () { //3. 当点击按钮的时候创建 script 标签 var script = document.createElement('script') //4. 给 script 标签添加 src 属性,并赋值 script.src = 'http://127.0.0.1:3000' //5. 把 script 标签添加到 body 中 document.body.appendChild(script) //6. 如果发送多次请求,就会在 body 中创建多个 script 标签,所以在 script 标签加载完之后就删除掉 script.onload = function () { // 添加到 body 中的 script 标签,是创建出来的 script 变量的引用,所以删除 script 这个变量就可以了 document.body.removeChild(script) // 因为 script 标签一加载完就会立即执行请求回来的函数调用,所以在加载完之后就可以移除了 } })
-
问题:客户端定义的函数名字必须和服务器端保持一样,那么在现实开发中,前后端人员还需要商量好函数的名字
解决:函数的名字由客户端定义,通过请求地址把函数的名字传递给后端,后端再取出来返回给前端// 客户端======================================================= <script> function fn(data) { // 在这里处理响应回来的数据 } </script> // 把函数的名字当做请求数据,放在请求地址的后面 callback=fn <script src="http://127.0.0.1:3000?callback=fn"></script> // 服务器端===================================================== app.get('/', function (req, res) { // 取出客户端的请求数据,获取函数的名字 var fnName = req.query.callback // 将获取到的函数名称当做函数的调用返回给客户端 var result = fnName + '({name: "张三"})' res.send(result) })
-
封装JSONP代码:
function jsonp(options) { // 1. 因为服务端返回的函数调用,需要在客户端定义一个函数,这里我们可以把函数定义在 jsonp 函数的参数中 // 2. 因为这个函数必须是全局函数,所以我们可以把函数放在window的属性中 // 3. 在多次 jsonp 请求的情况下,因为函数的名字相同,后面的函数会覆盖前面的函数,所以我们要动态创建函数的名字 var fnName = 'myJsonp' + Math.random().toString().replace('.', '') // 需要把随机数的 . 给替换成空字符串 window[fnName] = options.fn // 因为 fnName 是变量,所以不能用 . 的方式来创建window属性, 必须用[] //============================================================================================= //1. 把需要请求的参数拼接起来,然后在拼接到请求地址的后面 var params = '' for (var key in options.data) { // 因为请求地址前面已经拼接了一个函数,请求数据的前面必须用 & 隔开 params += & + key + options.data[key] } //============================================================================================= // 1. 动态创建 script 标签 var script = document.createElement('script') // 2. 为 script 标签添加 src 属性 script.src = options.url + '?callback=' + fnName + params // 3. 将 script标签追加到页面中 document.body.appendChild(script) // 4. 为 script 标签添加 onload 事件 script.onload = function () { // 5. 移除 script 标签 document.body.removeChild(script) } } //=========================================================================================== //=========================================================================================== // 服务器端代码 app.get('/', function (req, res) { // 在服务器端有一个 jsonp 函数,直接完成接收客户端传递过来参数的函数名字,以及数据 // 并且将返回给客户端的数据转换成字符串 res.jsonp(data) })
5. 访问非同源数据,服务器端解决方案
- 同源政策是浏览器给予 Ajax 技术的限制,服务器端是不存在同源政策限制的
// 一号客户端发送 ajax 请求 ajax({ type: 'get', url: 'http://127.0.0.1:3000', success: function (data) { console.log(data) } }) //=================================================== // 一号服务器端 // 要下载 request 模块(向其他服务器端请求数据的模块),然后引入 const request = require('request') app.get('/', function (req, res) { // 使用 request 方法向其他服务器端请求数据,然后响应给自己的客户端 request('http://127.0.0.1:3001', function (err, response, body) { res.send(body) }) })
6. cookie
6.1 传统网站应用中的问题
- 对于网站应用这种软件,分为客户端、服务器端两部分。客户端可以向服务器端发送请求,服务器端可以对客户端的请求
给予响应。 - 客户端和服务器端进行沟通的时候,要遵循HTTP协议中设定的规则,在HTTP协议中有这样一条规则,客户端和服务器端
的沟通是无状态性的。 - 无状态性的意思:服务器端不关系客户端是谁,服务器端只关心请求,只要请求来了服务器端就会对请求作出响应,响应
一旦结束,这次沟通也就随之结束了,当同一个客户端再次向服务器端发送请求的时候,服务器端并不知道客户端已经来
过一次了。这就是无状态性。 - 也就是说服务器端没有记忆功能,就算它们沟通再多次也是陌生人,这种特性在早期的网站应用中是没有问题的,因为
早期的网站应用只是在页面当中展示一些文字、图片之类的数据。浏览网站的人并不会和网站发生交互。 - 但是现在网站应用的需求是五花八门的,比如用户在电商网站中购物时,需要用户进行登录才能购买商品,如果用户不进行
登录,是不能购买商品的。 - 那么客户端和服务器端的交互没有状态,谁也不认识谁,既然这样的话如何实现登录功能呢,如何让服务器端识别客户端
的身份呢,这个时候cookie就诞生了
6.2 cookie 是什么
- cookie 是实现客户端和服务器端身份识别的一种技术
- 如何进行身份识别呢,当客户端第一次访问服务器端的时候,服务器端检测到当前的这个客户端我并不认识,这个时候
服务器端在对客户端作出响应的同时还可以给客户端发送一个小卡片。 - 这个小卡片可以把它理解为是服务器端发给客户端的身份证,有了这个身份证,服务器端就自动客户端是谁了,这个身份证
就可以理解为cookie,此时在客户端的浏览器中就有了这个身份证了 - 当客户端再一次访问服务器端的时候,这个身份证也就是cookie也会随着发送到服务器端,服务器端拿到这个身份证就在的
这个客户端是谁了,这样就建立了客户端和服务器端长久的联系
6.3 在跨域请求中设置允许携带cookie
- 在使用Ajax技术发送跨域请求时,默认情况下不会在请求中携带cookie信息,这也是处于安全性考虑,防止cookie信息
被窃取 - withCredentials:指定在涉及跨域请求时,是否携带cookie信息,默认值false,如果想要在跨域请求时携带cookie,我们要设置
withCredentials: true
// 在客户端 Ajax 请求中设置 xhr. withCredentials = true
- 这还不够,在服务器端我们还要在响应头中设置
Access-Control-Allow-Credentials 为 true
// 在服务器端设置响应头 res.header('Access-Control-Allow-Credentials', true)