前端基础重点回顾4:前后端通信

同源策略及限制

同源策略的概念

  • 同源:http协议,域名, 端口三者均相同
  • 同源策略是用来限制在一个源上加载的文档或脚本与来自另一个源的资源进行交互。这是一个用于隔离潜在恶意文件的关键的安全机制

同源策略的限制

  • cookie localStorage indexDB 无法读取
  • dom 无法获得 ajax请求不能发送

前后端通信的常见几种方式

  • Ajax(同源通信)
  • WebSocket(协议不同的不同源通信)
  • CORS(用于支持不同源之间ajax通信的方法)

Ajax通信

参考

Ajax 概念

  • Ajax(Async JavaScript And XML)是一种依赖CSS/HTML/JAVASCRIPT 等现有技术使用XMLHttpRequest
    对象发送http 请求并接受响应的一种技术方案

实现一个Ajax

/**
 * {string} param.url
 * {string} param.type? || 'get'
 * {object} param.data
 * {function} param.success
 * {function} param.error
 */
var ajax = function(param) {
    var xhr = XMLHttpRequest ? new XMLHttpRequest() : new ActiveXObject("Microsoft.XMLHTTP")
    var type = (param.type ||  'get').toUpperCase()
    var url = param.url
    if(!url) return
    var data = param.data
    var dataArr = []
    for (var k in data) {
      dataArr.push(k + '=' + data[k])
    }

    if (type === 'GET') {
      url = url + '?' + dataArr.join('&')
      xhr.open(type, url)
      xhr.send()
    }

    if (type === 'POST') {
      xhr.open(type, url)
      xhr.setRequestHeader("Content-type", "application/x-www.form-urlencoded")
      xhr.send(dataArr.join('&'))
    }
    
    xhr.onreadystatechange = function () {
      if (xhr.readyState === 4) {
        if (xhr.status === 200 || xhr.status === 304) {
          var res
          if (param.success && typeof param.success === 'function') {
            res = xhr.responseText
            if (typeof res === 'string') {
              res = JSON.parse(res)
              param.success.call(xhr, res)
            }
          }
        } else {
          param.error.call()
        }
      }
    }
}

跨域通信的几种方式

  • 首先我们先给host 设置几个子域名来模拟跨域


跨域代码示例

$ npm install
$ npm start
port: 3000 
使用示例前记得设置本机host

JSONP

jsonp 原理就是在页面上动态添加一个script标签,给标签的src 指定一个url 路径并加上回调函数query 参数,发送给后端后,后端利用需返回的数据和回调函数的query 参数拼接成类似handleJsonp({ a:1, b:2 })的字符串返回前端,前端定义的handleJsonp 的函数会直接运行并处理{ a:1, b:2 } 这个后端返回的数据

  • 只能发送GET请求
  • 可能会被注入恶意代码 callback=alter('111')
  • 任何域都可以发送jsonp请求,需进行验证,如token
// 前端代码
      jsonpBtn.addEventListener('click', function() {
        const script = document.createElement('script')
        script.src = 'http://b.yang.com:3000/jsonp?callback=handleJsonp'
        document.head.appendChild(script)
        // document.head.removeChild(script)
      })

      function handleJsonp(data) {
        console.log(data)
      }
// 后端代码
// JSONP
router.get('/jsonp', function(req, res, next) {
  let { callback: cb } = req.query
  const data = {
    type: 'jsonp',
    data: 'data'
  }
  cb = cb.replace(/\(/g, ''); // 替换掉() 防止恶意代码注入
  cb = cb.replace(/\)/g, '');
  res.send(cb + '(' + JSON.stringify(data) + ')')
})

CORS

  • CORS(cross-origin resource sharing) 跨域资源共享,是一种ajax 跨域请求资源的方式, 普遍用于前后端分离开发环境
  • 原理就在于Access-Control-Allow-Origin 响应头,它指定浏览器在何种域下发送的ajax 请求服务器资源时可以跨域
  • 服务器响应还可以设置其它header:
    1Access-Control-Allow-Methods: POST, GET, OPTIONS表明服务器允许客户端使用 POST, GET 和 OPTIONS 方法发起请求
    2Access-Control-Allow-Headers: X-PINGOTHER, Content-Type表明服务器允许请求中携带字段 X-PINGOTHER 与 Content-Type
    3Access-Control-Max-Age: 86400表明该响应的有效时间为 86400 秒
    4Access-Control-Allow-Credentials: true 表明跨域请求允许携带cookie
    MDN
