SignalR在React/Go技術(shù)棧的實踐

哼哧哼哧半年,優(yōu)化改進(jìn)了一個運維開發(fā)web平臺。
本文記錄SignalR在react/golang 技術(shù)棧的生產(chǎn)小實踐。
01
背景
有個前后端分離的運維開發(fā)web平臺, 后端會間隔5分鐘同步一次數(shù)據(jù),現(xiàn)在需要將最新一次同步的時間推送到web前端。
說到[web服務(wù)端推送],立馬想到SignalR,(我頭腦中一直有技術(shù)體系, 但一直沒實踐過。)
SignalR是微軟推出的實時通信標(biāo)準(zhǔn)框架,內(nèi)部封裝了 websocket、服務(wù)端發(fā)送事件、長輪詢, 可以算是實時通信的大殺器,傳送門。
實際編碼就是react寫SignalR客戶端,golang寫SignalR服務(wù)端,盲猜有對應(yīng)的輪子。
02
擼起袖子干
果然, signalr的作者David Fowler實現(xiàn)了node、go版本, 這位老哥是.NET技術(shù)棧如雷貫耳的大牛:

但是他的倉庫很久不更了,某德國大佬在此基礎(chǔ)上開了新github倉庫[1]繼續(xù)支持。
SignalR的基本交互原理:
(1) signalR提供了一組API, 用于創(chuàng)建從服務(wù)端到客戶端的遠(yuǎn)程過程調(diào)用(RPC),這個調(diào)用的具體體現(xiàn)是 :從服務(wù)端.NET 代碼調(diào)用位于客戶端的javascript 代碼。
(2) signalr提供了管理實例、連接、失連, 分組管控的API。

