Top
Fork me on Gitee My Github

前端跨域问题各种解决方式及原理

跨域的各种解决方式及原理

原因

因为浏览器有某些安全级别的限制,例如,同源策略,所以在进行浏览器端的web应用开发的时候,经常会遇到跨域问题。

同源策略:只有在同源的情况下(同域名,同协议,同端口)才能进行数据交互

跨域问题报错:XMLHttpRequest cannot load https://api.douban.com/v2/movie/in_theaters. No 'Access-Control-Allow-Origin' header is present on the requested resource. Origin 'http://127.0.0.1:5500' is therefore not allowed access.

当我们在公司开发的时候,很可能因为后端的服务器和我们的本地服务器不同源所以产生跨域问题,还有当我们公司的服务器有很多,其中很可能会有单独的数据服务器,开发应用的时候必然会有跨域,所以解决跨域问题是前端工程师必要的技能。

常用的跨域方式:jsonp,cors,服务端代理

  • jsonp(JSON with Padding)

jsonp是一种前后端结合的跨域问题解决方式

原理:动态的创建script标签,将script标签的src属性设置成请求的目标地址,和后端商议之后,设置callback回调函数,利用回调函数来接收数据并进行使用

为什么使用script:

依靠html中标签的src属性不受同源策略的影响来实现的,而script标签接收到数据之后可以进行数据的处理,所以一般选用script标签

为什么要动态的创建script标签:

script只能执行一次或者说只能请求一次,所以说当我们要不断的进行jsonp请求的时候,每一次的请求都需要一个script标签,所以需要动态去创建script标签,script标签能将请求到的字符串数据当成js代码去运行,所以我们可以依靠后端返回一段(执行某个函数,且给此函数传入数据)的这样一段字符串来实现,这样的话,script请求到该字符串之后,就会执行该函数,且该函数能接收到数据。

为什么需要和后端商议之后才能设置callback函数:

因为后端开发者并不知道前端准备来接收数据的函数是哪个函数,所以需要让前端通知后端接收数据的函数名,前端需要将函数名传递给后端,但是传递的时候是以键值对的方式传递过去的,所以需要前端将键值对的键名事先告知后端

注意:获取数据且操作完成后,一定要将创建的script标签去掉,将随机函数的内存给释放

缺点:只能做get请求

  • CORS

这是一种纯后端的跨域方式

因为每次请求的时候请求头信息都会被携带到目标服务器,目标服务器经过判断选择是否运行访问,不允许的时候就出现了跨域问题,所以我们可以可以让后端开发人员对后端目标服务器进行设置,使其能识别我们的服务器的请求头并允许访问,这就是cors,cors主要是靠后端设置:Access-Control-Allow-Origin

node:

res.setHeader("Access-Control-Allow-Origin", "*");
res.setHeader("Access-Control-Allow-Methods","PUT,POST,GET,DELETE,OPTIONS");
res.setHeader("Access-Control-Allow-Headers", "Content-Type,Content-Length, Authorization, Accept,X-Requested-With");
res.writeHead(200,{'Content-Type':'application/json; charset=utf-8'})
let data = {status:1,data:'hello world'}
res.end(JSON.stringify(data))
  • proxy

这也属于服务器代理的跨域处理方式

因为服务端之间的数据请求没有跨域限制,所以我们借助一个中间的代理服务器去向目标服务器发送请求:

前端代码:

let target = 'https://api.douban.com/v2/book/search'
let proxy = 'http://localhost:3000'
$.ajax({
    url: proxy+'/v2/book/search', // 向proxy服务器发送请求,后面的path之类的其实是target域的path
    data: {
    target: 'https://api.douban.com', // 告诉proxy服务器,我们真正像请求的目标服务器是谁
    tag: '励志'
  },
  success(result) {
   console.log(result)
  }
})

代理服务器:

const http = require('http')
const url_util = require('url')
const path = require('path')

const cross = require('./module/cross')

http.createServer((req,res)=>{
res.setHeader("Access-Control-Allow-Origin", "*");
res.setHeader("Access-Control-Allow-Methods","PUT,POST,GET,DELETE,OPTIONS");
res.setHeader("Access-Control-Allow-Headers", "Content-Type,Content-Length, Authorization, Accept,X-Requested-With");

// http://localhost:3000/v2/book/search?target=..params=...
let url_info = url_util.parse(req.url,true)

let {target} = url_info.query

delete url_info.query.target

let params = url_info.query
// 向真正的目标服务器发送请求
let real_url = target + url_info.pathname

let method = req.method

cross(real_url, method, params, (result) => {
  res.writeHead(200,{'Content-Type':"application/json;charset=utf8"})
  res.end(JSON.stringify(result))
})

}).listen(3000)

cross跨域模块工具:

const url_util = require('url')
const qs = require('querystring')
const cross = (url,method,params,cb) => {
//向url发送请求
//目标源的协议
let url_info = url_util.parse(url)

let protocol = url_info.protocol.replace(':','')
//引入到对应的模块
let http = require(protocol)

let data = qs.stringify(params)

let options = {
  hostname: url_info.hostname,
  port: url_info.port || ((protocol) => {return protocol == 'http'?80:443})(protocol),
  path: url_info.path,
  method: method
}

if (method=='post') {
  options.headers = {
    'Content-Type': 'application/x-www-form-urlencoded',
    'Content-Length': data.length
  }
}else{
  let symbol = url_info.path.indexOf('?') >=0 ? '&':'?'
  options.path = url_info.path+symbol+data
  console.log(options.path)
}

let req = http.request(options, (res) => {
let result = ''
  res.on('data', (chunk) => {result += chunk})
  res.on('end', (chunk) => {cb(result)})
})

if (method == 'post') {
  req.write(data)
}

req.end()

}

module.exports = cross
posted @ 2018-05-08 11:58  lisashare  阅读(706)  评论(0)    收藏  举报