詳解Javascript?基于長(zhǎng)連接的服務(wù)框架問(wèn)題
背景
經(jīng)常使用 Node 進(jìn)行服務(wù)端開(kāi)發(fā)的同學(xué),想必都知道 Koa 框架。Koa 是一種 http 服務(wù)框架,其基于洋蔥模型作為基本架構(gòu),能夠讓用戶(hù)方便快捷地添加中間件,進(jìn)行業(yè)務(wù)開(kāi)發(fā)。而 websocket 是一種長(zhǎng)連接的服務(wù)通信協(xié)議,需要自定義通訊 api 進(jìn)行數(shù)據(jù)通訊。一般情況下,基于 websocket 的通訊 api 也是遵循一問(wèn)一答的交互模式的,只是通信發(fā)起方可能會(huì)是客戶(hù)端,也可能會(huì)是服務(wù)方。
在 MBox 研發(fā)助手的開(kāi)發(fā)中,前端和服務(wù)端處于平等的地位,前端和服務(wù)端都有可能發(fā)起請(qǐng)求,所以采用 websocket 協(xié)議作為通信協(xié)議。在 websocket 基礎(chǔ)上,MBox 研發(fā)助手自定義的通訊 api 與 http 有相似之處,同樣采用一問(wèn)一答的交互模式。為了減少其他開(kāi)發(fā)同學(xué)的理解成本,維持接口的統(tǒng)一性和可擴(kuò)展性,在 MBox 研發(fā)助手中我們?cè)O(shè)計(jì)了一套基于長(zhǎng)連接的服務(wù)框架。
Webscoket 封裝
首先為了方便使用 websocket 接收消息,采用注冊(cè)回調(diào)函數(shù)的方式分發(fā)服務(wù)端發(fā)來(lái)的消息。
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è)對(duì)象,采用代理的方式來(lái)控制對(duì) request 和 response 的訪問(wèn),用戶(hù)可以通過(guò) context 間接操作 request 和 response。這里忽略了繁瑣的代理內(nèi)容,簡(jiǎn)單將 context 表示為擁有 request 和 response 的簡(jiǎn)單對(duì)象。Requset 和 Response 類(lèi)型的具體定義可以根據(jù)業(yè)務(wù)進(jìn)行抽象。
export interface Context {
request: Request;
response: Response;
}Middleware
在 Express 和 Koa 等 web 服務(wù)框架中,中間件指的是處于 request -response 生命周期中,處理請(qǐng)求的一系列函數(shù)。中間件函數(shù)對(duì)代碼進(jìn)行了解耦,各個(gè)中間件之間無(wú)感知,每個(gè)中間件只需要處理自己的事情即可。使用 Promise 實(shí)現(xiàn)中間件函數(shù)的級(jí)聯(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);
}
}
};
}請(qǐng)求處理
了解了 context 和 middleware 級(jí)聯(lián)處理請(qǐng)求的原理之后,你已經(jīng)明白 web 服務(wù)框架最基本兩個(gè)模塊了,下面開(kāi)始了解 FakeHttpServer 從接收 request 到返回 response 的處理過(guò)程。
FakeHttpServer 服務(wù)框架基于長(zhǎng)連接的特點(diǎn)就體現(xiàn)在使用 websocket 作為底層收發(fā)的數(shù)據(jù)協(xié)議,使用 listen 函數(shù)進(jìn)行請(qǐng)求監(jiān)聽(tīng)需要傳入一個(gè) Connection 連接而不是端口號(hào)。
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 類(lèi)型的消息
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ù)注冊(cè)了 Connection 的回調(diào),當(dāng)客戶(hù)端發(fā)送消息時(shí)會(huì)調(diào)用 receive 函數(shù)進(jìn)行處理。首先,F(xiàn)akeHttpServer 會(huì)將 Response 類(lèi)型的消息拋棄,只對(duì) Request 請(qǐng)求消息進(jìn)行響應(yīng)。然后,F(xiàn)akeHttpServer 會(huì)創(chuàng)建一個(gè)新的 context,將 request 和空 response 放入。最后使用 compose 后的中間件函數(shù)數(shù)組處理請(qǐng)求,返回響應(yīng)。至此,一個(gè)完整的發(fā)起請(qǐng)求到返回響應(yīng)的流程就結(jié)束了。
Quick Start
可以在自定義 Request 和 Response 類(lèi)型之后,來(lái)使用 FakeHttpServer 快速開(kāi)發(fā)一個(gè)基于長(zhǎng)連接的 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é)
本文針對(duì)經(jīng)常使用長(zhǎng)連接進(jìn)行消息收發(fā)的應(yīng)答場(chǎng)景,采用 Websocket 長(zhǎng)連接作為服務(wù)監(jiān)聽(tīng)的對(duì)象,模擬了一套類(lèi) http 服務(wù)框架。該框架結(jié)合了長(zhǎng)連接自定義通訊 api 的靈活和 http 服務(wù)框架的自動(dòng)應(yīng)答處理機(jī)制,提供了一種開(kāi)銷(xiāo)小、方便、統(tǒng)一、標(biāo)準(zhǔn)化的方式來(lái)使用長(zhǎng)連接進(jìn)行數(shù)據(jù)通訊。
到此這篇關(guān)于Javascript 基于長(zhǎng)連接的服務(wù)框架的文章就介紹到這了,更多相關(guān)js長(zhǎng)連接服務(wù)框架內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
微信小程序使用GoEasy實(shí)現(xiàn)websocket實(shí)時(shí)通訊
這篇文章主要介紹了微信小程序使用GoEasy實(shí)現(xiàn)websocket實(shí)時(shí)通訊的方法,本文通過(guò)實(shí)例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2020-05-05
jquery div模態(tài)窗口的簡(jiǎn)單實(shí)例
下面小編就為大家?guī)?lái)一篇jquery div模態(tài)窗口的簡(jiǎn)單實(shí)例。小編覺(jué)得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2016-05-05
簡(jiǎn)單的前端js+ajax 購(gòu)物車(chē)框架(入門(mén)篇)
其實(shí),一直想把自己寫(xiě)的一些js給總結(jié)下,也許是能力有限不能把它完美結(jié)合起來(lái)。只能自己默默的看著哪些代碼,無(wú)能為力2011-10-10
layuiAdmin循環(huán)遍歷展示商品圖片列表的方法
今天小編就為大家分享一篇layuiAdmin循環(huán)遍歷展示商品圖片列表的方法,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2019-09-09
PHP實(shí)現(xiàn)的各種中文編碼轉(zhuǎn)換類(lèi)分享
這篇文章主要介紹了PHP實(shí)現(xiàn)的各種中文編碼轉(zhuǎn)換類(lèi)分享,本文類(lèi)庫(kù)支持簡(jiǎn)體中文、繁體中文、GB2312、BIG5、UTF-8等多種格式之間的轉(zhuǎn)換,需要的朋友可以參考下2015-01-01
JS實(shí)現(xiàn)兼容性好,帶緩沖的動(dòng)感網(wǎng)頁(yè)右鍵菜單效果
這篇文章主要介紹了JS實(shí)現(xiàn)兼容性好,帶緩沖的動(dòng)感網(wǎng)頁(yè)右鍵菜單效果,可實(shí)現(xiàn)帶有彈性效果并且能夠自定義鼠標(biāo)事件的右鍵菜單功能,具有一定參考借鑒價(jià)值,需要的朋友可以參考下2015-09-09
JavaScript 語(yǔ)言精粹學(xué)習(xí)筆記
JavaScript建立在一些非常好的想法和少數(shù)非常壞的想法之上。2009-08-08
Bootstrap實(shí)現(xiàn)登錄校驗(yàn)表單(帶驗(yàn)證碼)
本文給大家介紹使用Bootstrap新制作的一個(gè)登錄框,帶驗(yàn)證碼,帶校驗(yàn),非常不錯(cuò),具有參考借鑒價(jià)值,感興趣的朋友可以參考下2016-06-06

