前端跨域问题解决方式

注: 文章摘自 写Bug - 思否javascript

什么是跨域

出于浏览器的同源策略限制。同源策略(Sameoriginpolicy)是一种约定,它是浏览器最核心也最基本的安全功能,若是缺乏了同源策略,则浏览器的正常功能可能都会受到影响。能够说Web是构建在同源策略基础之上的,浏览器只是针对同源策略的一种实现。同源策略会阻止一个域的javascript脚本和另一个域的内容进行交互。所谓同源(即指在同一个域)就是两个页面具备相同的协议(protocol),主机(host)和端口号(port)html

解决方式

一. 设置代理

例: 在 vue.config.js 文件中添加以下配置前端

const webpack = require('webpack');

module.exports = {
  devServer: {
    open: true, // 在 DevServer 启动, 且第一次构建完时自动打开网页
    port: 8080, // 端口号
    proxy: { // 设置代理
      '/api': { // 网关地址
        target: 'https://test.com', // 接口的域名
        ws: true, // 启用 websocket
        secure: false, // 若是是 https 协议,须要配置这个参数
        // 开启代理:在本地会建立一个虚拟服务端,而后发送请求的数据,并同时接收请求的数据,
        // 这样服务端和服务端进行数据的交互就不会有跨域问题
        changOrigin: true, // 跨域需开启
        pathRewrite: { // 重写地址
          '^/api': '' // 将前缀 '/api' 转为 '/'
        }
      }
    }
  }
}
复制代码

二. JSONP

注: 因为本质上 script 加载资源的方式是GET, 因此 JSONP 只能用于 GET 请求vue

在HTML标签里,一些标签好比 script、img 这样的获取资源的标签是没有跨域限制的,利用这一点,咱们能够这样干:java

  • 后端写个小接口
// 处理成功失败返回格式的工具
const {successBody} = require('../utli')
class CrossDomain {
  static async jsonp (ctx) {
    // 前端传过来的参数
    const query = ctx.request.query
    // 设置一个cookies
    ctx.cookies.set('tokenId', '1')
    // query.cb是先后端约定的方法名字,其实就是后端返回一个直接执行的方法给前端,因为前端是用script标签发起的请求,因此返回了这个方法后至关于立马执行,而且把要返回的数据放在方法的参数里。
    ctx.body = `${query.cb}(${JSON.stringify(successBody({msg: query.msg}, 'success'))})`
  }
}
module.exports = CrossDomain;
复制代码
  • 前端这样写
<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
  </head>
  <body>
    <script type='text/javascript'>
      // 后端返回直接执行的方法,至关于执行这个方法,因为后端把返回的数据放在方法的参数里,因此这里能拿到res。
      window.jsonpCb = function (res) {
        console.log(res)
      }
    </script>
    <script src='http://localhost:9871/api/jsonp?msg=helloJsonp&cb=jsonpCb' type='text/javascript'></script>
  </body>
</html>
复制代码
  • 简单封装一下前端这个套路
const request = ({url, data}) => {
  return new Promise((resolve, reject) => {
    // 处理传参成xx=yy&aa=bb的形式
    const handleData = (data) => {
      const keys = Object.keys(data)
      const keysLen = keys.length
      return keys.reduce((pre, cur, index) => {
        const value = data[cur]
        const flag = index !== keysLen - 1 ? '&' : ''
        return `${pre}${cur}=${value}${flag}`
      }, '')
    }
    // 动态建立script标签
    const script = document.createElement('script')
    // 接口返回的数据获取
    window.jsonpCb = (res) => {
      document.body.removeChild(script)
      delete window.jsonpCb
      resolve(res)
    }
    script.src = `${url}?${handleData(data)}&cb=jsonpCb`
    document.body.appendChild(script)
  })
}
// 使用方式
request({
  url: 'http://localhost:9871/api/jsonp',
  data: {
    // 传参
    msg: 'helloJsonp'
  }
}).then(res => {
  console.log(res)
});
复制代码

三. 空 iframe 加 form

  • 后端写个小接口
// 处理成功失败返回格式的工具
const {successBody} = require('../utli')
class CrossDomain {
  static async iframePost (ctx) {
    let postData = ctx.request.body
    console.log(postData)
    ctx.body = successBody({postData: postData}, 'success')
  }
}
module.exports = CrossDomain;
复制代码
  • 前端这样写
const requestPost = ({url, data}) => {
  // 首先建立一个用来发送数据的iframe.
  const iframe = document.createElement('iframe')
  iframe.name = 'iframePost'
  iframe.style.display = 'none'
  document.body.appendChild(iframe)
  const form = document.createElement('form')
  const node = document.createElement('input')
  // 注册iframe的load事件处理程序,若是你须要在响应返回时执行一些操做的话.
  iframe.addEventListener('load', function () {
    console.log('post success')
  })

  form.action = url
  // 在指定的iframe中执行form
  form.target = iframe.name
  form.method = 'post'
  for (let name in data) {
    node.name = name
    node.value = data[name].toString()
    form.appendChild(node.cloneNode())
  }
  // 表单元素须要添加到主文档中.
  form.style.display = 'none'
  document.body.appendChild(form)
  form.submit()

  // 表单提交后,就能够删除这个表单,不影响下次的数据发送.
  document.body.removeChild(form)
}
// 使用方式
requestPost({
  url: 'http://localhost:9871/api/iframePost',
  data: {
    msg: 'helloIframePost'
  }
});
复制代码

四. CORS

CORS是一个W3C标准,全称是"跨域资源共享"(Cross-origin resource sharing)跨域资源共享 CORS 详解。看名字就知道这是处理跨域问题的标准作法。CORS有两种请求,简单请求和非简单请求。node

只要同时知足如下两大条件,就属于简单请求。webpack

  • 请求方法是如下三种方法之一:web

    • HEAD
    • GET
    • POST
  • HTTP的头信息不超出如下几种字段:json

    • Accept
    • Accept-Language
    • Content-Language
    • Last-Event-ID
    • Content-Type:只限于三个值application/x-www-form-urlencoded、multipart/form-data、text/plain
  • 后端segmentfault

// 处理成功失败返回格式的工具
const {successBody} = require('../utli')
class CrossDomain {
  static async cors (ctx) {
    const query = ctx.request.query
    // *时cookie不会在http请求中带上
    ctx.set('Access-Control-Allow-Origin', '*')
    ctx.cookies.set('tokenId', '2')
    ctx.body = successBody({msg: query.msg}, 'success')
  }
}
module.exports = CrossDomain;
复制代码

前端什么也不用干,就是正常发请求就能够,若是须要带cookie的话,先后端都要设置一下

fetch(`http://localhost:9871/api/cors?msg=helloCors`, {
  // 须要带上cookie
  credentials: 'include',
  // 这里添加额外的headers来触发非简单请求
  headers: {
    't': 'extra headers'
  }
}).then(res => {
  console.log(res)
});
复制代码