// 前端代码
     cors.addEventListener('click', function() {
        let reqHeaders = new Headers()
        reqHeaders.append('Content-Type', 'application/x-www-form-urlencoded')
        fetch('http://b.yang.com:3000/cors/', {
          method: 'post',
          headers: reqHeaders,
          mode: 'cors',
          body: 'post body'
        }).then(function (response) {
          console.log(response)
        })
      })
// 后端代码
// CORS
router.post('/cors', function(req, res, next) {
  // res.header('Access-Control-Allow-Origin', 'http://a.yang.com:3000')
  res.header('Access-Control-Allow-Origin', '*')
  res.send('cors ok')
})

WebSocket

利用websocket 协议进行前后端跨域通信

// 前端代码
      var ws
      socket.addEventListener('click', function() {
        ws = new WebSocket(`ws://b.yang.com:3000/`)
        ws.onmessage = (data) => console.log(data);
        ws.onerror = () => console.log('WebSocket error');
        ws.onopen = () => console.log('WebSocket connection established');
        ws.onclose = () => console.log('WebSocket connection closed');
      })
      sendmsg.addEventListener('click', function() {
        ws.send('send a msg')
      })
// 后端代码
var express = require('express');
var app = express();
const WebSocket = require('ws')
var server = http.createServer(app);

const wss = new WebSocket.Server({ server })
wss.on('connection', (ws, req) => {
  ws.on('message', message => {
    console.log(message)
    ws.send(message)
  })
})
server.listen(3000)

降域(使用iframe)

// URL: http://a.yang.com:3000/a
<div class="ct">
  <h1>使用降域实现跨域</h1>
  <div class="main">
    <h4>URL: http://a.yang.com:3000/a</h4>
    <input type="text" placeholder="http://a.yang.com:3000/a">
  </div>
  <iframe src="http://b.yang.com:3000/b" frameborder="0" ></iframe>
</div>

<script>
  document.querySelector('.main input').addEventListener('input', function(){
    console.log(location.host, this.value);
    window.frames[0].document.querySelector('input').value = this.value;
  })
  document.domain = "yang.com"
</script>
// URL: http://b.yang.com:3000/b
<input id="input" type="text"  placeholder="http://b.yang.com:3000/b">
<script>
  document.querySelector('#input').addEventListener('input', function(){
    console.log(location.host, this.value);
    window.parent.document.querySelector('input').value = this.value;
  })
  document.domain = 'yang.com';
</script>

postMessage(使用iframe)

//URL: http://a.yang.com:3000/c
<div class="ct">
  <h1>使用postMessage实现跨域</h1>
  <div class="main">
    <h4>URL: http://a.yang.com:3000/c</h4>
    <input type="text" placeholder="http://a.yang.com:3000/c">
  </div>
  <iframe src="http://localhost:3000/d" frameborder="0" ></iframe>
</div>

<script>
  var input = document.querySelector('.main input')
  input.addEventListener('input', function(){
    console.log('a.yang.com - input event value', this.value);
    window.frames[0].postMessage(this.value,'*');
  })
  window.addEventListener('message',function(e) {
    input.value = e.data
    console.log('a.yang.com - message event value', e.data);
  });
</script>
// URL: http://b.yang.com:3000/d
<input id="input" type="text"  placeholder="http://b.yang.com:3000/d">
<script>
  var input = document.querySelector('#input')
  input.addEventListener('input', function(){
    console.log('b.yang.com - input event value', this.value);
    window.parent.postMessage(this.value, '*');
  })
  window.addEventListener('message',function(e) {
    input.value = e.data
    console.log('b.yang.com - message event value', e.data);
  });

</script>

其他hack

改变hash值