跨域的解决办法

跨域

1. jsonp

  • jsonp是一种跨域通信手段,通过script标签的src属性实现跨域,由于浏览器同源策略,并不会截断script的跨域响应
  • 通过将前端方法名作为参数传递到服务器端,然后由服务器端注入参数之后再返回,实现服务器端向客户端通信
  • 由于使用script标签的src属性,因此只支持get方法
// 前端准备
// 定义回调函数
function fn(arg) {
  // arg为服务端传来的数据
  console.log(`客户端获取的数据:${arg}`)
}
// 创建script标签
const s = document.createElement('script')
// 给script标签的src属性赋值,值为请求url,查询参数callback,需与后端对应
// fn为前端回调函数名
s.src = `http://127.0.0.1:3000/test?callback=fn`
// 向html添加此标签,添加完成之后浏览器自动请求script的src对应的网址
document.getElementsByTagName('head')[0].appendChild(s);
// 等待浏览器收到响应之后,将会自动执行响应内容的代码
----------------------------------------------
// 后端准备
// nestjs(ts)处理
@Controller('test') //api
export class TestController {
  @Get() //get方式请求
  //取url中的查询参数,即?之后的键值对,键与值对应query对象参数的键与值
  callback(@Query() query) {  
    // 返回的数据
    const data = '我是服务端返回的数据';
    // 取查询参数,这里的callback要与前端?之后的键名一致,fn即fn函数名
    const fn = query.callback;
    // 返回结果,格式:函数名(服务器的数据),注意这里需要序列化成字符串,如果参数本身是字符串那么要加引号,前端并不知道data是字符串
    return `${fn}('${data}')`;
  }
}

// express(js)处理,同上
router.get('/test', async (req, res) => {
  const data = '我是服务器返回的数据'
  // req.query为查询参数列表
  const fn = req.query.callback
  // 返回数据
  res.send(`${fn}('${data}')`)
})

响应内容
响应内容

2. CORS

  • 跨域资源共享cors,它使用额外的 HTTP 头来告诉浏览器,让运行在一个 origin (domain) 上的Web应用被准许访问来自不同源服务器上的指定的资源
  • 需要服务端与客户端同时支持cors跨域方式才能进行跨域请求,服务端通过设置

Access-Control-Allow-Origin:

即可开启cors允许跨域请求,使用通配符表示允许所有不同域的源访问资源,也可单独设置指定允许的源域名

  • 使用cors跨域时,将会在发起请求时出现2种情况:

  • 简单请求,需满足以下条件

    • 使用get、head、post方式发起的请求
    • Content-Type 的值仅限于下列三者之一:
      - text/plain
      - multipart/form-data
      - application/x-www-form-urlencoded
    • 不满足这些条件即为预检请求
  • 预检请求

    • 需预检的请求要求必须首先使用OPTIONS方法发起一个预检请求到服务器,以获知服务器是否允许该实际请求
    • 预检请求的使用,可以避免跨域请求对服务器的用户数据产生未预期的影响
    • 当满足以下条件之一,将会发送预检请求
      PUT
      DELETE
      CONNECT
      OPTIONS
      TRACE
      PATCH
  • 人为设置了对 CORS 安全的首部字段集合之外的其他首部字段。该集合为:
    Accept
    Accept-Language
    Content-Language
    Content-Type (需要注意额外的限制)
    DPR
    Downlink
    Save-Data
    Viewport-Width
    Width

  • Content-Type 的值不属于下列之一:
    application/x-www-form-urlencoded
    multipart/form-data
    text/plain

  • 满足以上条件之一将会发起预检请求,总共会发起2次请求,第一次为OPTIONS方式的请求,用来确定服务器是否支持跨域,如果支持,再发起第二次实际请求,否则不发送第二次请求

3. postMessage

  • postMessage可用于不同页面之间的跨域传递数据
  • postMessage(data,origin[, source]) data为发送的数据只能发送字符串信息,origin发送目标源,指定哪些窗口能接收到消息事件,如果origin设置为*则表示无限制,source为发送消息窗口的window对象引用,
