当前位置:
首页 > temp > JavaScript教程 >
-
Theia APIs——通过JSON-RPC进行通信
通过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。
原文地址:https://theia-ide.org/docs/json_rpc
栏目列表
最新更新
nodejs爬虫
Python正则表达式完全指南
爬取豆瓣Top250图书数据
shp 地图文件批量添加字段
爬虫小试牛刀(爬取学校通知公告)
【python基础】函数-初识函数
【python基础】函数-返回值
HTTP请求:requests模块基础使用必知必会
Python初学者友好丨详解参数传递类型
如何有效管理爬虫流量?
2个场景实例讲解GaussDB(DWS)基表统计信息估
常用的 SQL Server 关键字及其含义
动手分析SQL Server中的事务中使用的锁
openGauss内核分析:SQL by pass & 经典执行
一招教你如何高效批量导入与更新数据
天天写SQL,这些神奇的特性你知道吗?
openGauss内核分析:执行计划生成
[IM002]Navicat ODBC驱动器管理器 未发现数据
初入Sql Server 之 存储过程的简单使用
SQL Server -- 解决存储过程传入参数作为s
关于JS定时器的整理
JS中使用Promise.all控制所有的异步请求都完
js中字符串的方法
import-local执行流程与node模块路径解析流程
检测数据类型的四种方法
js中数组的方法,32种方法
前端操作方法
数据类型
window.localStorage.setItem 和 localStorage.setIte
如何完美解决前端数字计算精度丢失与数