上一篇:Theia APIs——事件

通过JSON-PRC进行通信

  在本节中,我将讲解如何创建后端服务并通过JSON-PRC来连接它。
  我将使用debug logging system作为例子来进行讲解。

概述

  本示例将用express框架创建一个服务,然后通过websocket连接该服务。

注册服务

  首先要做的是将服务公开,这样前端就能连接它。
  你需要创建一个后端服务模块(类似logger-server-module.ts):
  1. import { ContainerModule } from 'inversify';
  2. import { ConnectionHandler, JsonRpcConnectionHandler } from "../../messaging/common";
  3. import { ILoggerServer, ILoggerClient } from '../../application/common/logger-protocol';
  4.  
  5. export const loggerServerModule = new ContainerModule(bind => {
  6. bind(ConnectionHandler).toDynamicValue(ctx =>
  7. new JsonRpcConnectionHandler<ILoggerClient>("/services/logger", client => {
  8. const loggerServer = ctx.container.get<ILoggerServer>(ILoggerServer);
  9. loggerServer.setClient(client);
  10. return loggerServer;
  11. })
  12. ).inSingletonScope()
  13. });

  我们来详细看一下:

  1. import { ConnectionHandler, JsonRpcConnectionHandler } from "../../messaging/common";
  这一行导入了JsonRpcConnectionHandler,这是一个工厂类,我们用它创建了一个onConnection连接处理程序,它为后端通过JSON-RPC调用的对象创建一个代理,并将一个本地对象公开给JSON-RPC。
