亲宝软件园·资讯

展开

js长连接服务框架

user1516019128585 人气:0

背景

经常使用 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 服务框架的自动应答处理机制,提供了一种开销小、方便、统一、标准化的方式来使用长连接进行数据通讯。

加载全部内容

相关教程
猜你喜欢
用户评论