Welcome to my blog: see the world small melon | crownhuang. Cn /

What is RPC?

  • Remote Procedure Call (RPC). Simply understood, a node requests services provided by another node.
  • Local procedure call: If you want to call the getName() method of a local class, you simply implement a getName method and execute the corresponding code directly by running the local program.
  • Remote procedure call: If the getName() method is on the server and the method body executing the method is on the remote server, how do you tell the server that you need to call this method? In general:
  1. First, the client needs to tell the server which method of which class needs to be called. There is a mapping between the class and method and the process ID. When the client calls remotely, it needs to look up the method, find the corresponding ID, and then execute the code of the method.
  2. Client needs to pass the local parameters to a remote method, the process of local calls, direct pressure stack, but in the process of remote call is no longer the same memory, cannot directly transfer the parameters of the method, so we need the client into a byte stream, the parameters to the server, and then the server will be the byte stream into itself can read format, Is a serialization and deserialization process.
  3. Once the data is ready, how is it transmitted? The network transport layer needs to pass the ID and serialized parameters of the call to the server, and then serialize the calculated results to the client to complete a call process.

What are the application scenarios of JSON-RPC?

Vscode has developed a set of JSON-RPC plug-ins for LSPS based on JSON-RPC: Vscode-jsonrpc is developed based on the websocket version of VScode-WS-JSONRPC. This article is based on the development of the relevant mechanism of the plug-in.

Here’s an example:

The server has a GetHgService class with a getName() method

export class GetHgService implements GetHgServer {

    private client: GetHgClient | undefined;

    dispose(): void{ } getClient? (): GetHgClient {return this.client;
    }

    setClient(client: GetHgClient): void {
        this.client = client
    }
    getName(): string {
        console.log("server-message:execute getName!")
        this.client.onDone("hg");
        return this.getAge();
    }
    getAge(): string {
        console.log("server-message:execute getAge!")
        return "32"}}Copy the code

Now, if you want to call this method from the client’s browser:

import { getHgService } from "./frontend-application";

getHgService.getName();
Copy the code

The console on the server side prints:

App listening on http://localhost:3155.
Opening channel for service path '/services/search-in-workspace'. [ID: 0]
server-message:execute getName!
server-message:execute getAge!
Copy the code

Since the getName method on the server calls a client-side method: this.client.ondone (), the body of the method looks like this:

export class HGClient implements GetHgClient {
    onDone(args: string) :void {
        console.log("client-message:execute onDone!")
        console.log(args + "done!")}}Copy the code

So the client browser will print:

websocket open!
client-message:execute onDone!
hgdone!
Copy the code

Wow! Completely two separate services that can call a remote module from the client just as they would normally call a local module! This is the whole set of RPC calls. So, in order to implement this mechanism, how to design? This article will elaborate on the implementation process.

Overall structure:

Json-rpc overall call process design is shown in the figure above:

  • The communication layer
  • The communication layer mainly needs to establish a set of Websocket links for the server, and the process of establishing the connection can be used in the designexpressCreate a server that provides the ability to upgrade the protocol and receive webSocket requests from clients during the connection establishment handshake phase. The server is then availablewsModule to deal with express HTTP service upgrade after the establishment of a series of websocket onOpen, onMessage, onError and other monitoring events. The client needs to specify the server websocket address, which can be usedreconnecting-websocketFramework, which is compatible with all Websocket API standards, highly configurable mode capability, cross-platform features, reconnection support redirection URL, support fragment cache packet transmission after connection, and other capabilities can better meet the requirements of connecting to the server Websocket.
  • Broker layer
  • The proxy layer primarily allows clients and servers to create proxy objects using their proxy factories. Vscode-json-rpc framework is used to process jSON-RPC communication packets based on WebSocket. Agent layer can provide a service for the client side proxy objects (and vice versa), allows the server to build an RPC service, specify a URL of the service, and associated own an entity object or class so that the client proxy objects can access the service URL to associate an entity object from the server.
  • The interface layer
  • A proxy object is actually a concrete interface between a server and a client defined by the interface layer, and therefore belongs to a common module. The server needs to implement the methods defined in the interface so that the client proxy object can call them (and vice versa).
  • The service layer
  • The service layer is specifically implemented against the interface layer, so that the client proxy object actually calls the interface, and the implementation classes of the specific service on the server side implement the interface (and vice versa).
  • Call the layer
  • At the scheduling level, the client needs to create a proxy object for the server and pass its own proxy object interface to the server if the server also calls some methods of the client. The server needs to bind the corresponding implementation class in the scheduling layer to distribute the specific implementation class to the RPC request initiated by the client.

