《前端實戰總結》之使用postMessage實現可插拔的跨域聊天機器人

因爲筆者以前的項目中接觸過聊天機器人的項目,主要實現機器人客服模塊,以及支持跨多平臺使用的目的,因此特意總結一下,但願有所收穫。

你將學到

  • 跨域技術經常使用方案介紹
  • postMessage實現跨域通訊
  • 如何實現聊天機器人
  • node搭建本地服務器來實現渲染頁面和跨域
  • 回答語料庫設計思路

效果預覽

正文

1. 跨域技術經常使用方案介紹

首先要強調的是跨域的安全限制都是對瀏覽器端來講的,服務器端是不存在跨域安全限制的。咱們經常使用的跨域技術主要有以下幾種:javascript

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

JSONP實現跨域請求的原理就是動態建立script標籤,而後利用script的src 不受同源策略約束來跨域獲取數據。JSONP 主要由回調函數和數據兩部分組成。回調函數的名字通常是在請求中指定的。而數據就是傳入回調函數中的 JSON 數據。咱們通常能夠在全局定義一個回調函數,而後在script標籤裏傳入回調函數便可:css

window.handleData = function(data){
    // ...
}
let script = document.createElement("script");
script.src = "https://xxxx.com/v0/search?q=xuxi&callback=handleData";
document.body.insertBefore(script, document.body.firstChild);
複製代碼

這樣咱們就能在回調函數handleData中拿到服務器接口返回的數據了。html

雖然jsonp實現跨域方式很簡單,可是隻支持get請求,對傳輸的數據量有必定限制。cors跨域是目前咱們用的比較多的本地調試方式,原理就是在服務端設置響應頭header的Access-Control-Allow-Origin字段,這樣瀏覽器檢測到header中的Access-Control-Allow-Origin,這樣就能夠跨域了。前端

至於咱們設置了cors以後在network中出現了兩次請求的問題,其實涉及到cors跨域的請求預檢,分爲簡單請求和非簡單請求兩種,這塊知識能夠單獨抽離出一篇文章,感興趣的能夠本身學習瞭解一下。vue

2. postMessage實現跨域通訊

window.postMessage() 方法能夠安全地實現跨源通訊。一般,對於兩個不一樣頁面的腳本,只有當執行它們的頁面位於具備相同的協議,端口號以及主機 (兩個頁面的模數 Document.domain設置爲相同的值) 時,這兩個腳本才能相互通訊。window.postMessage() 方法提供了一種受控機制來規避此限制,只要正確的使用,這種方法就很安全。java

本質上說postMessage()是基於消息事件機制來實現跨域通訊,它隸屬於消息窗體自己,好比window以及window內嵌的frame的window,基本使用形式以下:node

someWindow.postMessage(message, targetOrigin, [transfer]);
複製代碼

參數介紹:webpack

  • someWindow 窗口的一個引用,好比iframe的contentWindow屬性、執行window.open返回的窗口對象、或者是命名過或數值索引的window.frames
  • message 將要發送到其餘 window的數據。意味着你能夠不受什麼限制的將數據對象安全的傳送給目標窗口而無需本身序列化
  • targetOrigin 經過窗口的origin屬性來指定哪些窗口能接收到消息事件,其值能夠是字符串"*"(表示無限制)。不提供確切的目標將致使數據泄露等安全問題
  • transfer 是一串和message 同時傳遞的 Transferable 對象. 這些對象的全部權將被轉移給消息的接收方,而發送一方將再也不保有全部權

咱們能夠經過以下方式來監聽message:css3

window.addEventListener("message", receiveMessage, false);

function receiveMessage(event){
  let origin = event.origin || event.originalEvent.origin; 
  if (origin !== "http://aaa:8080")
    return;

  // ...
  console.log(event.data)
}

// 派發消息的頁面
winB.postMessage(_({text: '休息休息'}), origin)
複製代碼

咱們的event裏有以下幾個核心的屬性:nginx

  • data 從其餘 window 中傳遞過來的對象
  • origin 調用 postMessage 時消息發送方窗口的 origin . 這個字符串由 協議、「://「、域名、「 : 端口號」拼接而成
  • source 對發送消息的窗口對象的引用; 您可使用此來在具備不一樣origin的兩個窗口之間創建雙向通訊

3. 實現聊天機器人

在熟悉以上知識點以後,咱們開始來寫咱們聊天機器人的demo。 首先咱們寫兩個html,分別爲a.html和b.html,而後用node分別代理兩個不一樣頁面,設置不一樣端口:

// a.js
//依賴一個http模塊,至關於java中的import,與C#中的using
var http = require('http');
var fs = require('fs');
var { resolve } = require('path');

