跨域的四种方式

376 阅读6分钟

本文不涉及跨域等基础理论知识(可自行查找何为跨域)

一:Jsonp跨域(只限于get)

0.工作前准备

0.1 npm init --yes

0.2 cnpm i express -S (express框架)

0.3 cnpm i axios -S (axios模块)

1. 正常创建一个js文件和网页

const express = require('express');
const app = express();
const fs = require('fs');

app.get('/',(req,res) => {
  fs.readFile('./index.html', (err,data) =>{
    if (err) {
      res.statusCode = 500;
      res.end('500 Interval Serval');
    }else{
      res.statusCode = 200;
      res.setHeader('Content-Type', 'text/html');
      res.end(data);
    }
  })
})
app.listen('3000');

index.html

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>
<body>
  <h2>jsonp跨域解决方案</h2>
</body>
</html>

2. 在index.html发起跨域请求(需要axios)

2.1在index.html中引入axios模块

<script src="/axios/dist/axios.js"></script>

2.2在js文件中使用中间件方法设置node_modules为静态资源目录(注意不是index.html)

将来在模板中如果使用了src属性 http://localhost:3000/node_modules

app.use(express.static('node_modules'))

2.3在index中进行跨域请求

    axios.jsonp('http://127.0.0.1:3000/user')
    .then(res => {
      console.log(res);
    }).catch(err => {
      console.log(err);
    }) 
但要注意的是axios已经不支持jsonp方法 要自己包装

2.4 包装jsonp

包装jsonp的几个思路

  • 创建一个script标签 设置对应的type url
  • url后面用?callback=回调函数名(之所以用callback是因为express框架中有个res.jsonp()方法,默认的回调名就是callback,也可以自行设置,官方图示) 参考来源: expressjs.com/zh-cn/4x/ap…
  • 回调函数要挂载到window上,不然后端识别不到
    axios.jsonp = url => {
      return new Promise((resolve, reject) =>{
        window.jsonCallBack = function (result){
          resolve(result);
        }
        // 动态创建script脚本 通过src属性 拼接url地址
        // http://127.0.0.1:3000/user?callback=jsonCallBack

        let JSONP = document.createElement('script');
        JSONP.type = 'text/javascript';
        JSONP.src = `${url}?callback=jsonCallBack`;
        document.querySelector('head').appendChild(JSONP);
        setTimeout(() => {
          document.querySelector('head').removeChild(JSONP);
        }, 1000);
      })
    }

2.5后端打印结果

  res.jsonp({name:'qlq'});

或者

  const callback = req.query.callback;
  res.end(`${callback}(${JSON.stringify({name:'qlq'})})`)

完整代码

html.index

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>
<body>
  <script src="/axios/dist/axios.js"></script>

  <h2>jsonp跨域解决方案</h2>

  <script>
    axios.jsonp = url => {
      return new Promise((resolve, reject) =>{
        window.jsonCallBack = function (result){
          resolve(result);
        }
        // 动态创建script脚本 通过src属性 拼接url地址
        // http://127.0.0.1:3000/user?callback=jsonCallBack

        let JSONP = document.createElement('script');
        JSONP.type = 'text/javascript';
        JSONP.src = `${url}?callback=jsonCallBack`;
        document.querySelector('head').appendChild(JSONP);
        setTimeout(() => {
          document.querySelector('head').removeChild(JSONP);
        }, 1000);
      })
    }

    axios.jsonp('http://127.0.0.1:3000/user')
    .then(res => {
      console.log(res);
    }).catch(err => {
      console.log(err);
    }) 

  </script>
</body>
</html>

js文件

const express = require('express');
const app = express();
const fs = require('fs');

//中间件设置静态资源目录
app.use(express.static('node_modules'))
app.get('/',(req,res) => {
  fs.readFile('./index.html', (err,data) =>{
    if (err) {
      res.statusCode = 500;
      res.end('500 Interval Serval');
    }else{
      res.statusCode = 200;
      res.setHeader('Content-Type', 'text/html');
      res.end(data);
    }
  })
})
app.get('/user', (req, res) => {
  //http://127.0.0.1:3000/user?callback=jsonCallBack
  const callback = req.query.callback;
  res.end(`${callback}(${JSON.stringify({name:'qlq'})})`)
  // res.jsonp({name:'qlq'});
})

app.listen('3000');

二:NodeJS中间价代理跨域

0 工作前准备与方法一一样

1. 创建js文件和index.html

js文件

const express = require('express');
const app = express();
const fs = require('fs');

app.use(express.static('node_modules'))
app.get('/', (req, res) => {
  fs.readFile('./index.html', (err, data) => {
    if (err) {
      res.statusCode = 500;
      res.end('500 Interval Serval');
    } else {
      res.statusCode = 200;
      res.setHeader('Content-Type', 'text/html');
      res.end(data);
    }
  })
})

app.get('/user', (req, res) => {
  res.json({name:'proxy成功跨域'})
})
app.listen('3001');

