跨域的几种实现方式

讲到跨域不得不说一说,为什么要跨域?--浏览器的同源策略。

浏览器的同源策略(Same Origin Policy)

源(Origin)是由 URL 中协议、主机名(域名 domain)以及端口共同组成的部分。在上面的网址中,源由协议 http、主机名www.baidu.com 和默认端口 8080 共同组成。

 

如果两个 URL 的源相同,我们就称之为同源。下面的 3 个 URL 和示例 URL 都是不同的源。

 

  • https://www.baidu.com:8080/other       协议不同
  • http://www.baidu.com:80/other            端口不同
  • http://www.tengxun.com:8080/other     主机名(域名)不同

当一个源访问另一个源的资源时就会产生跨源。同源策略就是用来限制其中一些跨源访问的,包括访问 iframe 中的页面、其他页面的 cookie 访问以及发送 AJAX 请求。最常见的跨源场景是域名不同,即常说的“跨域”。

跨域解决方案

  • 跨域资源共享

  跨域资源共享(CORS,Cross-Origin Resource Sharing)是浏览器为 AJAX 请求设置的一种跨域机制,让其可以在服务端允许的情况下进行跨域访问。

  主要通过 HTTP 响应头来告诉浏览器服务端是否允许当前域的脚本进行跨域访问。

  跨域资源共享将 AJAX 请求分成了两类:简单请求非简单请求

  简单请求

    • 请求方法为 GET、POST、HEAD。
    • 请求头只能使用下面的字段:Accept(浏览器能够接受的响应内容类型)、Accept-Language(浏览器能够接受的自然语言列表)、Content-Type (请求对应的类型,只限于 text/plain、multipart/form-data、application/x-www-form-urlencoded)、Content-Language(浏览器希望采用的自然语言)、Save-Data(浏览器是否希望减少数据传输量)。

    处理流程:

    1. 浏览器发出简单请求的时候,会在请求头部增加一个 Origin 字段,对应的值为当前请求的源信息;
    2. 当服务端收到请求后,会根据请求头字段 Origin 做出判断后返回相应的内容。
    3. 浏览器收到响应报文后会根据响应头部字段 Access-Control-Allow-Origin 进行判断,这个字段值为服务端允许跨域请求的源,其中通配符“*”表示允许所有跨域请求。如果头部信息没有包含 Access-Control-Allow-Origin 字段或者响应的头部字段 Access-Control-Allow-Origin 不允许当前源的请求,则会抛出错误。

  非简单请求

    只要有一条要求不符合的即为非简单请求。

    处理流程:

    当处理非简单的请求时,浏览器会先发出一个预检请求(Preflight)。这个预检请求为 OPTIONS 方法,并会添加了 1 个请求头部字段 Access-Control-Request-Method,值为跨域请求所使用的请求方法。

  • JSONP

  JSONP(JSON with Padding)的大概意思就是用 JSON 数据来填充,怎么填充呢?结合它的实现方式可以知道,就是把 JSON 数填充到一个回调函数中。

  依赖的是 script 标签跨域引用 js 文件不会受到浏览器同源策略的限制。

  具体实现方式:

  假设我们要在 http://www.1.com 中向 http://www.2.com 请求数据。

    1.申明一个回调函数fn,参数为请求返回的数据

function fn(result) {
  console.log(result)
}

    2.将函数名与其他参数一并写入 URL 中

var url = 'http://www.2.com?callback=fn&params=...';

    3.创建一个 script 标签,把 URL 赋值给 script 的 src

var script = document.createElement('script');
script.setAttribute("type","text/javascript");
script.src = url;
document.body.appendChild(script);

    4.当服务器接收到请求后,解析 URL 参数并进行对应的逻辑处理,得到结果后将其写成回调函数的形式并返回给浏览器

fn({
 attribut: '',
  ...
})

    5.在浏览器收到请求返回的 js 脚本之后会立即执行文件内容,即在控制台打印传入的数据内容

  JSONP存在以下几个问题:

  1. 只能发送 GET 请求,限制了参数大小和类型;
  2. 请求过程无法终止,导致弱网络下处理超时请求比较麻烦;
  3. 无法捕获服务端返回的异常信息。
  • Websocket

   它是HTML5 规范提出的一个应用层的全双工协议,适用于浏览器与服务器进行实时通信场景。

  “双工”是指从客户端到服务端,以及从服务端到客户端两个方向都可以通信,“全”指的是通信双方可以同时向对方发送数据,“工”指的是通信方向。

  案例:在 a 网站直接创建一个 WebSocket 连接,连接到 b 网站即可,然后调用 WebScoket 实例 ws 的 send() 函数向服务端发送消息,监听实例 ws 的 onmessage 事件得到响应内容。

var ws = new WebSocket("ws://b.com");
ws.onopen = function(){
  // ws.send(...);
}
ws.onmessage = function(e){
  // console.log(e.data);
}
  • 代理转发

  跨域是为了突破浏览器的同源策略限制,既然同源策略只存在于浏览器,那可以换个思路,在服务端进行跨域,比如设置代理转发。这种在服务端设置的代理称为“反向代理”,对于用户而言是无感知的。

  另一种在客户端使用的代理称为“正向代理”,主要用来代理客户端发送请求,用户使用时必须配置代理服务器的网址,比如常用的 VPN 工具就属于正向代理。

  代理转发实现起来非常简单,在当前被访问的服务器配置一个请求转发规则就行了。

  下面的代码是 webpack-dev-server 配置代理的示例代码。当浏览器发起前缀为 /api 的请求时都会被转发到 http://localhost:1000 这个网址,然后将响应结果返回给浏览器。对于浏览器而言还是请求当前网站,但实际上已经被服务端转发。

// webpack.config.js
module.exports = {
  //...
  devServer: {
    proxy: {
      '/api': 'http://localhost:1000'
    }
  }
};

页面跨域解决方案

  除了浏览器请求跨域之外,页面之间也会有跨域需求,例如使用 iframe 时父子页面之间进行通信。

  HTML5 推出了一个新的函数 postMessage() 用来实现父子页面之间通信,而且不论这两个页面是否同源。

  举例来说,如果父页面 http://a.com 要向子页面 http://b.com发消息,可以通过下面的代码实现。

// http://a.com
var child = window.open('http://b.com');
child.postMessage('hi', 'http://b.com');

  通过 window.open() 函数打开了子页面,然后调用 child.postMessage() 函数发送了字符串数据“hi”给子页面。

  在子页面中,只需要监听“message”事件即可得到父页面的数据。代码如下:

// http://b.com
window.addEventListener('message', function(e) {
  console.log(e.data);
},false);

  同样的,父页面也可以监听“message”事件来接收子页面发送的数据。子页面发送数据时则要通过 window.opener 对象来调用 postMessage() 函数。

// http://b.com
window.opener.postMessage('hello', 'http://a.com');

完结。

posted @ 2020-09-01 15:37  季风吹向大海  阅读(432)  评论(0编辑  收藏  举报