走过路过来看看各类实现跨域的方式~

跨域是什么?

说到跨域咱们先来讲说同源,同源是指"协议+域名+端口"三者相同,所谓的跨域就是跨协议跨域名跨端口javascript

跨域致使的问题

因为安全缘由,跨域访问是被各大浏览器所禁止的。html

Cookie、LocalStorage 和 IndexDB 没法跨域读取前端

假如你登陆了某个银行网站那么该网站会发送cookie做为登陆凭证。假如此时你访问了恶意网站,对其发送请求若是能够携带cookie跨域,那么cookie会发送到恶意网站,此时就能够模拟用户去银行网站发送请求。java


DOM同源策略也同样,不能经过iframe引入其余域下的页面直接操做其dom元素webpack

在本身的网站嵌入别人的网站,若是嵌入的网站有登陆功能,若是能够跨域得到嵌入页面的元素,那在咱们的网站上能够轻松获取到用户的帐号密码。nginx


ajax请求不能跨域发送web

若是不限制ajax跨域发送数据,就至关于网站的全部接口对全部人都是公开的ajax


跨域解决方案

一.jsonp

jsonp主要是利用标签的src属性没有受同源策略限制来获取来自其余域名的数据。咱们一般使用 img/script 标签。express

先举个例子~ (bd搜索框) 在访问bd进行搜索时咱们能够很轻松的获取搜索接口npm

https://sp0.baidu.com/5a1Fazu8AA54nxGko9WTAnF6hhy/su?wd=hello&cb=list

接口返回的结果

// 不难发现cb传什么返回的就是什么
list({q:"hello",p:false,s:["hello kitty","hello kitty乐园","hello 树先生","hello world","hellobike","hello tv","hello女神","hellotalk","hello语音","hello venus"]});
复制代码

开始来写咱们的例子

<script>
  function list(data){
    console.log(data);
  } 
</script>
<script src="https://sp0.baidu.com/5a1Fazu8AA54nxGko9WTAnF6hhy/su?wd=hello&cb=list"></script>
复制代码

好了这就已经实现了跨域了,咱们访问了bd的接口!

加深点印象咱们本身来写下服务端吧!

// client
<script>
  function list(data){
    console.log(data);
  } 
</script>
<script src="http://localhost:3000/getData?cb=list"></script>
// server
let express = require('express');
let app = express();
app.get('/getData', (req,res) => {
  let cbName = req.query.cb
  res.end(`${cbName}({name:'jw'})`);
});
app.listen(3000);
复制代码

缺点:它只支持GET请求而不支持POST等其它类型的HTTP请求,可能会致使xss攻击

二.CORS

CORS(Cross-Origin Resource Sharing, 跨源资源共享)是W3C出的一个标准,想要实现跨域主要靠服务器进行一些设置。客户端不用作任何更改!

先列点杂七杂八的东西,后面咱们会用到!
Access-Control-Allow-Origin 容许的域
Access-Control-Allow-Credentials 容许携带cookie
Access-Control-Expose-Headers 容许客户端获取哪一个头
Access-Control-Allow-Methods 容许的方法
Access-Control-Allow-Headers 容许哪些头
Access-Control-Max-Age 预检请求的结果缓存

废话很少说先上例子~启动两个服务

// 经过3000端口来启动index.html
let express = require('express');
let app = express();
app.use(express.static(__dirname));
app.listen(3000);

// 4000端口有getData接口
let express = require('express');
let app = express();
app.use(express.static(__dirname));
app.get('/getData', (req,res) => {
  console.log(req.headers);
  res.end('{name:"jw"}')
});
app.listen(4000);
复制代码

在html发送ajax请求

let xhr = new XMLHttpRequest();
xhr.open('get','/getData',true);
xhr.onreadystatechange = function () {
  if(xhr.readyState === 4){
    if(xhr.status >=200 && xhr.status<300|| xhr.status ===304){
      console.log(xhr.response)
    }
  }
}  
xhr.send();
复制代码

head1
head1
咱们能够发现虽然没有获取到数据可是4000端口实际上是接收到了响应的,只是浏览器默认屏蔽了响应结果

第一次改造服务端:

当请求发送过来时,要根据当前客户端的origin属性判断是否容许跨域

let express = require('express');
let app = express();
app.use(express.static(__dirname));
let whiteList = ['http://localhost:3000'];
app.use(function (req,res,next) {
  if (whiteList.includes(req.headers.origin)) {
    // 设置某个域能够容许访问,也可使用* 可是不建议这样作
    res.setHeader('Access-Control-Allow-Origin', req.headers.origin)
  }
  next();
})
app.get('/getData', (req,res) => {
  console.log(req.headers);
  res.end('{name:"jw"}');
});
app.listen(4000);
复制代码

默认跨域是不携带cookie的,但是我想带!强制设置携带cookie

// client
document.cookie = 'a=1'
let xhr = new XMLHttpRequest();
xhr.withCredentials = true;
复制代码

head1

第二次改造服务端:

app.use(function (req,res,next) {
  if (whiteList.includes(req.headers.origin)) {
    res.setHeader('Access-Control-Allow-Origin', req.headers.origin);
+   res.setHeader('Access-Control-Allow-Credentials',true)
  }
  next();
})
复制代码

客户端仍是不满意想在传递些自定义的头!

document.cookie = 'a=1'
let xhr = new XMLHttpRequest();
xhr.withCredentials = true;
xhr.open('get','http://localhost:4000/getData',true);
xhr.setRequestHeader('name', 'jw');
复制代码

head1

第三次改造服务端:

app.use(function (req,res,next) {
  if (whiteList.includes(req.headers.origin)) {
    res.setHeader('Access-Control-Allow-Origin', req.headers.origin);
    res.setHeader('Access-Control-Allow-Credentials',true);
+   res.setHeader('Access-Control-Allow-Headers',"name");
  }
  next();
});
复制代码

客户端又发现发送put请求时。又出现了恶心的问题......

xhr.open('put','http://localhost:4000/getData',true);
复制代码

head1
这回我一眼就看到了问题,确定是没有设置容许接收的方法

第四次改造服务端:

app.use(function (req,res,next) {
  if (whiteList.includes(req.headers.origin)) {
    res.setHeader('Access-Control-Allow-Origin', req.headers.origin);
    res.setHeader('Access-Control-Allow-Credentials',true);
    res.setHeader('Access-Control-Allow-Headers',"name");
+   res.setHeader('Access-Control-Allow-Methods','PUT');
  }
  next();
});
app.put('/getData', (req,res) => {
  console.log(req.headers);
  res.end('{name:"jw"}');
});
复制代码

等等怎么多了个请求?

head1

原来是这样对于非简单请求,会在正式通讯以前,增长一次HTTP查询请求,称为"预检"请求(preflight)。类型为 options。

第五次改造服务端:

app.use(function (req,res,next) {
  if (whiteList.includes(req.headers.origin)) {
    res.setHeader('Access-Control-Allow-Origin', req.headers.origin);
    res.setHeader('Access-Control-Allow-Credentials',true);
    res.setHeader('Access-Control-Allow-Headers',"name");
    res.setHeader('Access-Control-Allow-Methods','PUT');
    // 设置预检查发送的时间
+   res.setHeader('Access-Control-Max-Age',6000);
    if (req.method === 'OPTIONS'){
      res.end();
    }else{
      next();
    }
  }
});
复制代码

到此客户端终于不搞事了。服务端为了表示友好给他写了个头

app.put('/getData', (req,res) => {
  res.setHeader('info','ok');
  res.end('{name:"jw"}');
});
复制代码
xhr.onreadystatechange = function () {
  if(xhr.readyState === 4){
    if(xhr.status >=200 && xhr.status<300|| xhr.status ===304){
+     console.log(xhr.getResponseHeader('info'));
      console.log(xhr.response);
    }
  }
}  
复制代码

head1

这才发现客户端原来是收不到的,好吧!还须要告诉客户端能够拿到这个值

app.put('/getData', (req,res) => {
  res.setHeader('info', 'ok');
  res.setHeader('Access-Control-Expose-Headers', 'info');
  res.end('{name:"jw"}');
});
复制代码

到此! 终于知道cors是个什么鬼了,就是设置各类约定好的头

CORS 不只使用方便,支持全部类型请求,具备权限控制,并且浏览器原生支持,咱们能够轻易的处理请求异常。利于排查错误。因此咱们大多数状况下会首选该方式。在低版本浏览器可使用jsonp配合其余方式来兼容。

三.postMessage

在来看看两个窗口是如何实现跨域的,先看看H5的postMessage! postMessage()方法能够安全地实现跨源通讯

如今的需求是这样的 http://127.0.0.1:3000/a.html 想对http://127.0.0.1:4000/b.html 说我爱你,以后b页面收到消息后对其说我不爱你~,这事多么悲伤的一个故事#_#。

// a.html
<iframe src="http://localhost:4000/b.html" id="frame" onload="load()" frameborder="0"></iframe>
<script>
  function load() {
    let frame = document.getElementById('frame');
    frame.contentWindow.postMessage('我爱你','http://localhost:4000');
    window.onmessage = function (e) {
      console.log(e.data);
    }
  }
</script>

// b.html
<script>
  window.onmessage = function (e) {
    console.log(e.data);
    e.source.postMessage('我不爱你',e.origin);
  }  
</script>
复制代码

四.document.domain

domain方式跨域要先保证两个页面属于同一个域下的 咱们在hosts文件增长两个映射关系,位置在 C:\Windows\System32\drivers\etc

127.0.0.1 a.fullstackjavascript.cn
127.0.0.1 b.fullstackjavascript.cn
复制代码
// a.html
<iframe src="http://b.fullstackjavascript.cn:4000/b.html" 
        frameborder="0" id="frame"
        onload="load()"
></iframe>
<script>
  function load() {
    document.domain = 'fullstackjavascript.cn';
    console.log(frame.contentWindow.name);
  }
