通过 JSON-RPC 进行通信

在本节中,我将解释如何创建后端服务,然后通过 JSON-RPC 连接到它。

我将使用调试日志系统作为一个小例子。

概述
这通过 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 类型绑定到了 messing-module.ts 中的 ContributionProvider

因此,当 MessagingContribution 启动(调用 onStart)时,它会为所有绑定的 ConnectionHandlers 创建一个 websocket 连接。

像这样(来自messaging-module.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);
    }
}

我们看到一个 websocket 连接是通过 ConnectionHandler 类的扩展在 path: "logger" 上创建的,并且 path 属性设置为 "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);

这会在路径“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);

这将工厂连接到连接。

带有 services/* 路径的端点由 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 对象。

 

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

所以现在我们有了这些模块,我们需要将它们连接到示例中。 我们将为此使用浏览器示例,注意它与电子示例的代码相同。

后端
在示例/浏览器/src/backend/main.ts 中,您将需要以下内容:

import { loggerServerModule } from 'theia-core/lib/application/node/logger-server-module';

然后将其加载到主容器中:

container.load(loggerServerModule);

前端

在示例/浏览器/src/frontend/main.ts 中,您将需要以下内容:

 
import { loggerFrontendModule } from 'theia-core/lib/application/browser/logger-frontend-module';
container.load(frontendLanguagesModule);

完整示例

如果您希望查看我在本文档中提到的内容的完整实现,请参阅此提交。(https://github.com/eclipse-theia/theia/commit/99d191f19bd2a3e93098470ca1bb7b320ab344a1)

 

 

posted @ 2022-08-09 12:08  theiaide  阅读(752)  评论(0编辑  收藏  举报