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

 更新时间:2022年07月20日 15:36:00   作者:user1516019128585  
本文针对经常使用长连接进行消息收发的应答场景,采用 Websocket 长连接作为服务监听的对象,模拟了一套类 http 服务框架,通过实例代码介绍了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长连接服务框架内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • JS获取图片高度宽度的方法分享

    JS获取图片高度宽度的方法分享

    这篇文章主要介绍了JS获取图片高度宽度的方法,开始的时候没能做到兼容chrome,在度娘和众网友的帮助下,最终完成了完美兼容,这里分享给大家,有需要的小伙伴可以参考下。
    2015-04-04
  • javascript父子页面通讯实例详解

    javascript父子页面通讯实例详解

    这篇文章主要介绍了javascript父子页面通讯的实现方法,实例分析了javascript针对父子页面通讯的原理与相关实现技巧,具有一定参考借鉴价值,需要的朋友可以参考下
    2015-07-07
  • input 输入框内的输入事件详细分析

    input 输入框内的输入事件详细分析

    input 输入框内的输入事件详细分析,需要的朋友可以参考下。
    2010-03-03
  • bootstrap+jQuery实现的动态进度条功能示例

    bootstrap+jQuery实现的动态进度条功能示例

    这篇文章主要介绍了bootstrap+jQuery实现的动态进度条功能,结合完整实例形式分析了bootstrap+jQuery实现动态进度条的具体步骤与相关操作技巧,需要的朋友可以参考下
    2017-05-05
  • 基于JavaScript实现在新的tab页打开url

    基于JavaScript实现在新的tab页打开url

    这篇文章主要介绍了基于JavaScript实现在新的tab页打开url 的相关资料,非常不错,具有参考借鉴价值,需要的朋友可以参考下
    2016-08-08
  • TypeScript入门之利用装饰器扩展代码能力

    TypeScript入门之利用装饰器扩展代码能力

    在 TypeScript 中,装饰器是一种特殊的声明,可以让你的代码更有趣、更灵活,下面小编就来带大家学习一下TypeScript中装饰器的具体使用吧
    2023-06-06
  • 原型方法的不同写法居然会影响调试的解决方法

    原型方法的不同写法居然会影响调试的解决方法

    原型方法的不同写法居然会影响调试的解决方法...
    2007-03-03
  • 小程序:授权、登录、session_key、unionId的详解

    小程序:授权、登录、session_key、unionId的详解

    这篇文章主要介绍了小程序:授权、登录、session_key、unionId的详解,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2019-05-05
  • uniapp使用uni自带websocket进行即时通讯详细步骤

    uniapp使用uni自带websocket进行即时通讯详细步骤

    在开发程序过程中通信功能还是比较常用到的,下面这篇文章主要给大家介绍了关于uniapp使用uni自带websocket进行即时通讯的详细步骤,文中通过代码介绍的非常详细,需要的朋友可以参考下
    2023-11-11
  • js 字符串转换成数字的三种方法

    js 字符串转换成数字的三种方法

    在js读取文本框或者其它表单数据的时候获得的值是字符串类型的,例如两个文本框a和b,如果获得a的value值为11,b的value值为9 ,那么a.value要小于b.value,因为他们都是字符串形式的.在网上找了一下js字符串转数字的文章,这个比较全
    2013-03-03

最新评论