当前位置:主页 > 查看内容

可插拔的跨域聊天机器人实现方案复盘(postMessage版)

发布时间:2021-08-01 00:00| 位朋友查看

简介:由于笔者之前的项目中接触过聊天机器人的项目,主要实现机器人客服模块,以及支持跨多平台使用的目的,所以特地总结一下,希望有所收获。 你将学到 跨域技术常用方案介绍 postMessage实现跨域通信 如何实现聊天机器人 node搭建本地服务器来实现渲染页面和跨……

由于笔者之前的项目中接触过聊天机器人的项目,主要实现机器人客服模块,以及支持跨多平台使用的目的,所以特地总结一下,希望有所收获。

你将学到

  • 跨域技术常用方案介绍
  • postMessage实现跨域通信
  • 如何实现聊天机器人
  • node搭建本地服务器来实现渲染页面和跨域
  • 回答语料库设计思路

效果预览


正文

1. 跨域技术常用方案介绍

首先要强调的是跨域的安全限制都是对浏览器端来说的,服务器端是不存在跨域安全限制的。我们常用的跨域技术主要有如下几种:

  • JSONP跨域
  • iframe+domain跨域
  • nginx反向代理跨域
  • cors跨域
  • postMessage跨域

JSONP实现跨域请求的原理就是动态创建script标签,然后利用script的src 不受同源策略约束来跨域获取数据。JSONP 主要由回调函数和数据两部分组成。回调函数的名字一般是在请求中指定的。而数据就是传入回调函数中的 JSON 数据。我们一般可以在全局定义一个回调函数,然后在script标签里传入回调函数即可:

  1. window.handleData = function(data){ 
  2.     // ... 
  3. let script = document.createElement("script"); 
  4. script.src = "https://xxxx.com/v0/search?q=xuxi&callback=handleData"
  5. document.body.insertBefore(script, document.body.firstChild); 

这样我们就能在回调函数handleData中拿到服务器接口返回的数据了。

虽然jsonp实现跨域方式很简单,但是只支持get请求,对传输的数据量有一定限制。cors跨域是目前我们用的比较多的本地调试方式,原理就是在服务端设置响应头header的Access-Control-Allow-Origin字段,这样浏览器检测到header中的Access-Control-Allow-Origin,这样就可以跨域了。

至于我们设置了cors之后在network中出现了两次请求的问题,其实涉及到cors跨域的请求预检,分为简单请求和非简单请求两种,这块知识可以单独抽离出一篇文章,感兴趣的可以自己学习了解一下。

2. postMessage实现跨域通信

  • window.postMessage() 方法可以安全地实现跨源通信。通常,对于两个不同页面的脚本,只有当执行它们的页面位于具有相同的协议,端口号以及主机 (两个页面的模数 Document.domain设置为相同的值) 时,这两个脚本才能相互通信。window.postMessage() 方法提供了一种受控机制来规避此限制,只要正确的使用,这种方法就很安全。

本质上说postMessage()是基于消息事件机制来实现跨域通信,它隶属于消息窗体本身,比如window以及window内嵌的frame的window,基本使用形式如下:

  1. someWindow.postMessage(message, targetOrigin, [transfer]); 

参数介绍:

  • someWindow 窗口的一个引用,比如iframe的contentWindow属性、执行window.open返回的窗口对象、或者是命名过或数值索引的window.frames
  • message 将要发送到其他 window的数据。意味着你可以不受什么限制的将数据对象安全的传送给目标窗口而无需自己序列化
  • targetOrigin 通过窗口的origin属性来指定哪些窗口能接收到消息事件,其值可以是字符串"*"(表示无限制)。不提供确切的目标将导致数据泄露等安全问题
  • transfer 是一串和message 同时传递的 Transferable 对象. 这些对象的所有权将被转移给消息的接收方,而发送一方将不再保有所有权

我们可以通过如下方式来监听message:

  1. window.addEventListener("message", receiveMessage, false); 
  2.  
  3. function receiveMessage(event){ 
  4.   let origin = event.origin || event.originalEvent.origin;  
  5.   if (origin !== "http://aaa:8080"
  6.     return
  7.  
  8.   // ... 
  9.   console.log(event.data) 
  10.  
  11. // 派发消息的页面 
  12. winB.postMessage(_({text: '休息休息'}), origin) 

我们的event里有如下几个核心的属性:

  • data 从其他 window 中传递过来的对象
  • origin 调用 postMessage 时消息发送方窗口的 origin . 这个字符串由 协议、“://“、域名、“ : 端口号”拼接而成
  • source 对发送消息的窗口对象的引用; 您可以使用此来在具有不同origin的两个窗口之间建立双向通信

3. 实现聊天机器人

在熟悉以上知识点之后,我们开始来写我们聊天机器人的demo。首先我们写两个html,分别为a.html和b.html,然后用node分别代理两个不同页面,设置不同端口:

  1. // a.js 
  2. //依赖一个http模块,相当于java中的import,与C#中的using 
  3. var http = require('http'); 
  4. var fs = require('fs'); 
  5. var { resolve } = require('path'); 
  6.  
  7. //创建一个服务器对象 
  8. server = http.createServer(function (req, res) { 
  9. //设置请求成功时响应头部的MIME为纯文本 
  10. res.writeHeader(200, {"Content-Type""text/html"}); 
  11. //向客户端输出字符 
  12. let data = fs.readFileSync(resolve(__dirname, './a.html')) 
  13. res.end(data); 
  14. }); 
  15. //让服务器监听本地8000端口开始运行 
  16. server.listen(8000,'127.0.0.1'); 
  17. console.log('http://127.0.0.1:8000'
  18.  
  19. // b.js 
  20. // ... 
  21. server.listen(8001,'127.0.0.1'); 

由上可知我们a.html代理在8000端口下,b.html代理在8001端口下,由浏览器的同源策略可知他们存在跨域问题。

跨域实现之后我们可以开始搭建页面层级了,我们这里将b页面以iframe的形式嵌入到a页面,具体结构如下:


这样我们就可以愉快的搭建postMessage体系了。首先我们在a页面通过发送按钮和输入框将消息发送给b页面,大致结构如下:

  1. <body> 
  2.     <div class="wrap"
  3.         <iframe src="http://127.0.0.1:8001" frameborder="0" id="b"></iframe> 
  4.         <div class="control"
  5.             <input type="text" placeholder="请输入内容" id="ipt"
  6.             <span id="send">发送</span> 
  7.         </div> 
  8.     </div> 
  9.     <script> 
  10.         window.onload = function() { 
  11.             let origin = 'http://127.0.0.1:8001'
  12.             let _ = (data) => JSON.stringify(data); 
  13.             let winB = document.querySelector('#b').contentWindow; 
  14.             let sendBtn = document.querySelector('#send'); 
  15.             sendBtn.addEventListener('click', (e) => { 
  16.                 let text = document.querySelector('#ipt'); 
  17.                 winB.postMessage(_({text: text.value}), origin) 
  18.                 text.value = ''
  19.             }, false
  20.             winB.postMessage(_({text: ''}), origin) 
  21.         } 
  22.     </script> 
  23. </body> 

本文转载自网络,原文链接:https://mp.weixin.qq.com/s/6doi_KWJIxY2Urfg5EGEfA
本站部分内容转载于网络,版权归原作者所有,转载之目的在于传播更多优秀技术内容,如有侵权请联系QQ/微信:153890879删除,谢谢!

推荐图文


随机推荐