上一篇: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型別的代理,
在示例的前端和后端加載模塊
現在我們已經有了這些模塊,我們需要將它們引入到我們的示例中,我們將使用瀏覽器作為示例,在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, 原文地址:https://theia-ide.org/docs/json_rpc轉載請註明出處,本文鏈接:https://www.uj5u.com/qita/6683.html
標籤:其他
上一篇:Theia APIs——事件