First, in the communication layer, Express creates a server:

//json-rpc-learn\src\server\backend-application.ts
export class BackendApplication {
protected readonly app: express.Application = express();
asyncstart(aPort? :number, aHostname? :string) :Promise<http.Server | https.Server> {
	server = http.createServer(this.app);
	newMessagingContribution().onStart! (server) } }Copy the code

The above process is to pass the Server service created by Express to the MessagingContibution class, which is used for the key functions of websocket processing after the UPGRADE of HTTP service binding WS. First, initialize the happy song MessagingContibution instance. The constructor for this instance is as follows:

//json-rpc-learn\src\server\messaging-contribution.ts
constructor() {
        this.ws(WebSocketChannel.wsPath, (_, socket) = > this.handleChannels(socket));
    }
ws(spec: string.callback: (params: MessagingService.PathParams, socket: ws) = > void) :void {
        this.wsHandlers.push(spec, callback);
    }
Copy the code

As you can see, the strength constructor calls the WS method. And specifies a websocket service to the root of the service address, such as the purpose of this article, we can give the wsPath assignment/service, the client to establish a connection, the need to access the ws: / / X.X.X.X: XXXX/service. Also, the WS method needs to pass in a callback function (_,socket)=> this.handlechannels (socket). This means that WS will trigger the this.handlechannels (socket) method at some point. As for the timing, we’ll work our way down.

//json-rpc-learn\src\server\messaging-contribution.ts
export class MessagingContribution {... . onStart(server: http.Server | https.Server):void {
        this.webSocketServer = new ws.Server({
            noServer: true.perMessageDeflate: {
                // don't compress if a message is less than 256kb
                threshold: 256 * 1024}}); server.on('upgrade'.this.handleHttpUpgrade.bind(this)); }... . }Copy the code

After the server receives the upgrade request, the MessagingContribution is distributed to the handleHttpUpgrade method for processing:

//json-rpc-learn\src\server\messaging-contribution.ts
protected handleHttpUpgrade(request: http.IncomingMessage, socket: net.Socket, head: Buffer): void {
        try {
            this.webSocketServer! .handleUpgrade(request, socket, head,client= > {
                this.webSocketServer! .emit('connection', client, request);
                // this.messagingListener.onDidWebSocketUpgrade(request, client); // Some listener callbacks are triggered when an upgrade operation is listened on
            });
        } catch (error) {
            console.error(error);
            socket.write('HTTP / 1.1 500 Internal Error \ n \ n'); socket.destroy(); }}Copy the code

In the handleHttpUpgrade method, the WS module throws a Connection event, and ws listens for the connection event when it establishes the connection, triggering the subsequent link establishment logic.

onStart(server: http.Server | https.Server): void{... .this.webSocketServer.on('connection'.(socket: CheckAliveWS, request) = > {
				socket.alive = true;
				socket.on('pong'.() = > socket.alive = true);
				this.handleConnection(socket, request); }); . . }Copy the code

The connection listener is distributed to the handleConnection method, which will be covered in more detail in the scheduling layer tutorial. Let’s go back to the client side of the communication layer and see what’s going on. First, the client establishes a Websocket connection with the server:

//json-rpc-learn\src\client\ws-connection-provider.ts
export class WebSocketConnectionProvider extends AbstractConnectionProvider<WebSocketOptions> {
	constructor(port: any) {
			super(a);this.port = port;
			const url = this.createWebSocketUrl("/services");
			const socket = this.createWebSocket(url); }}Copy the code

The reconnection-websocket framework is used to create a websocket link to the server:

// json-rpc-learn\src\client\ws-connection-provider.ts
protected createWebSocket(url: string): ReconnectingWebSocket {
        return new ReconnectingWebSocket(url, undefined, {
            WebSocket: WS,
            maxReconnectionDelay: 10000.minReconnectionDelay: 1000.reconnectionDelayGrowFactor: 1.3.connectionTimeout: 10000.maxRetries: Infinity.debug: false
        });
    }
Copy the code

From this, the client can establish a link with the server. Next, in the proxy layer, we first start from the communication layer above, the communication layer server WS module in the connection listener, will be distributed to the handleConnection method, which does what? Since all requests are distributed through a Websocket channel, in order to distinguish each service request, different services need to be scheduled for processing according to the following service name in the URL. For example, when we send the ws: / / 127.0.0.1:8001 / service/crownhuang, can in this path by judge services to distribute to specific/services/crownhuang service to deal with, very similar to the service for springmvc distribution mechanism. HandleConnection is mainly used for service distribution based on the url routing path of the request:

// json-rpc-learn\src\server\messaging-contribution.ts
protected handleConnection(socket: ws, request: http.IncomingMessage): void {
        const pathname = request.url && url.parse(request.url).pathname;
        if (pathname && !this.wsHandlers.route(pathname, socket)) {
            console.error('Cannot find a ws handler for the path: '+ pathname); }}Copy the code

The method is retrieved from the wsHandler array based on the URL of the incoming request and forwarded to the specific processing service. As mentioned above, in the constructor of the MessagingContribution, wsPath, also known as /services, is stored in the wsHandler, which is the so-called first-level routing. WsHandler as a whole stores many callbacks for service processing based on different tier 1 route names. Also, the WS method needs to pass in a callback function (_,socket)=> this.handlechannels (socket). So, when the client access to the ws: / / X.X.X.X: XXXX/services, the handshake phase, a connection is established the service side trigger the route method, HandleChannels (socket) => this.handlechannels (socket) and passes the current socket link to this.Handlechannels. The route method is as follows:

// json-rpc-learn\src\server\messaging-contribution.ts
route(path: string.connection: T): string | false {
            for (const handler of this.handlers) {
                try {
                    const result = handler(path, connection);
                    if (result) {
                        returnresult; }}catch (e) {
                    console.error(e); }}if (this.parent) {
                return this.parent.route(path, connection);
            }
            return false;
        }
Copy the code

Next, we’ll look at the functionality of the this.handlechannels (Socket) method.

// json-rpc-learn\src\server\messaging-contribution.ts
protected handleChannels(socket: ws): void {
        const channelHandlers = this.getConnectionChannelHandlers(socket);
        const channels = new Map<number, WebSocketChannel>();
        socket.on('message'.data= > {
            try {
                const message: WebSocketChannel.Message = JSON.parse(data.toString());
                if (message.kind === 'open') {
                    const { id, path } = message;
                    const channel = this.createChannel(id, socket);
                    if (channelHandlers.route(path, channel)) {
                        channel.ready();
                        console.debug(`Opening channel for service path '${path}'. [ID: ${id}] `);
                        channels.set(id, channel);
                        channel.onClose(() = > {
                            console.debug(`Closing channel on service path '${path}'. [ID: ${id}] `);
                            channels.delete(id);
                        });
                    } else {
                        console.error('Cannot find a service for the path: '+ path); }}else {
                    const { id } = message;
                    const channel = channels.get(id);
                    if (channel) {
                        channel.handleMessage(message);
                    } else {
                        console.error('The ws channel does not exist', id); }}}catch (error) {
                console.error('Failed to handle message', { error, data }); }}); socket.on('error'.err= > {/ /... omit
        });
        socket.on('close'.(code, reason) = > { / /... omit
        });
    }
