詳解Javascript?基于長連接的服務(wù)框架問題
背景
經(jīng)常使用 Node 進(jìn)行服務(wù)端開發(fā)的同學(xué),想必都知道 Koa 框架。Koa 是一種 http 服務(wù)框架,其基于洋蔥模型作為基本架構(gòu),能夠讓用戶方便快捷地添加中間件,進(jìn)行業(yè)務(wù)開發(fā)。而 websocket 是一種長連接的服務(wù)通信協(xié)議,需要自定義通訊 api 進(jìn)行數(shù)據(jù)通訊。一般情況下,基于 websocket 的通訊 api 也是遵循一問一答的交互模式的,只是通信發(fā)起方可能會(huì)是客戶端,也可能會(huì)是服務(wù)方。
在 MBox 研發(fā)助手的開發(fā)中,前端和服務(wù)端處于平等的地位,前端和服務(wù)端都有可能發(fā)起請求,所以采用 websocket 協(xié)議作為通信協(xié)議。在 websocket 基礎(chǔ)上,MBox 研發(fā)助手自定義的通訊 api 與 http 有相似之處,同樣采用一問一答的交互模式。為了減少其他開發(fā)同學(xué)的理解成本,維持接口的統(tǒng)一性和可擴(kuò)展性,在 MBox 研發(fā)助手中我們設(shè)計(jì)了一套基于長連接的服務(wù)框架。
Webscoket 封裝
首先為了方便使用 websocket 接收消息,采用注冊回調(diào)函數(shù)的方式分發(fā)服務(wù)端發(fā)來的消息。
export class Connection { private ws: WebSocket = null; private handlers: WSHandler[] = []; constructor() { this.ws = new WebSocket(WSDomain); this.ws.onmessage = (ev: MessageEvent) => { this.handlers.forEach((handler: WSHandler) => { handler(ev.data); }); }; } send(data: string): void { this.ws.send(data); } registerRecvHandler(handler: WSHandler): void { this.handlers.push(handler); } close(): void { this.ws.close(); } }
FakeHttpServer
Context
Koa 的 context 將 resquest和 response 封裝成了一個(gè)對象,采用代理的方式來控制對 request 和 response 的訪問,用戶可以通過 context 間接操作 request 和 response。這里忽略了繁瑣的代理內(nèi)容,簡單將 context 表示為擁有 request 和 response 的簡單對象。Requset 和 Response 類型的具體定義可以根據(jù)業(yè)務(wù)進(jìn)行抽象。
export interface Context { request: Request; response: Response; }
Middleware
在 Express 和 Koa 等 web 服務(wù)框架中,中間件指的是處于 request -response 生命周期中,處理請求的一系列函數(shù)。中間件函數(shù)對代碼進(jìn)行了解耦,各個(gè)中間件之間無感知,每個(gè)中間件只需要處理自己的事情即可。使用 Promise 實(shí)現(xiàn)中間件函數(shù)的級聯(lián)操作,其核心代碼邏輯如下:
export function compose(middleware) { if (!Array.isArray(middleware)) throw new TypeError("Middleware stack must be an array!"); for (const fn of middleware) { if (typeof fn !== "function") throw new TypeError("Middleware must be composed of functions!"); } return function (context, next) { // last called middleware # let index = -1; return dispatch(0); function dispatch(i) { if (i <= index) return Promise.reject(new Error("next() called multiple times")); index = i; let fn = middleware[i]; if (i === middleware.length) fn = next; if (!fn) return Promise.resolve(); try { return Promise.resolve(fn(context, dispatch.bind(null, i + 1))); } catch (err) { return Promise.reject(err); } } }; }
請求處理
了解了 context 和 middleware 級聯(lián)處理請求的原理之后,你已經(jīng)明白 web 服務(wù)框架最基本兩個(gè)模塊了,下面開始了解 FakeHttpServer 從接收 request 到返回 response 的處理過程。
FakeHttpServer 服務(wù)框架基于長連接的特點(diǎn)就體現(xiàn)在使用 websocket 作為底層收發(fā)的數(shù)據(jù)協(xié)議,使用 listen 函數(shù)進(jìn)行請求監(jiān)聽需要傳入一個(gè) Connection 連接而不是端口號。
listen(conn: Connection) { this.conn = conn; this.conn.registerRecvHandler(this.receive.bind(this)); const fn = compose(this.middlewares); this.innerHandleRequest = ( request: Request, response: Response ) => { const ctx = this.createContext(request, response); return this.handleRequest(ctx, fn); }; } receive(data: string) { try { const obj = JSON.parse(data); // 丟掉 respnose 類型的消息 if (isRequest(obj)) { const request = obj as Request; const response = {} as Response; // 調(diào)用 handleRequset this.innerHandleRequest(request, response); } } catch (err) { console.error(err); } } private createContext( request: Request, response: Response ): LongContext { return { request: request, response: response } as Context; } private handleRequest(ctx: Context, fnMiddleware) { return fnMiddleware(ctx) .then(() => this.respond(ctx)) .catch((err) => console.error(err)); }
Listen 函數(shù)注冊了 Connection 的回調(diào),當(dāng)客戶端發(fā)送消息時(shí)會(huì)調(diào)用 receive 函數(shù)進(jìn)行處理。首先,F(xiàn)akeHttpServer 會(huì)將 Response 類型的消息拋棄,只對 Request 請求消息進(jìn)行響應(yīng)。然后,F(xiàn)akeHttpServer 會(huì)創(chuàng)建一個(gè)新的 context,將 request 和空 response 放入。最后使用 compose 后的中間件函數(shù)數(shù)組處理請求,返回響應(yīng)。至此,一個(gè)完整的發(fā)起請求到返回響應(yīng)的流程就結(jié)束了。
Quick Start
可以在自定義 Request 和 Response 類型之后,來使用 FakeHttpServer 快速開發(fā)一個(gè)基于長連接的 http 模擬服務(wù):
const app = new FakeHttpServer(); app.use((ctx: LongContext, next: Middleware) => { ctx.reponse.code = 0; next(); }); app.use((ctx: LongContext, next: Middleware) => { ctx.reponse.message = "success"; }); app.listen(new Connection());
小結(jié)
本文針對經(jīng)常使用長連接進(jìn)行消息收發(fā)的應(yīng)答場景,采用 Websocket 長連接作為服務(wù)監(jiān)聽的對象,模擬了一套類 http 服務(wù)框架。該框架結(jié)合了長連接自定義通訊 api 的靈活和 http 服務(wù)框架的自動(dòng)應(yīng)答處理機(jī)制,提供了一種開銷小、方便、統(tǒng)一、標(biāo)準(zhǔn)化的方式來使用長連接進(jìn)行數(shù)據(jù)通訊。
到此這篇關(guān)于Javascript 基于長連接的服務(wù)框架的文章就介紹到這了,更多相關(guān)js長連接服務(wù)框架內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
javascript多種數(shù)據(jù)類型表格排序代碼分析
這個(gè)表格排序代碼,性能比上一次那一個(gè)好了很多而且支持很多種類型的排序,這一次寫的能支持更多的排序。2010-09-09JavaScript導(dǎo)航腳本判斷當(dāng)前導(dǎo)航
這篇文章主要介紹了JavaScript導(dǎo)航腳本判斷當(dāng)前導(dǎo)航的相關(guān)資料,非常不錯(cuò),具有參考借鑒價(jià)值,需要的朋友可以參考下2016-07-07詳解JavaScript的懶加載是如何實(shí)現(xiàn)的
懶加載(Lazy Loading)是一種在軟件開發(fā)中常用的優(yōu)化技術(shù),它主要用于延遲加載資源,直到真正需要使用的時(shí)候才進(jìn)行加載,這樣可以減少初始加載的時(shí)間和資源消耗,并提升用戶體驗(yàn),本文給大家詳細(xì)介紹了JavaScript的懶加載是如何實(shí)現(xiàn)的,需要的朋友可以參考下2024-01-01javascripit實(shí)現(xiàn)密碼強(qiáng)度檢測代碼分享
這篇文章主要介紹了javascripit實(shí)現(xiàn)密碼強(qiáng)度檢測,大家參考使用吧2013-12-12BootStrap智能表單實(shí)戰(zhàn)系列(四)表單布局介紹
這篇文章主要介紹了BootStrap智能表單實(shí)戰(zhàn)系列(四)表單布局介紹,表單布局分為自動(dòng)布局和自定義布局兩種,本文通過代碼給大家介紹,介紹的非常詳細(xì),具有參考借鑒價(jià)值,感興趣的朋友一起學(xué)習(xí)吧2016-06-06JavaScript之瀏覽器對象_動(dòng)力節(jié)點(diǎn)Java學(xué)院整理
JavaScript可以獲取瀏覽器提供的很多對象,并進(jìn)行操作。下面通過本文給大家介紹JavaScript之瀏覽器對象的相關(guān)知識,一起看看吧2017-07-07微信小程序分享海報(bào)生成的實(shí)現(xiàn)方法
為了吸引更多的用戶,設(shè)計(jì)好一個(gè)分享海報(bào)還是很有必要的,這篇文章主要介紹了微信小程序分享海報(bào)生成的實(shí)現(xiàn)方法,非常具有實(shí)用價(jià)值,需要的朋友可以參考下2018-12-12原生JavaScript實(shí)現(xiàn)remove()和recover()功能示例
這篇文章主要介紹了原生JavaScript實(shí)現(xiàn)remove()和recover()功能,結(jié)合實(shí)例形式分析了javascript實(shí)現(xiàn)類似jQueryremove()和recover()功能的自定義函數(shù),需要的朋友可以參考下2018-07-07