<!-- test.html -->
<iframe src="http://127.0.0.1:5501/postMessage.html"
name="postIframe" onload="messageLoad()"></iframe>
<script>
// 定义加载之后执行的函数,给postMessage.html发送数据
function messageLoad() {
  const url = 'http://127.0.0.1:5501/postMessage.html'
  window.postIframe.postMessage('给postMessage的数据', url)
}
// 用于监听postMessage.html的回馈,执行回调
window.addEventListener('message', (event) => {
  console.log(event.data);
})
</script>
----------------------------------------------
<!-- postMessage.html -->
<script>
  // 监听test.html发来的数据,延迟1秒返回数据
  window.addEventListener('message', (event) => {
    setTimeout(() => {
      event.source.postMessage('给test的数据', event.origin)
    },1000)
  })
</script>
  • event对象的几个重要属性
    1.data 指的是从其他窗口发送过来的消息对象
    2.type 指的是发送消息的类型
    3.source 指的是发送消息的窗口对象
    4.origin 指的是发送消息的窗口的源

4. window.name

  • 由于window.name属于全局属性,在html中的iframe加载新页面(可以是跨域),通过iframe设置的src指向的源中更改name的值,同时主页面中的name也随之更改,但是需要给iframe中的window设置为about:blank或者同源页面即可
  • iframe使用之后应该删除,name的值只能为string类型,且数据量最大支持2MB
<!-- test.html -->
// 封装应该用于获取数据的函数
function foo(url, func) {
  let isFirst = true
  const ifr = document.createElement('iframe')
  loadFunc = () => {
    if (isFirst) {
      // 设置为同源
      ifr.contentWindow.location = 'about:blank'
      isFirst = false
    } else {
      func(ifr.contentWindow.name)
      ifr.contentWindow.close()
      document.body.removeChild(ifr)
    }
  }
  ifr.src = url
  ifr.style.display = 'none'
  document.body.appendChild(ifr)
  // 加载之后的回调
  ifr.onload = loadFunc
}
foo(`http://127.0.0.1:5501/name.html`, (data) => {
  console.log(data) //
})
----------------------------------------------
<!-- name.html -->
const obj = { name: "iframe" }
// 修改name的值,必须为string类型
window.name = JSON.stringify(obj); 

5.document.domain

  • document.domain的值对应当前页面的域名
  • 通过对domain设置当前域名来实现跨域,不过仅限于域名不同,但是又要属于同一个基础域名下,如http://a.baidu.comhttp://b.baidu.com这2个子域名之间才能使用domain跨域,一般用于子域名之间的跨域访问
  • domain只能赋值为当前域名或者其基础域名,即上级域名
<!-- test.html -->
<script>
document.domain = 'baidu.com';
const ifr = document.createElement('iframe');
ifr.src = 'a.baidu.com/test.html';
ifr.style.display = 'none';
document.body.appendChild(ifr);
ifr.onload = function(){
  var doc = ifr.contentDocument || ifr.contentWindow.document;
  // 此处即可操作domain.html的document
  ifr.onload = null;
};
</script>
----------------------------------------------
<!-- domain.html -->
<script>
  // domain.html下设置为与test.html中的domain一致
  document.domain = 'baidu.com';
</script> 
  • 主要就是通过设置为同源域名(只能为其基础域名),通过iframe操作另一个页面的内容

6.nginx反向代理

  • nginx反向代理,代理从客户端来的请求,转发到其代理源
  • 通过配置nginx的配置文件实现代理到不同源
// nginx.conf配置
server {
  listen 80;  // 监听端口
  server_name  www.baidu.com; // 匹配来源
  location / {  //匹配路径
    // 反向代理到http://127.0.0.1:3000
    proxy_pass http://127.0.0.1:3000;
    // 默认入口文件
    index  index.html index.htm index.jsp;
} 
  • nginx反向代理还能实现负载均衡
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值