Copy the code

First, from the big box, mainly for the incoming socket link related life cycle function processing. But the beginning of a piece of code that calls the enclosing getConnectionChannelHandlers (socket); Method, the method is as follows:

// json-rpc-learn\src\server\messaging-contribution.ts
protected getConnectionChannelHandlers(socket: ws): MessagingContribution.ConnectionHandlers<WebSocketChannel> {
        const connectionChannelHandlers = new MessagingContribution.ConnectionHandlers(this.channelHandlers);
        connectionChannelHandlers.push(HgServiceConnectionhandler.path, (_, channel) = > {
            const connection = createWebSocketConnection(channel, new ConsoleLogger());
            HgServiceConnectionhandler.onConnection(connection);
        });
        return connectionChannelHandlers;
    }
Copy the code

The code above is a bit obscure, but it’s familiar. Inside this method, a new instance of connectionHandler is created, which is the same as the wsHanlder instance we started with: protected readonly channelHandlers = new MessagingContribution.ConnectionHandlers

(); So what’s the difference between the two? See through the code, in the push method, the incoming is HgServiceConnectionhandler path, its specific for export const HG_WS_PATH = ‘/ services/hg – service; A level-1 route can also be bound to multiple level-2 routes. In the preceding code, it means that a level-2 route is bound to an HgService. If there are multiple level-2 routes, they need to be bound in this method. So once the client to establish a link to access the ws: / / X.X.X.X: XXXX/service/hg – service, will trigger the code block (_, channel) = {} in the callback function. It iterates through the channelHandlers array based on the secondary routing address to see if there are matching services to handle the request. The channelHandlers array is used to pass in different service instances during connection establishment. For example, the code we created a HgService service, the path of the instance (HgServiceConnectionhandler. Path) and listening to the established methods of dealing with the links ((_, channel) = > {… }) to channelHandlers through the code above. When the message is received, the message kind is used to determine whether it is opened for the first time or already opened. If it is open, the message path is used to determine whether it exists in the channelHandlers, that is, whether it has been registered. For example: New JsonRpcConnectionHandler