接下来我们来看看具体的实现过程。
  ConnectionHandler是一个简单的接口,它指定了连接的路径以及在连接创建时的行为。
  它是这样的:
  1. import { MessageConnection } from "vscode-jsonrpc";
  2.  
  3. export const ConnectionHandler = Symbol('ConnectionHandler');
  4.  
  5. export interface ConnectionHandler {
  6. readonly path: string;
  7. onConnection(connection: MessageConnection): void;
  8. }
  1. import { ILoggerServer, ILoggerClient } from '../../application/common/logger-protocol';

  文件logger-protocol.ts包含了服务器和客户端需要实现的接口。

  这里的服务器指的是将通过JSON-RPC调用的后端对象,而客户端指的是可以接收来自后端对象的通知的对象。
  稍后我们会详细介绍。
  1. bind<ConnectionHandler>(ConnectionHandler).toDynamicValue(ctx => {
  这里有个地方很神奇,乍一看,它是一个ConnectionHandler的实现。
  神奇之处在于,这个ConnectionHandler类型是绑定到messaging-module.ts文件中的ContributionProvider的。
  所以,当MessageingContribution启动时(调用onStart),它为所有绑定ConnectionHandlers创建一个websocket连接。
  像这样(来自messageing-mocule.ts):
  1. constructor( @inject(ContributionProvider) @named(ConnectionHandler) protected readonly handlers: ContributionProvider<ConnectionHandler>) {
  2. }
  3.  
  4. onStart(server: http.Server): void {
  5. for (const handler of this.handlers.getContributions()) {
  6. const path = handler.path;
  7. try {
  8. createServerWebSocketConnection({
  9. server,
  10. path
  11. }, connection => handler.onConnection(connection));
  12. } catch (error) {
  13. console.error(error)
  14. }
  15. }
  16. }
  要深入了解ContributionProvider,可以参考这里
  然后:
  1. new JsonRpcConnectionHandler<ILoggerClient>("/services/logger", client => {

  我们来看看这个类的实现做了哪些事情:

  1. export class JsonRpcConnectionHandler<T extends object> implements ConnectionHandler {
  2. constructor(
  3. readonly path: string,
  4. readonly targetFactory: (proxy: JsonRpcProxy<T>) => any
  5. ) { }
  6.  
  7. onConnection(connection: MessageConnection): void {
  8. const factory = new JsonRpcProxyFactory<T>(this.path);
  9. const proxy = factory.createProxy();
  10. factory.target = this.targetFactory(proxy);
  11. factory.listen(connection);
  12. }
  13. }
  我们看到,这里通过ConnectionHandler类的扩展创建了一个websocker连接,路径是"/services/logger"。
  让我们来看看这个onConnection具体做了什么:
  1. onConnection(connection: MessageConnection): void {
  2. const factory = new JsonRpcProxyFactory<T>(this.path);
  3. const proxy = factory.createProxy();
  4. factory.target = this.targetFactory(proxy);
  5. factory.listen(connection);

  我们一行一行来看:

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

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

  1. const proxy = factory.createProxy();

  然后,我们从工厂创建了一个代理对象,它将使用ILoggerClient接口来调用JSON-RPC连接的另一端。

  1. factory.target = this.targetFactory(proxy);

  上面这一行将调用我们在参数中传递的函数,所以:

  1. client => {
  2. const loggerServer = ctx.container.get<ILoggerServer>(ILoggerServer);
  3. loggerServer.setClient(client);
  4. return loggerServer;
  5. }
  这里在loggerServer上设置客户端,本例中它用于向前端发送有关日志更改的通知。
  同时它返回loggerServer,用作在JSON-RPC上公开的对象。
  1. factory.listen(connection);
  上面这一行将工厂连接到Connection。
  带有services/*路径的endpoints由webpack开发服务器提供,参见webpack.config.js:
  1. '/services/*': {
  2. target: 'ws://localhost:3000',
  3. ws: true
  4. },

连接到服务

  现在我们已经有了一个后端服务,让我们来看看如何从前端连接它。
  要做到这一点,你需要像下面这样:
(来自logger-frontend-module.ts)
  1. import { ContainerModule, Container } from 'inversify';
  2. import { WebSocketConnectionProvider } from '../../messaging/browser/connection';
  3. import { ILogger, LoggerFactory, LoggerOptions, Logger } from '../common/logger';
  4. import { ILoggerServer } from '../common/logger-protocol';
  5. import { LoggerWatcher } from '../common/logger-watcher';
  6.  
  7. export const loggerFrontendModule = new ContainerModule(bind => {
  8. bind(ILogger).to(Logger).inSingletonScope();
  9. bind(LoggerWatcher).toSelf().inSingletonScope();
  10. bind(ILoggerServer).toDynamicValue(ctx => {
  11. const loggerWatcher = ctx.container.get(LoggerWatcher);
  12. const connection = ctx.container.get(WebSocketConnectionProvider);
  13. return connection.createProxy<ILoggerServer>("/services/logger", loggerWatcher.getLoggerClient());
  14. }).inSingletonScope();
  15. });

  其中最重要的几行:

  1. bind(ILoggerServer).toDynamicValue(ctx => {
  2. const loggerWatcher = ctx.container.get(LoggerWatcher);
  3. const connection = ctx.container.get(WebSocketConnectionProvider);
  4. return connection.createProxy<ILoggerServer>("/services/logger", loggerWatcher.getLoggerClient());
  5. }).inSingletonScope();

  我们一行一行来看:

  1. const loggerWatcher = ctx.container.get(LoggerWatcher);
  这一行创建了一个监听器,它通过loggerWatcher客户端从后端获取有关事件的通知(loggerWatcher.getLoggerClient())。
  想要了解更多有关事件如何在theia中工作的信息,可以查看这里
  1. const connection = ctx.container.get(WebSocketConnectionProvider);

  上面这一行获得了一个websocket连接,它将被用来创建一个代理。

  1. return connection.createProxy<ILoggerServer>("/services/logger", loggerWatcher.getLoggerClient());

  我们将一个本地对象作为第二个参数传入,用来处理来自远程对象的JSON-RPC消息。有时,本地对象依赖于代理,在代理实例化之前无法实例化。这种情况下,代理接口应该实现JsonRpcServer,而本地对象应该作为客户端来提供。

  1. export type JsonRpcServer<Client> = Disposable & {
  2. setClient(client: Client | undefined): void;
  3. };
  4.  
  5. export interface ILoggerServer extends JsonRpcServery<ILoggerClient> {
  6. // ...
  7. }
  8.  
  9. const serverProxy = connection.createProxy<ILoggerServer>("/services/logger");
  10. const client = loggerWatcher.getLoggerClient();
  11. serverProxy.setClient(client);
  所以,在最后一行,我们将ILoggerServer接口绑定到JsonRpc代理。
  注意底层的调用:
  1. createProxy<T extends object>(path: string, target?: object, options?: WebSocketOptions): T {
  2. const factory = new JsonRpcProxyFactory<T>(path, target);
  3. this.listen(factory, options);
  4. return factory.createProxy();
  5. }
  这个和后端的例子很像。
  也许你也注意到了,就连接而言,这里前端是服务器而后端是客户端,但对我们的逻辑来说这并不重要。
  这里还有几点:
  • 在路径"logger"上创建JsonRpc代理。
  • 公开loggerWatcher.getLoggerClient()对象。
  • 返回ILoggerServer类型的代理。
  现在,ILoggerServer的实例通过JSON-RPC被代理到后端的LoggerServer对象。

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

  现在我们已经有了这些模块,我们需要将它们引入到我们的示例中。我们将使用浏览器作为示例,在electron中代码是相同的。
后端
  在examples/browser/src/backend/main.ts中,你需要像这样来引用:
  1. import { loggerServerModule } from 'theia-core/lib/application/node/logger-server-module';

  然后将其载入到主容器。

  1. container.load(loggerServerModule);
前端
  在examples/browser/src/frontend/main.ts中,你需要像这样来引用:
  1. import { loggerFrontendModule } from 'theia-core/lib/application/browser/logger-frontend-module';
  1. container.load(frontendLanguagesModule);

完成示例

   如果你想查看本文中提到的完整示例,可以查看这里的commit
 

Theia APIs——通过JSON-RPC进行通信的更多相关文章

  1. Theia APIs——Preferences

    上一篇:Theia APIs——命令和快捷键 Preferences Theia有一个preference service,模块可以通过它来获取preference的值,提供默认的preference ...

  2. 測试JSON RPC远程调用(JSONclient)

    #include <string> #include <iostream> #include <curl/curl.h> /* 标题:JSonclient Auth ...

  3. Theia APIs——事件

    上一篇:Theia APIs——Preferences 事件 Theia中的事件或许会让你感到困惑,希望本节能阐述清楚. 来看下面的代码: (来自logger-watcher.ts) @injecta ...

  4. .net core consul grpc--系统服务RPC实现通信(一)

    .net core grpc 系统服务实现通信(一) 现在系统都服务化,.net core 实现服务化的方式有很多,我们通过grpc实现客户端.服务端通信. grpc(https://grpc.io/ ...

  5. RPC 框架通信原理

    RPC是指远程过程调用,也就是说两台服务器A,B,一个应用部署在A服务器上,想要调用B服务器上应用提供的函数/方法,由于不在一个内存空间,不能直接调用,需要通过网络来表达调用的语义和传达调用的数据: ...

  6. [PHP 作为iOS后台Json格式HTTP通信及文件上传的实现]

    1.数据库连接 configmysql.php <?php $q = mysql_connect("localhost:8889","root",&quo ...

  7. Unity3d使用json与javaserver通信

    Unity3d使用json能够借助LitJson 下载LitJson,复制到Unity3d工作文件夹下 于是能够在代码中实现了 以下发送请求到server并解析 System.Collections. ...

  8. Theia APIs——命令和快捷键

    上一篇:使用Theia——创建语言支持 命令和快捷键 Theia可以通过多种不同的方式进行扩展.命令允许packages提供可以被其它包调用的唯一命令,还可以向这些命令添加快捷键和上下文,使得它们只能 ...

  9. golang RPC通信读写超时设置

    golang RPC通信中,有时候就怕读写hang住. 那是否可以设置读写超时呢? 1.方案一: 设置连接的读写超时 1.1 client RPC通信基于底层网络通信,可以通过设置connection ...

随机推荐

  1. PLSQL中的三种参数模式IN、OUT、IN OUT

    原文链接:https://www.cnblogs.com/zbj815/p/6854108.html 1.IN模式 IN模式是参数的默认模式,这种模式就是在程序运行的时候已经具有值,在程序体中值不会改 ...

  2. H3C 帧中继显示与调试

  3. H3C PPP MP配置示例二

  4. 关于 FormData 和 URLSearchParams

    一.FormData FormData 接口提供了一种表示表单数据的键值对的构造方式,经过它的数据可以使用 XMLHttpRequest.send() 方法送出,本接口和此方法都相当简单直接.如果送出 ...

  5. H3C 高级ACL

  6. QuartusII 13.0的完美破解

    网络上破解QuartusII 13.0软件的方法都不行,最后经过本人总结测试(独创),最终实现了QuartusII 13.0的破解,破解方法如下: 网上常规操作之后,会得到一个“license.dat ...

  7. linux 在 /proc 里实现文件

    所有使用 /proc 的模块应当包含 <linux/proc_fs.h> 来定义正确的函数. 要创建一个只读 /proc 文件, 你的驱动必须实现一个函数来在文件被读时产生数据. 当 某个 ...

  8. hdu 6579 Operation (在线线性基)

    传送门 •题意 一个数组a有n个数 m个操作 操作① 询问$[l,r]$区间的异或值 操作② 在数组末尾追加一个数x,数组长度变为$n+1$ 其中$l,r$不直接给出,其中$l=l%n+1,r=r%n ...

  9. VRChat之blender2.8版本设置

    推荐先看:VRChat模型制作及上传总篇(包含总流程和所需插件):https://www.cnblogs.com/raitorei/p/12015876.html blender2.8视频:https ...

  10. 剑指Offer-62.数据流中的中位数(C++/Java)

    题目: 如何得到一个数据流中的中位数?如果从数据流中读出奇数个数值,那么中位数就是所有数值排序之后位于中间的数值.如果从数据流中读出偶数个数值,那么中位数就是所有数值排序之后中间两个数的平均值.我们使 ...