# 前端通信

# 同源策略

同源策略限制从一个源加载的文档或脚本如何与来自另一个源的资源进行交互。这是一个用于隔离潜在恶意文件的关键安全机制。

什么是源:协议、域名和端口。这三者任一不同,就算是跨域

什么是限制:不是一个源的文档,没有权限去操作另一个源的文档,如:

  • cookie、localStorage 和 indexDB 无法读取
  • DOM 无法获得
  • Ajax 请求发送成功,但是响应会被浏览器拦截

# 跨域通信方式

  1. JSONP(只支持 get 请求)

    通过 script 标签的异步加载实现,利用 script 标签不受同源策略的限制,天然可以跨域的特性

    const script = document.createElement('script')
    script.type = 'text/javascript'
    script.src = 'https://www.mock-api.com/?callback=jsonp' // 接口地址
    
    document.head.appendChild(script)
    
    function jsonp(...res){
      console.log(res)
    }
    
  2. Hash

    url # 后面的内容就叫 Hash。Hash 改变,页面不会刷新

    // 在 A 中伪代码
    const B = document.getElementsByTagName('iframe')
    B.src = B.src + '#' + 'data'
    
    // 在 B 中的伪代码
    window.onhashchange = function() {
      const { data } = window.location
    }
    
  3. postMessage

    H5 新增的 postMessage() 方法,可以用来做跨域通信

    // 在 A 窗口
    const url = '...'
    const Bwindow = window.open(url)
    Bwindow.postMessage('data', url)
    
    // 在 B 窗口
    window.addEventListener('message', function(event) {
      console.log(event.origin) // A 窗口 url
      console.log(event.source) // A 窗口 window 对象
      console.log(event.data) // A 窗口传过来的数据
    }, false)
    
  4. WebSocket

    WebSocket protocol 是 HTML5 一种新的协议,它实现了浏览器与服务器全双工通信,同时允许跨域通讯,是 server push 技术的一种很好的实现

    const ws = new WebSocket('wss://echo.websocket.org')
    
    ws.onopen = function(event) {
      console.log('Connection open ...')
      ws.send('Hello WebSockets')
    }
    
    ws.onmessage = function(event) {
      console.log('Received Message: ', event.data)
    	ws.close()
    }
    
    ws.onclose = function(event) {
      console.log('Connection closed')
    }
    
  5. CORS(现代浏览器普遍跨域解决方案)

    整个 CORS 通信过程都是浏览器自动完成,不需要用户参与。对于开发者来说,CORS 通信与同源的 AJAX 通信没有差别,代码完全一样。浏览器一旦发现 AJAX 请求跨域,就会自动添加一些附加的头信息,有时还会多出一次附加的请求,但用户不会有感觉。因此,实现 CORS 通信的关键是服务器,只要服务器实现了 CORS 接口,就可以跨域通信

    CORS 需要浏览器和服务端同时支持。IE 8 和 9 需要通过 XSDomainRequest 来实现。

    通过这种方式解决跨域问题,会在发送请求的时候出现两种情况,分别为 简单请求复杂请求

    1. 简单请求

      只要同时满足以下两大条件,就属于简单请求:

      1. 使用下列方法之一:
        • GET
        • HEAD
        • POST
      2. Content-Type 的值仅限于以下三者之一:
        • text / plain
        • multipart / form-data
        • application / x-www-form-urlencoded

      请求中的任意 XMLHttpRequestUpload 对象均没有注册任何事件监听器;

      XMLHttpRequestUpload 对象可以使用 XMLHttpRequest.upload 属性访问。

    2. 复杂请求

      不符合以上条件的就是复杂请求。复杂请求的 CORS 请求,会在正式通信之前,增加一次 HTTP 查询请求,称为 预检请求 ,该请求的方法是 Option,通过该请求来查询服务端是否允许跨域请求。

# 服务端实现 CORS 的方式

# Node.JS

// 以 express 为例
app.all('*', function(req, res, next) {
  res.header('Access-Control-Allow-Origin', '*')
  res.header('Access-Control-Allow-Headers', 'X-Requested-With')
  res.header('Access-Control-Allow-Methods', 'PUT,POST,GET,DELETE,OPTIONS')
  next()
})

# Nginx

server {
  add_header Access-Control-Allow-Credentials true;
  add_header Access-Control-Allow-Origin $http_origin;
  
  location /file {
    if($request_method = 'OPTIONS') {
      add_header Access-Control-Allow-Origin $http_origin;
      add_header Access-Control-Allow-Methods $http_access_control_request_methods;
      add_header Access-Control-Allow-Credentials true;
      add_header Access-Control-Allow-Headers $http_access_control_request_headers;
      add_header Access-Control-Max-Age 1728000;
      return 204;
    }
  }
}