(“/services/hg-service”, client => {, where path is “/services/hg-service”, If so, it is saved with a unique ID in a Map called Channels; If it is not open, the corresponding channel is retrieved from the Map and its handleMessage method is called for message processing.

Where ID is a field in the data format generated by vscode-WS-JSONRPC package. When open, it is not JSON-RPC. When not open, message is the data in JSON-RPC format, which is also generated by VScode-WS-JsonRPC.

More on channels later.

How does the back end process messages

Before we look at how the back end handles messages, we need to understand what a channel is. Let’s start with the following code:

const channel = this.createChannel(id, socket);
Copy the code

The above code creates a channel that implements the following:

protected createChannel(id: number.socket: ws): WebSocketChannel {
    return new WebSocketChannel(id, content= > {
        if (socket.readyState < ws.CLOSING) {
            socket.send(content, err= > {
                if (err) {
                    throwerr; }}); }}); }Copy the code

Here we find that this channel is actually a WebSocketChannel. Let’s look at the code for WebSocketChannel:

export class WebSocketChannel implements IWebSocket {
	static wsPath = '/services';
	// ...
	constructor(
        readonly id: number.protected readonly doSend: (content: string) = >void
    ){}// ...
	handleMessage(message: WebSocketChannel.Message): void {
        if (message.kind === 'ready') {
            this.fireOpen();
        } else if (message.kind === 'data') {
            this.fireMessage(message.content);
        } else if (message.kind === 'close') {
            this.fireClose(message.code, message.reason);
        }
    }

	open(path: string) :void {
        this.checkNotDisposed();
        this.doSend(JSON.stringify(<WebSocketChannel.OpenMessage>{
            kind: 'open'.id: this.id,
            path
        }));
    }

	ready(): void {
        this.checkNotDisposed();
        this.doSend(JSON.stringify(<WebSocketChannel.ReadyMessage>{
            kind: 'ready'.id: this.id
        }));
    }

    send(content: string) :void {
        this.checkNotDisposed();
        this.doSend(JSON.stringify(<WebSocketChannel.DataMessage>{
            kind: 'data'.id: this.id,
            content
        }));
    }
	// ...
	protected fireOpen: () = > void = () = >{}; onOpen(cb:() = > void) :void {
        this.checkNotDisposed();
        this.fireOpen = cb;
        this.toDispose.push(Disposable.create(() = > this.fireOpen = () = >{})); }protected fireMessage: (data: any) = > void = () = >{}; onMessage(cb:(data: any) = > void) :void {
        this.checkNotDisposed();
        this.fireMessage = cb;
        this.toDispose.push(Disposable.create(() = > this.fireMessage = () = >{})); }// ...
}
Copy the code

Return to the code above to process the message as follows:

channel.handleMessage(message);
Copy the code

HandleMessage means that the message is passed to the handleMessage method of the channel, and the implementation of the handler can be found through the WebSocketChannel. Here we look at the jSON-RPC message processing part, as follows:

if (message.kind === 'data') {
    this.fireMessage(message.content);
}
Copy the code

This method can be assigned by the onMessage method of the channel. Vscode-ws-jsonrpc has an assignment as follows:

//vscode-ws-jsonrpc/src/socket/reader.ts
export class WebSocketMessageReader extends AbstractMessageReader {
	// ...
	constructor(protected readonly socket: IWebSocket) {
        super(a);this.socket.onMessage(message= >
            this.readMessage(message)
        );
		// ...
	}
	// ...
	protected readMessage(message: any) :void {
        if (this.state === 'initial') {
            this.events.splice(0.0, { message });
        } else if (this.state === 'listening') {
            const data = JSON.parse(message);
            this.callback!(data);
        }
    }
	// ...
}
Copy the code

Eventually vscode-jsonRPC will trigger the above code block. When will it trigger? When a request is received and fireMessage is triggered, the vscode-WS-JsonRPC plug-in’s readMessage method is called. ReadMessage calls this.callback! Connection.listen () is executed when the listen method is called from JsonRpcProxyFactory below; Bind callback, so what does this listen method mainly associate with? Vscode-jsonrpc internally specifies a series of message types, such as Request, Notification, and Disposable. The response function processing of these message types in JsonRpcProxyFactory is conducted in listen method. See the detailed code below:

// json-rpc-learn\src\server\messaging-contribution.ts
connectionChannelHandlers.push(HgServiceConnectionhandler.path, (_, channel) = > {
            const connection = createWebSocketConnection(channel, new ConsoleLogger());
            HgServiceConnectionhandler.onConnection(connection);
        });


//
export class JsonRpcConnectionHandler<T extends object> implements ConnectionHandler {
    constructor(. .) { }

    onConnection(connection: MessageConnection): void {
        const factory = new this.factoryConstructor();
        const proxy = factory.createProxy();
        factory.target = this.targetFactory(proxy); factory.listen(connection); }}export class JsonRpcProxyFactory<T extends object> implements ProxyHandler<T> {
	listen(connection: MessageConnection): void {
			connection.onRequest((prop, ... args) = > this.onRequest(prop, ... args)); connection.onNotification((prop, ... args) = > this.onNotification(prop, ... args)); connection.onDispose(() = > this.waitForConnection());
			connection.listen();
			this.connectionPromiseResolve(connection); }}Copy the code

So, if you bind LISTEN, you will associate vscode-WS-JsonRPC with vscode-WS-JsonRPC. Once the server receives the request, the callback will be triggered. Vscode-ws-jsonrpc will place all requests processed in a message queue and trigger the application layer callback function step by step based on the request type. Next, we’ll go through the whole process of processing messages at the front and back ends.

How does the front end connect to the WS on the back end and process jSON-RPC messages

Call procedure:

Json-rpc proxy can be used to call server methods directly from the client and client methods directly from the server. The following code comment in theia gives you an idea of the usage.

proxy-factory.ts

/** * Factory for JSON-RPC proxy objects. * * A JSON-RPC proxy exposes the programmatic interface of an object through *  JSON-RPC. This allows remote programs to call methods of this objects by * sending JSON-RPC requests. This takes place over a bi-directional stream, * where both ends can expose an object and both can call methods each other's * exposed object. * * For example, assuming we have an object of the following type on one end: * * class Foo { * bar(baz: number): number { return baz + 1 } * } * * which we want to expose through a JSON-RPC interface. We would do: * * let target = new Foo() * let factory = new JsonRpcProxyFactory<Foo>('/foo', target) * factory.onConnection(connection) * * The party at the other end of the `connection`, in order to remotely call * methods on this object would do: * * let factory = new JsonRpcProxyFactory<Foo>('/foo') * factory.onConnection(connection) * let proxy = factory.createProxy(); * let result = proxy.bar(42) * // result is equal to 43 * * One the wire, it would look like this: * * --> {"jsonrpc": "2.0", "id" : 0, "method" : "bar", "params" : {42} "baz" : < -- {} * "jsonrpc" : "2.0", "id" : 0, "result" : 43} * * Note that in the code of the caller, we didn't pass a target object to * JsonRpcProxyFactory, because we don't want/need to expose an object. * If we had passed a target object, the other side could've called methods on * it. * *@param <T> - The type of the object to expose to JSON-RPC.
 */
export class JsonRpcProxyFactory<T extends object> implements ProxyHandler<T> {
	// ...
}
Copy the code

So how to achieve this effect? JsonRpcProxyFactory (JsonRpcProxyFactory) Back to the place used above, the front end is ready to establish a link with the server, the front end and the back end to establish a connection main code is as follows:

The front end

createProxy<T extends object>(path: string, arg? :object): JsonRpcProxy<T> {
    const factory = arg instanceof JsonRpcProxyFactory ? arg : new JsonRpcProxyFactory<T>(arg);
    this.listen({
        path,
        onConnection: c= > factory.listen(c)
    });
    return factory.createProxy();
}
Copy the code

The back-end

// readonly factoryConstructor: new () => JsonRpcProxyFactory<T> = JsonRpcProxyFactory
onConnection(connection: MessageConnection): void {
    const factory = new this.factoryConstructor();
    const proxy = factory.createProxy();
    factory.target = this.targetFactory(proxy);
    factory.listen(connection);
}
Copy the code

JsonRpcProxyFactory creates a proxy object with createProxy, which can be used to create a proxy object.

export class JsonRpcProxyFactory<T extends object> implements ProxyHandler<T> {
	// ...
	/**
     * Build a new JsonRpcProxyFactory.
     *
     * @param target - The object to expose to JSON-RPC methods calls.  If this
     *   is omitted, the proxy won't be able to handle requests, only send them.
     */
    constructor(publictarget? :any) {
        this.waitForConnection();
    }
	// ...
	/** * Connect a MessageConnection to the factory. * * This connection will be used to send/receive JSON-RPC requests and  * response. */
    listen(connection: MessageConnection): void {
        connection.onRequest((prop, ... args) = > this.onRequest(prop, ... args)); connection.onNotification((prop, ... args) = > this.onNotification(prop, ... args)); connection.onDispose(() = > this.waitForConnection());
        connection.listen();
        this.connectionPromiseResolve(connection);
    }
	/**
     * Process an incoming JSON-RPC method call.
     *
     * onRequest is called when the JSON-RPC connection received a method call
     * request.  It calls the corresponding method on [[target]].
     *
     * The return value is a Promise object that is resolved with the return
     * value of the method call, if it is successful.  The promise is rejected
     * if the called method does not exist or if it throws.
     *
     * @returns A promise of the method call completion.
     */
    protected async onRequest(method: string. args:any[]) :Promise<any> {
        // ...
		return await this.target[method](... args);// ...
    }
	// ...
	/** * Create a Proxy exposing the interface of an object of type T. This Proxy * can be used to do JSON-RPC method calls  on the remote target object as * if it was local. * * If `T` implements `JsonRpcServer` then a client is used as a target object for a remote target object. */
    createProxy(): JsonRpcProxy<T> {
        const result = new Proxy<T>(this as any.this);
        return result as any;
    }

	get(target: T, p: PropertyKey, receiver: any) :any {
		if (p === 'setClient') {
            return (client: any) = > {
                this.target = client;
            };
        }
        if (p === 'getClient') {
            return () = > this.target;
        }
		// ...
		return (. args:any[]) = > {
			// ...
			constresultPromise = connection.sendRequest(method, ... args)as Promise<any>;
			// ...}}}Copy the code

How do the front and back ends send messages to each other

  • Straight throughchannel.send(message)Send a message
  • Available through the proxy objectgetmethods

Channel.send was mentioned above, so let’s see how to send messages using proxy objects.

We can see from the factory function above that reading the properties on the proxy object triggers the internally declared get method, which executes:

constresultPromise = connection.sendRequest(method, ... args)as Promise<any>;
Copy the code

Where the sendRequest method is in vscode-jsonrpc/lib/main.js, the relevant code is as follows:

vscode-jsonrpc/lib/main.js

sendRequest: (type, ... params) = > {
	// ...
	messageWriter.write(requestMessage);
	// ...
}
Copy the code

vscode-ws-jsonrpc/src/socket/writer.ts

class WebSocketMessageWriter extends messageWriter_1.AbstractMessageWriter {
    constructor(protected readonly socket: IWebSocket) {
        super(a); }write(msg) {
        // ...
		this.socket.send(content);
		// ...}}Copy the code

vscode-ws-jsonrpc/src/socket/connection.ts

export function createWebSocketConnection(socket: IWebSocket, logger: Logger) :MessageConnection {
    const messageReader = new WebSocketMessageReader(socket);
    const messageWriter = new WebSocketMessageWriter(socket);
    const connection = createMessageConnection(messageReader, messageWriter, logger);
    connection.onClose(() = > connection.dispose());
    return connection;
}
Copy the code

CreateWebSocketConnection is the key method in this paper, we create the connection through the channel. Thus, when the client calls gethgService.getName (); Const resultPromise = connection.sendrequest (method,… args) as Promise

; This sends a request to the server. In JsonRpcProxyFactory, the listen method binds a series of functions to handle JSON-RPC messages. When the server receives the message, the fireMessage method is triggered, as described in the code above, which triggers the readMessage method originally bound, which is associated with the set of callbacks bound in LISTEN. If the message type is Request, the onRequest listener function in LISTEN will be triggered, which will trigger the following code:

protected async onRequest(method: string. args:any[]) :Promise<any> {
        try {
            if (this.target) {
                return await this.target[method](... args); }else {
                throw new Error(`no target was set to handle ${method}`); }}catch (error) {
            const e = this.serializeError(error);
            if (e instanceof ResponseError) {
                throw e;
            }
            const reason = e.message || ' ';
            const stack = e.stack || ' ';
            console.error(`Request ${method} failed with error: ${reason}`, stack);
            throwe; }}Copy the code

conclusion

This paper is mainly based on Theia using VScode-JSON-RPC plug-in remote procedure call mechanism at the front and back end, designed a set of local simple implementation. So we can deeply understand the underlying technical design about proxy factory, client, server, JSON-RPC request type, about the whole routing schedule, and the binding process of listener function.