由于浏览器同源策略,凡是发送请求url的协议、域名、端口三者之间任意一与当前页面地址不同即为跨域。
本文参考:https://www.cnblogs.com/giggle/p/5496596.html
序言
之前在公司实习的时候才接触到的(落后了很多吧),Axios的请求,第一次是option
,第二次才是我们写在前端代码里面的get/post
之类的请求。
小白视角:Σ(っ°Д°;)っ 我不是只写一个 异步请求吗?怎么会发了两个呢?诶,第一个怎么什么都还没返回,method怎么还是option,它是什么鬼???
一. 跨域是什么?你常常听人说,但是却不知道那是怎么样的。 (JSONP 跨域)
学习的 Vue/jQuery/React 的时候,你可曾使用过网上的 CDN 路径?像这样的 --- <script src="http://cdn/vue.js"></script>
然后我们的网页就能:使用Vue、React、使用jQuery的$了。这其实就是跨域。这是一种通过JSONP的方式实现的跨域。
下面让我们通过一个 更清晰、超简单 !的例子来模拟一下跨域。 语言 采用 Node.js
跨域 ---> 其实是为了请求数据。 所以,我们的目的其实是 : 获取目的域的数据。
- 首先,我们要开一个服务器
const http = require('http');
// 导入 node 自带的 http 包
const server = http.createServer((req, res) => {
const callback = 'test' + '(' + ')';
res.end(callback);
}).listen(3000);
console.log('Server is running');
上面的代码做了这样一件事:开了一个 端口 为 3000 的服务器,当被请求的时候,将返回 test()
,注意,在这里是 String 类型 'test()'
- 然后,我们写一个前端页面。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Document</title>
</head>
<body>
<h1>jsonp</h1>
</body>
<script type="text/javascript">
function test() {
console.log('77');
}
</script>
<script src="http://127.0.0.1:3000"></script>
</html>
通过上图我们可以看见,我们的test
基本上已经被运行了!!!!
过程应当是这样的: script:src
访问 -> 服务器返回response -> 不是以 String 类型返回的。(为什么待更!!本人知识系统还不够完全 ORZ)
- 但是这样其实是没有完成的。因为跨域的目的是为了 获取数据,我们修改一下代码。
前端
<script type="text/javascript">
function test(data) {
console.log("接收到服务器的数据" + data);
}
</script>
<script src="http://127.0.0.1:3000"></script>
后台node
const http = require('http');
// 导入 node 自带的 http 包
const server = http.createServer((req, res) => {
let data = {
name: 'ccc'
}
data = JSON.stringify(data);
const callback = 'test' + '(' + data + ')';
res.end(callback);
}).listen(3000);
console.log('Server is running');
至此,其实我们就已经看到了 JSONP 的一次过程。是不是很简单!!~
- 上面已经基本完成了
JSONP
的介绍,不过我们的JSONP
代码不可能那么随便。下面就放一下相对严谨的JSONP
吧。
1.我们需要区分 JSONP 和其他正常的请求。2.我们需要灵活的 传回 函数,而不是限死为
test()
**JSONP 36K豪华版代码如下**
后台
const http = require('http');
// 导入 node 自带的 http 包
const url = require('url');
// 用于解析 url 的字符串:用于解决 问题1: 区分 JSONP
const qs = require('querystring');
// 依旧是用于 解析 字符串(主要是'?'之后的 查询字符串 ):用于解决 问题2:不在后端限死回调函数的名字
const server = http.createServer((req, res) => {
const urlPath = url.parse(req.url).pathname;
// req.url 是一个 url 对象,其中的 pathname 为 IP端口地址 后面的 路径
const urlQs = qs.parse(req.url.split('?')[1]);
console.log(urlQs);
if (urlPath === '/jsonp' && urlQs.callback) {
// 我们自己设定的路径 jsonp ,前端的时候请求的时候需要请求这个路径
let data = {
name: 'ccc'
}
data = JSON.stringify(data);
const callback = urlQs.callback + '(' + data + ')';
res.end(callback);
} else {
res.end("hello, world, this is 127.0.0.1:3000");
}
}).listen(3000);
console.log('Server is running');
前端
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Document</title>
</head>
<body>
<h1>jsonp</h1>
</body>
<script type="text/javascript">
function test(data) {
console.log("接收到服务器的数据", data);
}
</script>
<script src="http://127.0.0.1:3000/jsonp?callback=test"></script>
</html>
- 一进入页面就直接跨域?是否也有点不妥? 前端再优化下!
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Document</title>
</head>
<body>
<h1>jsonp</h1>
<button>开始跨域</button>
</body>
<script type="text/javascript">
function test(data) {
console.log("接收到服务器的数据", data);
}
// 注意:上面的回调的方法必须为全局方法!!!
const oBtn = document.getElementsByTagName("button")[0];
oBtn.onclick = () => {
const script = document.createElement('script');
script.src = "http://127.0.0.1:3000/jsonp?callback=test";
document.body.appendChild(script);
}
</script>
<!-- <script src="http://127.0.0.1:3000/jsonp?callback=test"></script>
-->
</html>
大功告成~是不是很简单呢!代码量也不多!
由于浏览器安全是基于同源策略的 -> 所以要采用跨域。所谓同源策略就是
二. 什么是同源?
jsonp不受同源策略的影响。
所谓同源?如下:
- 协议相同。
- 域名相同。 (域名并不等于IP地址、域名并不等于IP地址、域名并不等于IP地址)
- 端口相同。
同源策略限制从一个源加载的文档或脚本如何与来自另一个源的资源进行交互。这是一个用于隔离潜在恶意文件的关键的安全机制。浏览器的同源策略,出于防范跨站脚本的攻击,禁止客户端脚本(如 JavaScript)对不同域的服务进行跨站调用(通常指使用XMLHttpRequest请求)。
特例:域名 和 域名对应IP 都不算是同源:http://70.32.92.74 和 http://www.a.com
三. 跨域的方法
A. JSONP
使用概述:这种方式主要是动态插入一个 script。
浏览器对 script 资源的引用没有同源限制。同时资源加载后会立即执行。(没有阻塞的话)
使用方法:
后端:解析 url地址,根据 url 地址返回对应的 callback 函数名(具体看约定)
const http = require('http');
const url = require('url'); // 获取 IP 地址后面的路径
const qs = require('querystring'); // 获取查询字符串
const server = http.createServer((req, res) => {
let data = JSON.stringify({
name: 'cc',
value: 10
});
const jsonpObj = qs.parse(req.url.split('?')[1]);
res.end(jsonpObj.callback + "(" + data + ")");
}).listen(3000);
前端:用js生成一个 script:src ,当然,也可以直接写标签。
let script = document.createElement('script');`
script.src = "http://127.0.0.1:3000/jsonp?key=1&callback=test";
document.body.appendChild(script);
特点:
- 只能使用GET请求
- 不能注册 success/error 等事件监听很熟,不能很容易判断 JSONP 是否请求成功(一般用时间才可以)
- JSONP 是否从其他域 加载代码执行,容易收到跨站请求伪造的攻击,安全性无法保证。
B. 图片Ping
既然 script 标签可以进行跨域,为什么 图片不行? 答案是可以的。。
(在这里感谢面试的大大QAQ,这是可以的,我当时以为 图片没办法直接执行 callback 就答了不行。
但其实在后端是可以返回 response的,只是 操作比较受限制罢了)
使用方法:
const http = require('http');
let number = 0;
const server = http.createServer((req, res) => {
++ number;
console.log(number);
res.end("加载了一次");
}).listen(3000);
<img src="http://127.0.0.1:3000" alt="">
该方法常用于 跟踪用户点击页面次数 或者 动态广告曝光次数.该方法不能访问响应的文本。(差评)
无响应!!
次数的变化
特点:
- 只能发送Get请求(劣势)
- 无法响应服务器的响应文本,单向请求(劣势)
C. CORS(跨资源共享) - cross site resouce sharing
和 ajax 其实大体相同。这是现代浏览器支持跨域请求资源的一种方式。
CORS 支持所有 HTTP 要求。
使用概述:
1. 当我们使用 XMLHttpRequest 发送请求的时候,浏览器发现该请求不符合 同源策略,会给该请求 添加一个请求头 Access-Control-Allow-Origin。并且判断是否包含 Origin 的值。
2. 如果有 Origin,则浏览器会处理相应,我们就可以拿到响应数据。
3. 如果不包含 浏览器直接驳回、这时候我们拿不到响应数据。
使用概述2:
服务器需要添加以下响应头的一种或者几种。
- Access-Control-Allow-Origin: *
- Access-Control-Allow-Methods: POST, GET, OPTIONS
- Access-Control-Allow-Headers: X-PINGOTHER, Content-Type
- Access-Control-Max-Age: 86400
使用概述3:
跨域请求不会携带 Cookie 信息,如需携带,请配置如下参数
- "Access-Control-Allow-Credentials": true
- "withCredentials": true // ajax 设置
D. Proxy 代理(这个我在 Vue 的 webpack配置里面见过)
这种方法将请求发送给后台服务器,让服务器发起请求,然后将请求返回给前端。相当于前端告诉后端 我要 跨域的地址, 让后端帮忙。
来吧。。让我们拿博客园 开刀(不要打我)
下面这个例子最后在我本地是 失败的 。。 可以先跳过 方法 D,待更。。
后端代码(需要 npm 安装 request 模块)
const http = require('http');
const qs = require('querystring');
const request = require('request');
http.createServer((req, res) => {
// 1. 这里是 截取前端要我们跨域的 域名 (前端 要我干啥 我就干啥)
const proxyUrl = req.url.substr(req.url.indexOf('?') + 1);
// 2. 前端给了我们 域名, 那么,我们用什么方法 去请求? 前端也告诉了我们
if (req.method === 'POST') {
let post = '';
// 用于暂存请求体的信息
req.on('data', (chunk) => {
post += chunk;
});
req.on('end', () => {
console.log(post);
post = qs.parse(post);
request({
method: 'POST',
url: proxyUrl,
form: post
}).pipe(res);
});
}
}).listen(3000);
前端代码
const xhr = new XMLHttpRequest();
xhr.onload = function() {
console.log(xhr.responseText);
}
xhr.open('POST', 'http://127.0.0.1:3000', true);
xhr.send("f=json");
E. 使用window.name实现跨域
待更。
参考文献:
js中几种实用的跨域方法原理详解
js实现跨域的几种方法汇总(图片ping、JSONP和CORS)
八种方式实现跨域请求
利用script标签实现的跨域名AJAX请求(ExtJS)
阮一峰大大的教程