打算纯前端做一个接口测试工具, 直到遇到
这个报错, 触碰到了知识盲区了, 怎么办???
还好, 有谷哥和度娘. 原来是跨域 随手整理了一下常用的跨域方式处理方案, 这里马上分享给大家 :yum:
ps: 为了保证前后端编码的一致性, 本系列文章中涉及部分后端内容. 后端统一使用原生 nodejs 来搞, 请奔走相告.
准备工作
为了托管我们的静态页面, 我们需要一个可以提供服务器环境的插件, 这里推荐 live-server
, 通过命令 npm i -g live-server
安装即可. 该插件支持html文件热更新. 那用户体验简直飞起. 一键启动, 只需要在需要托管的目录执行 live-server .
即可.
ps: live-server
依赖 nodejs, 没有安装的小伙伴, 请参照这篇文章安装 nodejs.
AJAX 访问接口跨域解决方案
首先, 更正几个常见的错误认识:
同源策略是浏览器的行为, 和 js 关系不大.
所谓跨域是指请求发起方页面所在的 url 与访问的 api 存在协议, 域名, 端口中任意一个不同即视为跨域. 并不单单是指域名.
跨域这个东西, 日常工作中并不是很常用. 你想, 谁会闲的没事儿干总是请求人家别人的 api 去.
jsonp
可能有小伙伴会说. 圈圈, 你扯淡, 既然浏览器有跨域限制. 为什么我司项目从bootcdn, 引入的 jquery 依然跑在信息高速路上, 没有任何低头的意思?
hhh, :smile:. 这个质疑提的好. 浏览器同源策略禁止的是 ajax 请求. 然鹅, jquery 是一个 js 文件. 不受该策略的限制.
我尼玛, 那到底是啥限制啥不限制嘛???
根据MDN (自备梯子), 对于浏览器的同源策略的解释, 不受限制的外域资源加载情况有以下几种:
script
link
img
video
object embed applet
font-face 有的浏览器允许, 有的禁止
frame
那么问题来了, 挖掘机学校..., 不好意思走错片场了. 既然有这么多方式可以绕过浏览器同源策略的限制. 那么, 是不是我们可以做一点事情呢 ^_^
答: 是的 :smile:.
那还不抓紧搞起来?
我们使用第一个特例 script
一步一步实现跨域访问 (jsonp).
首先, 创建本次文章的项目目录
目录中, be 代表是后端项目, fe 代表前端项目. jsonp 目录说明我们是用 jsonp 的方式实现跨域.
在项目根目录下执行 live-server ./fe/jsonp/
启动前端 web 容器
编辑 ./fe/jsonp/
目录下的 index.html 文件. 代码如下:
jsonp 实现跨域
浏览器访问localhost:8080浏览器如下图说明前端 web 容器部署成功.
编写后端代码, 编写 be/jsonp/index.js
文件, 文件内容如下
var http = require('http');
var PORT = 8888
// 创建一个 http 服务
var server = http.createServer(function(request, response) {
response.end('hello world')
})
// 启动服务, 监听端口
server.listen(PORT, function() {
console.log('服务启动成功, 正在监听: ', PORT)
})
编写完成后命令行执行 node ./be/jsonp/index.js
命令行中出现
说明后端程序启动成功.此时可以通过浏览器访问localhost:8888获得 hello world
下来, 我们在前端的 index.html
中尝试通过 ajax 请求 http://localhost:8888/
来获取返回数据, 添加如下代码, 添加以后 代码
回到浏览器, 查看页面控制台, 熟悉的错误出现了. Access to XMLHttpRequest at http://localhost:8888/
from origin http://127.0.0.1:8080
has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource. 这个错误说明了, 我们是不能通过 ajax 的方式从 http://127.0.0.1:8080
访问 http://localhost:8888/
的.
既然不能通过 ajax 实现跨域的访问, 同时 mdn 又说 script 标签不受同源策略的限制. 那么, 我们尝试一下用 script 标签引入 http://localhost:8888/
试试?
此时的 代码 , 网络请求没有问题. 知识报了 js 文件不合法的问题. 如果我们把接口返回的数据调整为规范的 js 是不是, 嗯哼???
干起, 修改后端代码, 返回的内容由 hello world
改为 console.log('hello world')
, 修改后的 代码 (修改完后端代码以后切记重启服务哈 ^_^)
不得了, 不得了, 返回的结果不紧没有报错, 甚至可以执行. 我们从后端返回的 hellow world
成功的答应到了控制台了.
试想一下, 如果我们通过 js 文件里定义一个变量用于存放后端返回给前端的数据, 前端插入一个 script 标签, 把后端返回的变量定义执行一把. 那样定义的变量岂不是就可以在全局可以获取到后端定义的变量了. 赶紧试一把 :smile:
首先修改后端代码, 只需要调整一行.(修改完后端代码以后切记重启服务哈 ^_^)
response.end("var aaaa = {name: 'quanquan', friend: 'guiling'}");
其次调整前端代码
当前 代码 , 通过这种方式, 我们能够成功的获取到后端返回的数据. 但是, 接口这个东西时快时慢. 写个定时器轮询? 有点不够 666, 肿么办?
======================== 思考 5 分钟 ========================
======================== 5 分钟已过 ========================
既然, 写在 script 标签上的内容是可以直接执行的. 那么, 如果我们把变量的定义改写成一个函数的执行可不可以呢 ^_^, 试试?
后端(修改完后端代码以后切记重启服务哈 ^_^)
response.end("aaaa({name: 'quanquan', friend: 'guiling'})");
前端
结果
此时 代码 , 目前为止, 我们已经彻底解决了跨域的问题. 很靠谱有木有? 当然木有. 这个玩意儿只是说明了 jsonp 的原理, 并没有实用性. 下一步, 我们做一点封装. 让我们的代码更健壮 :muscle|type_1_2:
最后, 修改一把代码
前端
// 创建 Jsonp 类
// 初始化时传入两个参数, url 是接口的url
// cb 是对于接口返回的参数的处理
function Jsonp(url, cb) {
this.callbackName = 'jsonp_' + Date.now()
this.cb = cb
this.url = url
this.init()
}
// 初始化方法 用于拼接 url
Jsonp.prototype.init = function() {
if(~this.url.indexOf('?')) {
this.url = this.url + '&callback=' + this.callbackName
} else {
this.url = this.url + '?callback=' + this.callbackName
}
this.createCallback()
this.createScript()
}
// 创建 script 标签, src 取接口请求的url
Jsonp.prototype.createScript = function() {
var script = document.createElement('script')
script.src = this.url
script.Onload= function() {
this.remove()
}
document.body.appendChild(script)
}
// 绑定回调函数
Jsonp.prototype.createCallback = function() {
window[this.callbackName] = this.cb
}
// 创建 jsonp 实例, 并指定回调函数
new Jsonp('http://localhost:8888/', function(data) {
console.log(data)
})
后端(修改完后端代码以后切记重启服务哈 ^_^)
const http = require('http');
// 新引入了 url 模块, 主要用于解析请求参数
const url = require('url');
const PORT = 8888;
// 创建一个 http 服务
const server = http.createServer((request, response) => {
// 获取前端请求数据
const queryObj = url.parse(request.url, true).query;
// 这里把前端传来的 callback 字段作为后端返回的回调函数的函数名称
response.end(`${queryObj.callback}({name: 'quanquan', friend: 'guiling'})`);
});
// 启动服务, 监听端口
server.listen(PORT, () => {
console.log('服务启动成功, 正在监听: ', PORT);
});
目前 代码 , 至此我们已经能够顺利的获取跨域资源了. :clap|type_1_2:.
下集预告: jsonp 是一种传统的跨域解决方案, 关于这种方式的优缺点, 请度娘, 下一节, 我们一起学习相对比较现代一点的跨域解决方案. CORS
, See You