//建立一個服務器對象
server = http.createServer(function (req, res) {
//設置請求成功時響應頭部的MIME爲純文本
res.writeHeader(200, {"Content-Type": "text/html"});
//向客戶端輸出字符
let data = fs.readFileSync(resolve(__dirname, './a.html'))
res.end(data);
});
//讓服務器監聽本地8000端口開始運行
server.listen(8000,'127.0.0.1');
console.log('http://127.0.0.1:8000')

// b.js
// ...
server.listen(8001,'127.0.0.1');
複製代碼

由上可知咱們a.html代理在8000端口下,b.html代理在8001端口下,由瀏覽器的同源策略可知他們存在跨域問題。

跨域實現以後咱們能夠開始搭建頁面層級了,咱們這裏將b頁面以iframe的形式嵌入到a頁面,具體結構以下:

這樣咱們就能夠愉快的搭建postMessage體系了。

首先咱們在a頁面經過發送按鈕和輸入框將消息發送給b頁面,大體結構以下:

<body>
    <div class="wrap">
        <iframe src="http://127.0.0.1:8001" frameborder="0" id="b"></iframe>
        <div class="control">
            <input type="text" placeholder="請輸入內容" id="ipt">
            <span id="send">發送</span>
        </div>
    </div>
    <script> window.onload = function() { let origin = 'http://127.0.0.1:8001'; let _ = (data) => JSON.stringify(data); let winB = document.querySelector('#b').contentWindow; let sendBtn = document.querySelector('#send'); sendBtn.addEventListener('click', (e) => { let text = document.querySelector('#ipt'); winB.postMessage(_({text: text.value}), origin) text.value = ''; }, false) winB.postMessage(_({text: ''}), origin) } </script>
</body>
複製代碼

咱們能夠經過iframe的contentWindow來拿到b頁面窗體的引用,而後在發送按鈕的點擊事件中觸發postMessage將數據發送給B。B頁面結構以下:

<body>
    <div class="content">
            <h4>Lab智能機器人</h4>
            <div class="content-inner"></div>
    </div>
    <script> // 語料庫 const pool = []; window.addEventListener("message", receiveMessage, false); let content = document.querySelector('.content-inner'); let initContentH = content.scrollHeight; let _ = (data) => JSON.stringify(data); function createChat(type, mes) { let dialog = document.createElement('div'); dialog.className = type === 0 ? 'dialog robot' : 'dialog user'; let content = type === 0 ? ` <span class="tx">${type === 0 ? 'lab' : 'user'}</span> <span class="mes">${mes}</span> ` : ` <span class="mes">${mes}</span> <span class="tx">${type === 0 ? 'lab' : 'user'}</span> `; dialog.innerHTML = content; return dialog } function scrollTop(el, h) { if(el.scrollHeight !== h) { el.scrollTop = h + 100; } } function receiveMessage(event){ // 兼容其餘瀏覽器 let origin = event.origin || event.originalEvent.origin; if(origin === 'http://127.0.0.1:8000') { let data = JSON.parse(event.data); if(data && !data.text) { mes = { text: '你好,我是機器人Lab,請問有什麼能夠幫到您的嗎?' }; event.source.postMessage(_(mes), event.origin) content.appendChild(createChat(0, mes.text)) }else { content.appendChild(createChat(1, data.text)) scrollTop(content, initContentH) setTimeout(() => { content.appendChild(createChat(0, '正在解決')) scrollTop(content, initContentH) }, 2000); } } } </script>
</body>
複製代碼

咱們在b頁面中去解析a頁面的數據並作出相應的回答。這樣,咱們的基本聊天機器人就實現了。

4. 回答語料庫設計思路

至於當咱們在a頁面發送了一個消息,b頁面如何解析並回答,能夠有以下幾種思路:

  • 經過後端接口實現,即咱們能夠將a的數據做爲參數傳遞給某個後端接口,讓後端來實現返回須要的數據,這種在AI機器人中應用的很普遍。
  • 純前端實現。前端定義回答的語料庫,經過關鍵詞匹配來拿到實現應答,這種通常用於普通的預設問題的回答。

5.實現可插拔式

可插拔式就是一個頁面能夠放在不一樣平臺使用。這種咱們能夠設置origin白名單,只須要將b頁面封裝,其餘系統可使用類於a頁面的方式,只提供發送信息的接口,這樣咱們就能夠在不一樣平使用了。

關於本聊天程序的全部代碼我已經提交到 GitHub,感興趣的朋友能夠下載體驗一下,或者基於他實現更智能的聊機器人。

最後

若是想了解更多webpack,node,gulp,css3,javascript,nodeJS,canvas等前端知識和實戰,歡迎在公衆號《趣談前端》加入咱們一塊兒學習討論,共同探索前端的邊界。

更多推薦

相關文章
相關標籤/搜索