Theia APIs——经过JSON-RPC进行通讯

上一篇:Theia APIs——事件html

经过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包含了服务器和客户端须要实现的接口。node

  这里的服务器指的是将经过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 => {

  咱们来看看这个类的实现作了哪些事情:webpack

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

  咱们一行一行来看:git

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

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

const proxy = factory.createProxy();

  而后,咱们从工厂建立了一个代理对象,它将使用ILoggerClient接口来调用JSON-RPC链接的另外一端。web

factory.target = this.targetFactory(proxy);

  上面这一行将调用咱们在参数中传递的函数,因此:express

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

  其中最重要的几行:json

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
 
相关文章
相关标签/搜索