HTTP: 跨域真的有这么难吗

前言

以前写前端小项目的时候就听说过跨域这个词,什么 JSONP啊,CORS啊。感觉很高级,但是无奈项目太小没机会用上。今天就写篇博客总结一下常用的跨域操作。

为什么要跨域

一般来说写项目的时候都是自己给自己发请求的,如 localhost:8080 发了一个 /users 请求,本质上就是向 localhost:8080/users 发请求,这是被允许的。但是如果给支付宝发是不行的,如 post /zhifubao/sendMoney/user?money=1000,如果允许的话那每个人装一个 postman 不断发请求就可以去支付宝偷钱了(不考虑验证情况下)。所以呢跨域请求是不好的,但是在某些条件下是可以的。像大家都是兄弟公司,虽然我们域名不一样但是我们有 py 交易呀,数据都共享,你发请求给我,我也发请求给你,这就需要跨域请求了。

JSONP

JSONP 其实真名叫:JSON + Padding。JSON 大家都知道跟 JS 对象差不多,是一种通信格式。至于 Padding ,emmm 如果你知道这个原因就感觉很 SB。

为什么会有 JSONP

JSONP 本来不是用来解决跨域问题的,是用来实现发请求时不重刷页面的,这都是 Ajax 出现前的一个技术,或者说是一个替代品,只不过刚好可以用来实现跨域请求。

前端实现

实现思路是

  1. 定义一个 loadData 函数在全局
function loadData(data) {
   console.log(data)
}
  1. 创建一个 <script> 标签,并在 src 加上请求 url 如: /hello?callback=loadData
  2. 添加这个 <script> 标签到 document.body 上。一旦添加了浏览器马上会发一个 /hello?callback=loadData 的 GET 请求
  3. 等服务器响应后,就会执行返回的 JS 代码。一般来说这 JS 代码就是这个 loadData(data),这个 data 是服务器添加上去的,至于怎么返回 JS 代码请看服务端实现

后端实现

  1. 首先获取查询参数里的 callback(回调函数的名字,这里就是 loadData)
  2. 然后生成要返回的数据
  3. 将数据和前端提取出来的回调函数结合,生成 JS 代码,返回到浏览器,浏览器一收到就会执行
app.get('/hello', (req,res) => {
  let callback = req.query.callback;
  let obj = {
    userName: 'Jack',
    password: '123456'
  };
  res.writeHead(200, {"Content-Type": "text/javascript"});
  res.end(callback + '(' + JSON.stringify(obj) + ')');
})

好了,这里说下 Padding 是啥,你看到 userNamepassword 前面的字符,那就是 Padding,没了。JSON 是因为传入回调函数的数据一般为 JSON 格式。

CORS

CORS 全名是 Corss Origin Resource Sharing。这种方法比较简单,因为前端发请求就好了,后端也正常返回数据。要改的是后端在返回响应时要添加一个响应头。

app.post('/hello',(req,res) => {
  if(req.headers.origin){
    res.writeHead(200, {
      "Content-Type": "text/html; charset=UTF-8",
      "Access-Control-Allow-Origin":'http://127.0.0.1:8888'
    });
    let user= {
      userName: 'Jack',
      password: '123456'
    }
    res.end(JSON.stringify(user));
  }
})

这里解释一下:当浏览器发现发了跨域请求就会在请求头里添加 Origin: 当前域 字段。服务器此时会检测是否存在 Origin 字段,如果存在那么会在响应头里添加 Access-Control-Allow-Origin: Origin 的值 再返回 JSON 格式结果就行了。前端收到响应后,浏览器会检查 Access-Control-Allow-Origin 的值是否和当前的地址相同,如果相同才能获取到数据。

postMessage

这个方法主要是在前端完成的。在发送请求方只需要调用新 API 的 postMessage 函数就可以了。

postMessage('要发送的消息', '目的地地址', '当前地址')

在接受方要在 window 上监听一个 message 事件。

function receiveMessage(event) {
  // 对发送者做检查 ,如果不是自己人就不返回
  if (event.origin !== "http://example.com:8080")
    return;

  // 这里可以打印发送方的消息
  console.log(event.data)

  // 如果是自己人,就返回数据 
  event.source.postMessage("你好,自己人",event.origin)
}

// 全局监听 `message` 事件
window.addEventListener("message", receiveMessage, false);

这种方法比较简单,但是也要做好发送方的检测。

推荐阅读更多精彩内容