index文件

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>
<body>
  <script src="/axios/dist/axios.js"></script>
  <h2>proxy中间件代理跨域解决方案</h2>
  <script>
    axios.get('http://localhost:8080/user')
    .then(res => {
      console.log(res);
    }).catch(err => {
      console.log(err);
    }) 
  </script>
</body>
</html>

这里的目标是在http://localhost:8080中能跨域访问http://localhost:3001中的数据

2.创建一个跨域服务器

2.1 创建需要监听的端口

const express = require('express')
const app = express();
app.listen(8080);

2.2 设置允许跨域访问该服务器

app.all('*', function (req,res,next) {
  res.header('Access-Control-Allow-Origin', '*');//第二个参数是白名单
  res.header('Access-Control-Allow-Headers', 'Content-Type');
  res.header('Access-Control-Allow-Methods', '*');
  res.header('Content-Type','application/json;charset=utf-8');
  next();
});

2.3 终端下载中间件 cnpm i http-proxy-middleware -S 并且使用中间件进行转发

app.use('触发转发的虚拟目录',createProxyMiddleware( { target:'要转发的地址', changeOrigin: true }));

//下载插件 http-proxy-middleware
const { createProxyMiddleware } = require('http-proxy-middleware');
//中间件 筛子 每个请求来之后 都会转发到target 后端服务器
app.use('/', createProxyMiddleware({ target: 'http://localhost:3001', changeOrigin: true }));
ps:github上有关于http-proxy-middleware的讲解 github.com/chimurai/ht…

完整的中间件服务器

const express = require('express')
const app = express();
//下载插件 http-proxy-middleware
const { createProxyMiddleware } = require('http-proxy-middleware');

//代理服务器的操作
// 设置允许跨域访问该服务器
app.all('*', function (req,res,next) {
  res.header('Access-Control-Allow-Origin', '*');//第二个参数是白名单
  res.header('Access-Control-Allow-Headers', 'Content-Type');
  res.header('Access-Control-Allow-Methods', '*');
  res.header('Content-Type','application/json;charset=utf-8');
  next();
});

//中间件 筛子 每个请求来之后 都会转发到target 后端服务器
app.use('/', createProxyMiddleware({ target: 'http://localhost:3001', changeOrigin: true }));

app.listen(8080);

三:CORS跨域

有两种方式使用cors
首先先写index.html

<body>
  <script src="/axios/dist/axios.js"></script>

  <h2>CORS跨域</h2>

  <script>
    axios.defaults.baseURL = 'http://127.0.0.1:3002'
    axios.post('/login',{
      username:'qlq',
      password:123
    },{
      headers:{
        'X-Token':'xxxxx'
      },
      //表示跨域请求时需要使用凭证 允许携带cookies
      withCredentials:true
    })
    .then(res => {
      console.log(res);
    }).catch(err => {
      console.log(err);
    }) 

  </script>
</body>

方法1 直接用app.all配置服务器端

1.跟上面proxy中间件代理用app.all()基本一样 但是如果要做post传值,比如cookie等 就要多加一条

res.header('Access-Control-Allow-Credentials', 'true');表示允许携带证书(如果有这条 Access-Control-Allow-Origin的值不能是通配符* 不然会报错)

2.如果前端要在headers头携带x-token等其他属性 需要在Access-Control-Allow-Headers加上这些属性

const express = require('express');
const app = express();
const fs = require('fs');

//设置允许跨域访问该服务.
app.all('*', function (req, res, next) {
  /// 允许跨域访问的域名:若有端⼝需写全(协议+域名+端⼝),若没有端⼝末尾不⽤加'/'
  res.header('Access-Control-Allow-Origin', 'http://localhost:3002');
  ////允许令牌通过

  res.header('Access-Control-Allow-Headers', 'Content-Type,X-Token');
  res.header('Access-Control-Allow-Methods', '*');

  //允许携带cookie
  res.header('Access-Control-Allow-Credentials', 'true');
  res.header('Content-Type', 'application/json;charset=utf-8');
  next();
});
app.use(express.static('node_modules'))
app.get('/', (req, res) => {
  fs.readFile('./index.html', (err, data) => {
    if (err) {
      res.statusCode = 500;
      res.end('500 Interval Serval');
    } else {
      res.statusCode = 200;
      res.setHeader('Content-Type', 'text/html');
      res.end(data);
    }
  })
})

app.post('/login', (req, res) => {
  res.json({message:'cors成功跨域'})
})
app.listen('3002');

方法2.使用cors插件

1.终端cnpm i cors -S

2.引入cors模块

3.app.use(cors({配置文件}))

ps:如何配置参考 github.com/expressjs/c…
const cors = require('cors')
//cors
//设置cors中间件 允许跨域访问
app.use(cors({
  "origin": "http://localhost:3002",
  "credentials" :true
}))

如何获取post请求体中的数据