這里面最關(guān)鍵的一個概念是集線器Hub,其實也就是RPC領(lǐng)域常說的客戶端代理。
服務(wù)端在baseUrl上建立signalr的監(jiān)聽地址;
客戶端連接并注冊receive事件;
服務(wù)端在適當(dāng)時候通過hubServer向HubClients發(fā)送數(shù)據(jù)。
go服務(wù)端
(1) 添加golang pgk:go get github.com/philippseith/signalr
(2) 定義客戶端集線器hub,這里要實現(xiàn)HubInterface接口的幾個方法, 你還可以為集線器添加一些自定義方法。
package?services
import?(
?"github.com/philippseith/signalr"
?log?"github.com/sirupsen/logrus"
?"time"
)
type?AppHub?struct{
??signalr.Hub
}
func?(h?*AppHub)?OnConnected(connectionID?string)?{
?//?fmt.Printf("%s?connected\n",?connectionID)
?log.Infoln(connectionID,"?connected\n"?)
}
func?(h?*AppHub)?OnDisconnected(connectionID?string)?{
?log.Infoln(connectionID,"?disconnected\n")
}
//?客戶端調(diào)用的函數(shù),?本例不用
func?(h?*AppHub)?Send(message?string)?{
?h.Clients().All().Send("receive",?time.Now().Format("2006/01/02?15:04:05")?)
}
(3) 初始化集線器, 并在特定地址監(jiān)聽signalr請求。
這個庫將signalr監(jiān)聽服務(wù)抽象為獨立的hubServer
shub?:=?services.AppHub{}
sHubSrv,err:=?signalr.NewServer(context.TODO(),
??signalr.UseHub(&shub),?//?這是單例hub
??signalr.KeepAliveInterval(2*time.Second),
??signalr.Logger(kitlog.NewLogfmtLogger(os.Stderr),?true))
?sHubSrv.MapHTTP(mux,?"/realtime")
(4) 利用sHubServer在合適業(yè)務(wù)代碼位置向web客戶端推送數(shù)據(jù)。
if?clis:=?s.sHubServer.HubClients();?clis!=?nil?{
????c:=?clis.All()
????if??c!=?nil?{
?????c.Send("receive",ts.Format("2006/01/02?15:04:05"))
????}
???}
注意:上面的receive方法是后面react客戶端需要監(jiān)聽的JavaScript事件名。
react客戶端
前端菜雞,跟著官方示例琢磨了好幾天。
(1) 添加@microsoft/signalr 包
(2) 在組件掛載事件componentDidMount初始化signalr連接
實際也就是向服務(wù)端baseUrl建立HubConnection,注冊receive事件,等待服務(wù)端推送。
import?React?from?'react';
import?{
??JsonHubProtocol,
??HubConnectionState,
??HubConnectionBuilder,
??HttpTransportType,
??LogLevel,
}?from?'@microsoft/signalr';
class?Clock?extends?React.Component?{
????constructor(props)?{
??????super(props);
??????this.state?=?{
????????message:'',
????????hubConnection:?null,
??????};
????}
??
????componentDidMount()?{
??????const?connection?=?new?HubConnectionBuilder()
????????.withUrl(process.env.REACT_APP_APIBASEURL+"realtime",?{
????????})
????????.withAutomaticReconnect()
????????.withHubProtocol(new?JsonHubProtocol())
????????.configureLogging(LogLevel.Information)
????????.build();
?
????//?Note:?to?keep?the?connection?open?the?serverTimeout?should?be
????//?larger?than?the?KeepAlive?value?that?is?set?on?the?server
????//?keepAliveIntervalInMilliseconds?default?is?15000?and?we?are?using?default
????//?serverTimeoutInMilliseconds?default?is?30000?and?we?are?using?60000?set?below
????????connection.serverTimeoutInMilliseconds?=?60000;
?
????//?re-establish?the?connection?if?connection?dropped
????????connection.onclose(error?=>?{
????????????console.assert(connection.state?===?HubConnectionState.Disconnected);
????????????console.log('Connection?closed?due?to?error.?Try?refreshing?this?page?to?restart?the?connection',?error);
????????});
????
????????connection.onreconnecting(error?=>?{
????????????console.assert(connection.state?===?HubConnectionState.Reconnecting);
????????????console.log('Connection?lost?due?to?error.?Reconnecting.',?error);
????????});
????
????????connection.onreconnected(connectionId?=>?{
????????????console.assert(connection.state?===?HubConnectionState.Connected);
????????????console.log('Connection?reestablished.?Connected?with?connectionId',?connectionId);
????????});
????????
????????this.setState({?hubConnection:?connection})
????????this.startSignalRConnection(connection).then(()=>?{
??????????????if(connection.state?===?HubConnectionState.Connected)?{
????????????????connection.invoke('RequestSyncTime').then(val?=>?{
??????????????????console.log("Signalr?get?data?first?time:",val);
??????????????????this.setState({?message:val?})
????????????????})
??????????????}
????????})?;
????????connection.on('receive',?res?=>?{
??????????console.log("SignalR?get?hot?res:",?res)
????????????this.setState({
??????????????message:res
????????????});
????????});
????}
??
????startSignalRConnection?=?async?connection?=>?{
??????try?{
??????????await?connection.start();
??????????console.assert(connection.state?===?HubConnectionState.Connected);
??????????console.log('SignalR?connection?established');
??????}?catch?(err)?{
??????????console.assert(connection.state?===?HubConnectionState.Disconnected);
??????????console.error('SignalR?Connection?Error:?',?err);
??????????setTimeout(()?=>?this.startSignalRConnection(connection),?5000);
??????}
????};
??
????render()?{
??????return?(
????????
??????????最新同步完成時間:?{this.state.message}??
????????
??????);
????}
??}
export??default??Clock;
(3) 將該react組件插入到web前端頁面
03
效果分析
最后的效果如圖:

效果分析:
(1) web客戶端與服務(wù)器協(xié)商 傳輸方式http://localhost:9598/realtime/negotiate?negotiateVersion=1,
返回可用的傳輸方式和連接標(biāo)識ConnectionId。
{
????"connectionId":?"hkSNQT-pGpZ9E6tuMY9rRw==",
????"availableTransports":?[{
????????"transport":?"WebSockets",
????????"transferFormats":?["Text",?"Binary"]
????},?{
????????"transport":?"ServerSentEvents",
????????"transferFormats":?["Text"]
????}]
}
(2) web客戶端利用上面的ConnectionId向特定的服務(wù)器地址/realtime連接,建立傳輸通道,默認(rèn)優(yōu)先websocket。

以上網(wǎng)絡(luò)交互,大部分會通過SignalR框架自動完成。
源碼:Github Demo[2]

引用鏈接
[1]?Github倉庫:?https://github.com/philippseith/signalr[2]?Github Demo:?https://github.com/zaozaoniao/SignalR-apply-to-react-and-golang

●大前端快閃四:這次使用一個舒服的姿勢插入HttpClient攔截器
點“贊”
戳“在看”
體現(xiàn)態(tài)度很有必要!
