• Theia APIs——通过JSON-RPC进行通信


    上一篇:Theia APIs——事件

    通过JSON-PRC进行通信

      在本节中,我将讲解如何创建后端服务并通过JSON-PRC来连接它。
      我将使用debug logging system作为例子来进行讲解。

    概述

      本示例将用express框架创建一个服务,然后通过websocket连接该服务。

    注册服务

      首先要做的是将服务公开,这样前端就能连接它。
      你需要创建一个后端服务模块(类似logger-server-module.ts):
    import { ContainerModule } from 'inversify';
    import { ConnectionHandler, JsonRpcConnectionHandler } from "../../messaging/common";
    import { ILoggerServer, ILoggerClient } from '../../application/common/logger-protocol';
    
    export const loggerServerModule = new ContainerModule(bind => {
        bind(ConnectionHandler).toDynamicValue(ctx =>
            new JsonRpcConnectionHandler<ILoggerClient>("/services/logger", client => {
                const loggerServer = ctx.container.get<ILoggerServer>(ILoggerServer);
                loggerServer.setClient(client);
                return loggerServer;
            })
        ).inSingletonScope()
    });

      我们来详细看一下:

    import { ConnectionHandler, JsonRpcConnectionHandler } from "../../messaging/common";
      这一行导入了JsonRpcConnectionHandler,这是一个工厂类,我们用它创建了一个onConnection连接处理程序,它为后端通过JSON-RPC调用的对象创建一个代理,并将一个本地对象公开给JSON-RPC。
    接下来我们来看看具体的实现过程。
      ConnectionHandler是一个简单的接口,它指定了连接的路径以及在连接创建时的行为。
      它是这样的:
    import { MessageConnection } from "vscode-jsonrpc";
    
    export const ConnectionHandler = Symbol('ConnectionHandler');
    
    export interface ConnectionHandler {
        readonly path: string;
        onConnection(connection: MessageConnection): void;
    }
    import { ILoggerServer, ILoggerClient } from '../../application/common/logger-protocol';

      文件logger-protocol.ts包含了服务器和客户端需要实现的接口。

      这里的服务器指的是将通过JSON-RPC调用的后端对象,而客户端指的是可以接收来自后端对象的通知的对象。
      稍后我们会详细介绍。
    bind<ConnectionHandler>(ConnectionHandler).toDynamicValue(ctx => {
      这里有个地方很神奇,乍一看,它是一个ConnectionHandler的实现。
      神奇之处在于,这个ConnectionHandler类型是绑定到messaging-module.ts文件中的ContributionProvider的。
      所以,当MessageingContribution启动时(调用onStart),它为所有绑定ConnectionHandlers创建一个websocket连接。
      像这样(来自messageing-mocule.ts):
    constructor( @inject(ContributionProvider) @named(ConnectionHandler) protected readonly handlers: ContributionProvider<ConnectionHandler>) {
        }
    
        onStart(server: http.Server): void {
            for (const handler of this.handlers.getContributions()) {
                const path = handler.path;
                try {
                    createServerWebSocketConnection({
                        server,
                        path
                    }, connection => handler.onConnection(connection));
                } catch (error) {
                    console.error(error)
                }
            }
        }
      要深入了解ContributionProvider,可以参考这里
      然后:
    new JsonRpcConnectionHandler<ILoggerClient>("/services/logger", client => {

      我们来看看这个类的实现做了哪些事情:

    export class JsonRpcConnectionHandler<T extends object> implements ConnectionHandler {
        constructor(
            readonly path: string,
            readonly targetFactory: (proxy: JsonRpcProxy<T>) => any
        ) { }
    
        onConnection(connection: MessageConnection): void {
            const factory = new JsonRpcProxyFactory<T>(this.path);
            const proxy = factory.createProxy();
            factory.target = this.targetFactory(proxy);
            factory.listen(connection);
        }
    }
      我们看到,这里通过ConnectionHandler类的扩展创建了一个websocker连接,路径是"/services/logger"。
      让我们来看看这个onConnection具体做了什么:
    onConnection(connection: MessageConnection): void {
            const factory = new JsonRpcProxyFactory<T>(this.path);
            const proxy = factory.createProxy();
            factory.target = this.targetFactory(proxy);
            factory.listen(connection);

      我们一行一行来看:

    const factory = new JsonRpcProxyFactory<T>(this.path);

      上面这一行在路径"/services/logger"上创建了一个JsonRpcProxy。

    const proxy = factory.createProxy();

      然后,我们从工厂创建了一个代理对象,它将使用ILoggerClient接口来调用JSON-RPC连接的另一端。

    factory.target = this.targetFactory(proxy);

      上面这一行将调用我们在参数中传递的函数,所以:

    client => {
                const loggerServer = ctx.container.get<ILoggerServer>(ILoggerServer);
                loggerServer.setClient(client);
                return loggerServer;
            }
      这里在loggerServer上设置客户端,本例中它用于向前端发送有关日志更改的通知。
      同时它返回loggerServer,用作在JSON-RPC上公开的对象。
    factory.listen(connection);
      上面这一行将工厂连接到Connection。
      带有services/*路径的endpoints由webpack开发服务器提供,参见webpack.config.js
    '/services/*': {
            target: 'ws://localhost:3000',
            ws: true
        },

    连接到服务

      现在我们已经有了一个后端服务,让我们来看看如何从前端连接它。
      要做到这一点,你需要像下面这样:
    (来自logger-frontend-module.ts)
    import { ContainerModule, Container } from 'inversify';
    import { WebSocketConnectionProvider } from '../../messaging/browser/connection';
    import { ILogger, LoggerFactory, LoggerOptions, Logger } from '../common/logger';
    import { ILoggerServer } from '../common/logger-protocol';
    import { LoggerWatcher } from '../common/logger-watcher';
    
    export const loggerFrontendModule = new ContainerModule(bind => {
        bind(ILogger).to(Logger).inSingletonScope();
        bind(LoggerWatcher).toSelf().inSingletonScope();
        bind(ILoggerServer).toDynamicValue(ctx => {
            const loggerWatcher = ctx.container.get(LoggerWatcher);
            const connection = ctx.container.get(WebSocketConnectionProvider);
            return connection.createProxy<ILoggerServer>("/services/logger", loggerWatcher.getLoggerClient());
        }).inSingletonScope();
    });

      其中最重要的几行:

    bind(ILoggerServer).toDynamicValue(ctx => {
            const loggerWatcher = ctx.container.get(LoggerWatcher);
            const connection = ctx.container.get(WebSocketConnectionProvider);
            return connection.createProxy<ILoggerServer>("/services/logger", loggerWatcher.getLoggerClient());
        }).inSingletonScope();

      我们一行一行来看:

    const loggerWatcher = ctx.container.get(LoggerWatcher);
      这一行创建了一个监听器,它通过loggerWatcher客户端从后端获取有关事件的通知(loggerWatcher.getLoggerClient())。
      想要了解更多有关事件如何在theia中工作的信息,可以查看这里
    const connection = ctx.container.get(WebSocketConnectionProvider);

      上面这一行获得了一个websocket连接,它将被用来创建一个代理。

    return connection.createProxy<ILoggerServer>("/services/logger", loggerWatcher.getLoggerClient());

      我们将一个本地对象作为第二个参数传入,用来处理来自远程对象的JSON-RPC消息。有时,本地对象依赖于代理,在代理实例化之前无法实例化。这种情况下,代理接口应该实现JsonRpcServer,而本地对象应该作为客户端来提供。

    export type JsonRpcServer<Client> = Disposable & {
        setClient(client: Client | undefined): void;
    };
    
    export interface ILoggerServer extends JsonRpcServery<ILoggerClient> {
        // ...
    }
    
    const serverProxy = connection.createProxy<ILoggerServer>("/services/logger");
    const client = loggerWatcher.getLoggerClient();
    serverProxy.setClient(client);
      所以,在最后一行,我们将ILoggerServer接口绑定到JsonRpc代理。
      注意底层的调用:
    createProxy<T extends object>(path: string, target?: object, options?: WebSocketOptions): T {
            const factory = new JsonRpcProxyFactory<T>(path, target);
            this.listen(factory, options);
            return factory.createProxy();
        }
      这个和后端的例子很像。
      也许你也注意到了,就连接而言,这里前端是服务器而后端是客户端,但对我们的逻辑来说这并不重要。
      这里还有几点:
    • 在路径"logger"上创建JsonRpc代理。
    • 公开loggerWatcher.getLoggerClient()对象。
    • 返回ILoggerServer类型的代理。
      现在,ILoggerServer的实例通过JSON-RPC被代理到后端的LoggerServer对象。

    在示例的前端和后端加载模块

      现在我们已经有了这些模块,我们需要将它们引入到我们的示例中。我们将使用浏览器作为示例,在electron中代码是相同的。
    后端
      在examples/browser/src/backend/main.ts中,你需要像这样来引用:
    import { loggerServerModule } from 'theia-core/lib/application/node/logger-server-module';

      然后将其载入到主容器。

    container.load(loggerServerModule);
    前端
      在examples/browser/src/frontend/main.ts中,你需要像这样来引用:
    import { loggerFrontendModule } from 'theia-core/lib/application/browser/logger-frontend-module';
    container.load(frontendLanguagesModule);

    完成示例

       如果你想查看本文中提到的完整示例,可以查看这里的commit
     
  • 相关阅读:
    Oracle游标举例
    java程序写的模拟用户点击的程序(抢小米程序)
    最好的ASP.NET MVC入门 step by step 来自微软
    项目经理
    程序员的职场晋升之路
    程序员怎么样才能进入微软?
    浅谈程序员创业
    软件销售心得-送给自己卖软件的程序员
    lamda表达式,匿名函数
    为什么Flash没能在移动设备上挺住?
  • 原文地址:https://www.cnblogs.com/jaxu/p/12176861.html
Copyright © 2020-2023  润新知