这里使用cors里面的index.html作为前端页面

app.post('/login', (req, res) => {
  console.log(req.body);
})

此时获取的打印出来的结果是undefined 参考文献: expressjs.com/zh-cn/4x/ap…
这样是无法获取post请求体中的数据 有两种方式获取

1.app.use(express.json())

如果post传来的数据时json格式可以直接读取数据

app.use(express.json())
app.post('/login', (req, res) => {
  console.log(req.body);
})

可以获取

2.app.use(express.urlencoded({ extended: true}))

如果post传来的数据时urlencoded格式才可以获取数据

2.1在服务器端app.use(express.urlencoded({ extended: true}));

app.use(express.urlencoded({ extended: true}));
app.post('/login', (req, res) => {
  console.log(req.body);
})

2.2 在前端将json格式转化为urlencoded格式

2.2.1 需要借助qs脚本

  <script src="https://cdn.bootcdn.net/ajax/libs/qs/6.9.4/qs.js"></script>

2.2.2 拦截request请求 axios.interceptors.request.use

  1. 获取config.data(request请求体数据)
  2. Qs.stringify() 将数据格式转化为urlencoded
 axios.interceptors.request.use(function (config) {
      let data = config.data;
      //Qs.stringify(data) 将json数据转化为urlencoded
      config.data = Qs.stringify(data);
      console.log(Qs.stringify(data));
      // 在发送请求之前做些什么
      return config;
    }, function (error) {
      // 对请求错误做些什么
      return Promise.reject(error);
    });

即可获取request请求体的数据

完整代码index.html

<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>

<body>
  <script src="/axios/dist/axios.js"></script>
  <!-- qs -->
  <script src="https://cdn.bootcdn.net/ajax/libs/qs/6.9.4/qs.js"></script>
  <h2>CORS跨域</h2>

  <script>
    axios.defaults.baseURL = 'http://127.0.0.1:3002'
    axios.interceptors.request.use(function (config) {
      let data = config.data;
      //Qs.stringify(data) 将json数据转化为urlencoded
      config.data = Qs.stringify(data);
      console.log(Qs.stringify(data));
      // 在发送请求之前做些什么
      return config;
    }, function (error) {
      // 对请求错误做些什么
      return Promise.reject(error);
    });

    axios.post('/login', {
      username: 'qlq',
      password: 123
    }, {
      headers: {
        'X-Token': 'xxxxx'
      },
      //表示跨域请求时需要使用凭证 允许携带cookies
      withCredentials: true
    })
      .then(res => {
        console.log(res);
      }).catch(err => {
        console.log(err);
      })

  </script>
</body>

</html>

服务器端js文件

const express = require('express');
const app = express();
const fs = require('fs');
const cors = require('cors')
//cors
//设置cors中间件 允许跨域访问
app.use(cors({
  "origin": "http://localhost:3002",
  "credentials" :true
}))

// app.use(express.json()) //json格式

//username=qlq&password=123
app.use(express.urlencoded({ extended: true})) //urlencoded格式 

// //设置允许跨域访问该服务.
// app.all('*', function (req, res, next) {
//   /// 允许跨域访问的域名:若有端⼝需写全(协议+域名+端⼝),若没有端⼝末尾不⽤加'/'
//   res.header('Access-Control-Allow-Origin', 'http://localhost:3002');
//   ////允许令牌通过

//   res.header('Access-Control-Allow-Headers', 'Content-Type,X-Token');
//   res.header('Access-Control-Allow-Methods', '*');

//   //允许携带cookie
//   res.header('Access-Control-Allow-Credentials', 'true');
//   res.header('Content-Type', 'application/json;charset=utf-8');
//   next();
// });
app.use(express.static('node_modules'))
app.get('/', (req, res) => {
  fs.readFile('./index.html', (err, data) => {
    if (err) {
      res.statusCode = 500;
      res.end('500 Interval Serval');
    } else {
      res.statusCode = 200;
      res.setHeader('Content-Type', 'text/html');
      res.end(data);
    }
  })
})

app.post('/login', (req, res) => {
  //如何获取post请求体中的数据
  console.log(req.body);
  res.json({message:'cors成功跨域'})
})
app.listen('3002');

四:nginx反向代理

实现原理类似于Node中间件处理,需要搭建一个中转nginx服务器,用于转发请求。

// proxy服务器
server {
  listen 81;
  server_name www.domain1.com;
  location / {
    proxy_pass http://www.domain2.com:8080; //#反向代理
    proxy_cookie_domain www.domain2.com www.domain1.com; //#修改cookie⾥域名
    index index.html index.htm;
    // 当⽤webpack-dev-server等中间件代理接⼝访问nignx时,此时⽆浏览器参与,故没有同源限制,下⾯的跨域配置可不启⽤
    add_header Access-Control-Allow-Origin http://www.domain1.com;//当前端只跨域不带cookie时,可为*
    add_header Access-Control-Allow-Credentials true;
  }
}