跨域的九种解决方案

·  阅读 2163

背景

跨域问题在开发中很常见,目前很多框架都我们封装好了具体解决方案。我们只需要直接配置一个属性、或是调用一个方案等等,即可完成跨域。解决跨域的方案不唯一,下面我们来看一下跨域有几种解决方案。

同源策略

提到跨域就要有同源策略了。同时满足 协议  域名  端口号 一致,则两个页面具有相同的源,才算是同一个域名,否则均认为需要做跨域的处理。

jsonp

提到跨域不得不提 jsonp ,该方法兼容性好,但不支持 POST ,且不安全会造成 XSS 攻击。具体前后端 Demo  完整实现可见 链接
下面只写出核心代码,若后端不自行实现,可拿百度搜索接口来测试;

前端入门助理解版

function searchBaidu (value) {
  jsonp(`https://www.baidu.com/sugrec?json=1&prod=pc&wd=${value}&bs=${value}&cb=jsonpCallBack`) // 百度接口测试
}
function jsonp(url) {
  var element = document.createElement('script');
  element.setAttribute('src', url);
  document.getElementsByTagName('head')[0].appendChild(element);
}
function jsonpCallBack (res) {
  const { g } = res
  const list = Array.from(g, ({ q }) => q)
  const domList = list.map(item => {
    return `<div>${item}</div>`
  })
  $('#selectWrap').html(domList.join(''))
}
复制代码

前端 promise 封装版

function searchBaidu (value) {
  jsonp({
    params: {
      wd: value,
      bs: value,
      json: 1,
      prod: 'pc',
    },
    url: `https://www.baidu.com/sugrec`,
    cb: 'jsonpCallBack',
  }).then(res => { // 运用 promise 封装,即可用 then 回调实现
    const { g } = res
    const list = Array.from(g, ({ q }) => q)
    const domList = list.map(item => {
      return `<div>${item}</div>`
    })
    $('#selectWrap').html(domList.join(''))
  })
}

// promise 封装
function jsonp({ url, params, cb }) {
  params = { ...params, cb }

  let paramsStringify = Object.keys(params).map(item => `${item}=${params[item]}`).join('&')

  return new Promise((resolve, reject) =>{
    var element = document.createElement('script');
    element.setAttribute('src', `${url}?${paramsStringify}`);
    document.getElementsByTagName('head')[0].appendChild(element);
    window[cb] = res => {
      resolve(res)
    }
  })
}
复制代码

后端 Node.js 实现

const url = require('url');

require('http').createServer((req, res) => {
		const data = {"q":"跨域","p":false,"g":[{"type":"sug","sa":"s_1","q":"跨域解决方案"},{"type":"sug","sa":"s_2","q":"跨域物流"},{"type":"sug","sa":"s_3","q":"跨域立案"},{"type":"sug","sa":"s_4","q":"跨域是什么"},{"type":"sug","sa":"s_5","q":"跨域请求"},{"type":"sug","sa":"s_6","q":"跨越速运单号查询"},{"type":"sug","sa":"s_7","q":"跨域问题"},{"type":"sug","sa":"s_8","q":"跨越速运"},{"type":"sug","sa":"s_9","q":"跨域访问"},{"type":"sug","sa":"s_10","q":"跨域立案诉讼服务规定"}],"slid":"10238582904221488926"}
    const callback = url.parse(req.url, true).query.cb;
    res.writeHead(200);
    res.end(`${callback}(${JSON.stringify(data)})`); // 后端实现最核心

}).listen(9000, '127.0.0.1');

console.log('启动服务,监听 127.0.0.1:9000');
复制代码

实践代码

image.png

效果演示

image.png

CORS

普通跨域请求:只服务端设置头 Access-Control-Allow-Origin  即可,前端无须设置。

实践代码

image.png

效果演示

image.png

框架跨域应用

  • Egg.js 实现跨域具体方案 egg-cors

http-proxy

实践代码

这里服务没有基于 Koa 、Express   来写,采用无框架 Node.js   写法。

image.png

效果演示

  • 可以看到在 localhost:8080  端口号请求,可以获取到 localhost:9000  的数据;

image.png

框架跨域应用

vue 、 react  的脚手架已经帮我们将跨域配置简化抽离,相信也是基于 http-proxy  原理进行跨域访问的,只是框架都帮我们封装好了,让我们使用起来更简单。

  • vue.config.js  一部分
devServer: {
    host: '0.0.0.0',
    disableHostCheck: true,
    proxy: {
      '/mock': {
        target: `http://127.0.0.1:3000`,
        changeOrigin: true,
        pathRewrite: {
          '^/mock': ''
        }
      },     
    },
  },
复制代码
  • React  中的 setupProxy.js  很清晰基于 http-proxy-middleware
const proxy = require('http-proxy-middleware')
const { REACT_APP_PROXY_URL } = process.env

