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
 
相關文章
相關標籤/搜索