</script>

// b.html
<script>
  document.domain = 'fullstackjavascript.cn';
  var name = 'jw';  
</script>
复制代码

访问http://a.fullstackjavascript.cn:3000/a.html,发现是能够拿到另外一个页面中的值

五.location.hash

用hash传递数据就比较有意思了,须要借助第三个页面实现。
先用粗俗的话介绍一下,先有三个页面hash1,hash2,hash3他们之间的关系是hash1和hash2是同域下的hash3是独立域下的,咱们经过hash1页面用iframe引入hash3页面,能够在引入时给hash3页面传递hash值,hash3接到hash值后算出须要返回结果,在建立iframe引入hash2把结果经过hash的方式传递给hash2,hash2和hash1是同域的,hash2能够直接操控hash1的值,此时hash1页面能够监控hash值的变化来获取hash2的数据

// hash1.html
<iframe src="http://a.fullstackjavascript.cn:3000/hash3.html#iloveyou" frameborder="0"></iframe>
<script>
    window.onhashchange = function () {
      console.log(location.hash);
    }
</script>

// hash2.html
<script>
  let hash = location.hash;
  let data;
  if(hash === '#iloveyoue'){
    data = 'idontloveyou'
  }
  let frame = document.createElement('iframe');
  frame.src = `http://a.fullstackjavascript.cn:3000/hash3.html#${data}`;
</script>

// hash3.html
<script>
  let hash = location.hash;
  window.parent.parent.location.hash = hash 
</script>
复制代码

效果是没问题的,不过感受有点麻烦~

六.window.name

使用window.name跨域是利用切换路径后window上的name属性是会保留的。不懂?不要紧,举个例子,仍是有三个页面a,b,c。a和b是同域下的,c本身一个域。a先引用c,c将想表达的内容放到name,属性上以后,a改变引用路径,改为引用b,此时name属性不会被删除,由于a,b是同域的,因此能够直接获取。

// a.html
<iframe src="http://a.fullstackjavascript.cn:4000/c.html" id="myFrame" onload="load()" frameborder="0"></iframe>
<script>
  let first = true
  function load() {
    if(first){
      myFrame.src = 'http://b.fullstackjavascript.cn:3000/c.html';
      first = false
    }else{
      let name = myFrame.contentWindow.name;
      console.log(name);
    }
  }
</script>
// c.html
<script>
  window.name = '我爱你'
</script>
复制代码

直接访问http://b.fullstackjavascript.cn:3000/a.html发现能够拿到咱们想要的结果啦~

七.WebSocket

WebSocket最大特色就是,服务器能够主动向客户端推送信息,客户端也能够主动向服务器发送信息,是真正的双向平等对话。这个不是我们考虑的问题。我们如今的话题是跨域,偏偏WebSocket是没有同源限制的!

能够直接建立socket对象和服务器通讯发消息

// socket.html
<script>
  let socket = new WebSocket('ws://localhost:4000');
  socket.onopen= function () {
    socket.send('我爱你');
  }
  socket.onmessage =  function (e) {
    console.log(e.data);
  }
</script>
复制代码

服务端须要ws模块

$ npm install ws
复制代码
let express = require('express');
let app = express();
let WebSockect = require('ws');
let wss = new  WebSockect.Server({port:4000});
wss.on('connection',function (ws) {
  ws.on('message',function (data) {
    console.log(data);
    ws.send('我不爱你');
  });
})
复制代码

咱们都知道好东西每每不兼容,咱们可使用socket.io模块保证兼容性问题。这里我就不过多去介绍websocket的使用了

八.Proxy

咱们来看看服务端是如何实现代理的,也就是咱们常说的反向代理!你们可能都对webpack比较熟悉了,webpack-dev-server内置了http-proxy-middleware。那好吧我们就用express配合这个中间件插件来试试这东西怎么玩。

// 3000服务器
let express = require('express');
let app = express();
var proxy = require('http-proxy-middleware');
let proxyOptions = {
  target: 'http://localhost:4000',
  changeOrigin: true
};
app.use('/api', proxy(proxyOptions));
app.listen(3000);
复制代码
// 4000服务器
let express = require('express');
let app = express();
app.get('/api', (req,res) => {
  res.end('ok');
})
app.listen(4000);
复制代码

当咱们访问http://localhost:3000/api路径时请求会被转发到4000服务器上,将4000服务器上的结果响应给客户端

九.Nginx

我相信你们对Nginx都并不陌生,他能够实现代理咱们的接口。这里我就简单的使用一下展现下效果! 这里以windows系统为例:

先要安装nginx 下载地址

更改conf/nginx.conf增长访问代理

location /api/ {
  proxy_pass http://localhost:4000;
}
复制代码

双击nginx.exe运行,访问localhost/api发现返回了4000服务器上的结果。

废了这么多口水,终于把前端经常使用的跨域手段介绍了一遍!有什么疑问欢迎联系我!

结语

喜欢的点个赞吧^_^!
支持个人能够给我打赏哈!

打赏
相关文章
相关标签/搜索