module.exports = function(app) {
  app.use(proxy('/mock', { 
    target: 'http://127.0.0.1:3001',
    secure: false,
    changeOrigin: true,
    pathRewrite: {
    '^/mock': ''
    }
  }))
  app.use(proxy(`${REACT_APP_PROXY_URL}`, {
    target: 'http://xxxx.xxx.com',
    secure: false,
    changeOrigin: true,
    pathRewrite: {
      [`^${REACT_APP_PROXY_URL}`]: ''
    }
  }))
}
复制代码
  • Umi.js  配置的跨域, 在 config.ts  文件中
const {
  REACT_APP_PROXY_URL,
  REACT_APP_PROXY_BASE_URL,
} = process.env;
/// ...

export default {
  /// ....,
  proxy: {
    [`${REACT_APP_PROXY_URL}`]: {
      target: REACT_APP_PROXY_BASE_URL,
      changeOrigin: true,
      pathRewrite: {
        [`^${REACT_APP_PROXY_URL}`]: '',
      },
    },
  },
	//...
}
复制代码

正反代理

通过上面我们知道通过 CORS  方式的代码,前端 HTML  直接请求 localhost:9000  地址的数据,
Proxy  方式,前端 HTML  还是请求 localhost:8080  地址的数据。 CORS  方式成为正向代理, Proxy  方式成为反向代理。

总结一句话到底是正向代理还是反向代理,只需要判断客户端知不知道真正返回数据的服务器在谁,知道就是正向代理,不知道就是反向代理

PostMessage

PostMessage  是 HTML5  中的 API ,且是为数不多可以跨域操作的 Window  属性之一。

当前页面与打开新窗口 进行数据传输

  • 实践代码
    • 前提:在 from  中打开新窗口  to ,  from  在 8080  端口号访问, to  在 9000  端口号访问;
    • from  通过找到 window.open  返回得到 to 的 window ,在通过 postMessage  传递消息;
    • to  通过监听 message  得到数据;

image.png

  • 效果图

image.png

页面与嵌套 iframe  消息传递

  • 实践代码
    • from  嵌套 to , from  在 8080  端口号访问, to  在 9000  端口号访问。
    • from  通过找到 to  的 iframe  得到 window  通过 postMessage  传递消息
    • to  通过监听 message  得到数据

image.png

  • 效果图

image.png

Nginx

未配置前

server {
  listen 8888;
  server_name localhost;
  location /mystatus {
    stub_status;
  }

  location / {
    root /www/test;
    index  1.html;
  }
}
复制代码

image.png

配置后

  • 重点在:add_header "Access-Control-Allow-Origin" "*";
server {
  listen 8888;
  server_name localhost;
  location /mystatus {
    stub_status;
  }

  location / {
    root /www/test;
    index  1.html;
    add_header "Access-Control-Allow-Origin" "*";
  }

}
复制代码
  • 页面请求该网址接口

image.png

document.domain

该方式只能用于二级域名相同的情况下,比如 a.test.comb.test.com 适用于该方式。
只需要给页面添加 document.domain = 'test.com' 表示二级域名都相同就可以实现跨域

实践代码

  • 首先在本地 host  配置 两个域名指向 127.0.0.1
127.0.0.1 b.test.com
127.0.0.1 a.test.com
复制代码
  • 接这开启两个服务 8080  端口给到 a.html  页面, 9000  端口给到 b.html  页面

image.png

效果演示

  • 未加 domain 之前

image.png

  • 添加 domain 之后

image.png

window.name

实践代码

  • a1.html  a2.html  部署在同域 8080  端口下, b.html  部署在 9000  端口上
  • a1.htmliframe  嵌套不同域 b.html
  • b.html  中数据挂载到 window.name  上

image.png

效果演示

image.png

location.hash

代码实践

  • a1.html  a2.html  部署在同域 8080  端口下, b.html  部署在 9000  端口上
  • a1.htmliframe  嵌套不同域 b ,并将传递数据放置到 hash  上
  • 在 b.html  将监听到的 hash  值经过处理,在创建 iframe , src  指向 a2
  • 此时 a2  可以将自己 location.hash  给到 window.parent.parent.location.hash


image.png

效果演示

image.png

WebSocket

实践代码

image.png

效果演示

image.png
image.png

总结

由此可看跨域种类多种多样,目前最常用的是 proxy , Webpack  已经帮我们封装好, Vue 、 React  等脚手架配置起来也相当方便, window.name  domain  hash  都与 iframe 相干,目前不常用。 CORS 方案也有封装好,方便项目中使用。大家一起来手动实践一下吧,相信会有所收获;当然不止这些例子,还有更多等着你去挖掘;

分类:
前端
标签:
分类:
前端
标签:
收藏成功!
已添加到「」, 点击更改