详解Javascript 基于长连接的服务框架问题

背景

经常使用 Node 进行服务端开发的同学,想必都知道 Koa 框架。Koa 是一种 http 服务框架,其基于洋葱模型作为基本架构,能够让用户方便快捷地添加中间件,进行业务开发。而 websocket 是一种长连接的服务通信协议,需要自定义通讯 api 进行数据通讯。一般情况下,基于 websocket 的通讯 api 也是遵循一问一答的交互模式的,只是通信发起方可能会是客户端,也可能会是服务方。

在 MBox 研发助手的开发中,前端和服务端处于平等的地位,前端和服务端都有可能发起请求,所以采用 websocket 协议作为通信协议。在 websocket 基础上,MBox 研发助手自定义的通讯 api 与 http 有相似之处,同样采用一问一答的交互模式。为了减少其他开发同学的理解成本,维持接口的统一性和可扩展性,在 MBox 研发助手中我们设计了一套基于长连接的服务框架。

Webscoket 封装

首先为了方便使用 websocket 接收消息,采用注册回调函数的方式分发服务端发来的消息。

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 封装成了一个对象,采用代理的方式来控制对 request 和 response 的访问,用户可以通过 context 间接操作 request 和 response。这里忽略了繁琐的代理内容,简单将 context 表示为拥有 request 和 response 的简单对象。Requset 和 Response 类型的具体定义可以根据业务进行抽象。

export interface Context {
  request: Request;
  response: Response;
}

Middleware

在 Express 和 Koa 等 web 服务框架中,中间件指的是处于 request -response 生命周期中,处理请求的一系列函数。中间件函数对代码进行了解耦,各个中间件之间无感知,每个中间件只需要处理自己的事情即可。使用 Promise 实现中间件函数的级联操作,其核心代码逻辑如下:

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 级联处理请求的原理之后,你已经明白 web 服务框架最基本两个模块了,下面开始了解 FakeHttpServer 从接收 request 到返回 response 的处理过程。

FakeHttpServer 服务框架基于长连接的特点就体现在使用 websocket 作为底层收发的数据协议,使用 listen 函数进行请求监听需要传入一个 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;

        // 调用 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 函数注册了 Connection 的回调,当客户端发送消息时会调用 receive 函数进行处理。首先,FakeHttpServer 会将 Response 类型的消息抛弃,只对 Request 请求消息进行响应。然后,FakeHttpServer 会创建一个新的 context,将 request 和空 response 放入。最后使用 compose 后的中间件函数数组处理请求,返回响应。至此,一个完整的发起请求到返回响应的流程就结束了。

Quick Start

可以在自定义 Request 和 Response 类型之后,来使用 FakeHttpServer 快速开发一个基于长连接的 http 模拟服务:

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());

小结

本文针对经常使用长连接进行消息收发的应答场景,采用 Websocket 长连接作为服务监听的对象,模拟了一套类 http 服务框架。该框架结合了长连接自定义通讯 api 的灵活和 http 服务框架的自动应答处理机制,提供了一种开销小、方便、统一、标准化的方式来使用长连接进行数据通讯。

到此这篇关于Javascript 基于长连接的服务框架的文章就介绍到这了,更多相关js长连接服务框架内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

你可能感兴趣的:(详解Javascript 基于长连接的服务框架问题)