From d40191a5a430a4362d6c19610d800eb7f7a95959 Mon Sep 17 00:00:00 2001 From: Dirk Baeumer Date: Mon, 2 Oct 2017 11:46:23 +0200 Subject: [PATCH] Fixes #245: Is socket support works correct? --- client/package.json | 4 +- client/src/main.ts | 79 ++++++++++++++++++++++++++++-------- jsonrpc/package.json | 2 +- jsonrpc/src/main.ts | 1 + jsonrpc/src/socketSupport.ts | 45 ++++++++++++++++++++ protocol/package.json | 4 +- protocol/src/main.ts | 4 +- server/package.json | 4 +- server/src/main.ts | 20 ++++----- 9 files changed, 126 insertions(+), 37 deletions(-) create mode 100644 jsonrpc/src/socketSupport.ts diff --git a/client/package.json b/client/package.json index 7a1464c89..0f8ccf1c2 100644 --- a/client/package.json +++ b/client/package.json @@ -1,7 +1,7 @@ { "name": "vscode-languageclient", "description": "VSCode Language client implementation", - "version": "3.4.5", + "version": "3.5.0-next.1", "author": "Microsoft Corporation", "license": "MIT", "engines": { @@ -20,7 +20,7 @@ "vscode": "^1.1.5" }, "dependencies": { - "vscode-languageserver-protocol": "^3.4.4" + "vscode-languageserver-protocol": "^3.5.0-next.1" }, "scripts": { "prepublish": "npm run update-vscode && npm run compile && npm test", diff --git a/client/src/main.ts b/client/src/main.ts index 1019bf052..be9151dc4 100644 --- a/client/src/main.ts +++ b/client/src/main.ts @@ -16,10 +16,10 @@ import { import { StreamMessageReader, StreamMessageWriter, IPCMessageReader, IPCMessageWriter, - createClientPipeTransport, generateRandomPipeName + createClientPipeTransport, generateRandomPipeName, createClientSocketTransport } from 'vscode-languageserver-protocol'; -import * as is from './utils/is'; +import * as Is from './utils/is'; import * as electron from './utils/electron'; import { terminate } from './utils/processes'; @@ -50,12 +50,27 @@ export interface ForkOptions { export enum TransportKind { stdio, ipc, - pipe + pipe, + socket } +export interface SocketTransport { + kind: TransportKind.socket; + port: number; +} + +namespace Transport { + export function isSocket(value: Transport): value is SocketTransport { + let candidate = value as SocketTransport; + return candidate && candidate.kind === TransportKind.socket && Is.number(candidate.port); + } +} + +export type Transport = TransportKind | SocketTransport; + export interface NodeModule { module: string; - transport?: TransportKind; + transport?: Transport; args?: string[]; runtime?: string; options?: ForkOptions; @@ -82,7 +97,7 @@ export class LanguageClient extends BaseLanguageClient { let serverOptions: ServerOptions; let clientOptions: LanguageClientOptions; let forceDebug: boolean; - if (is.string(arg2)) { + if (Is.string(arg2)) { id = arg1; name = arg2; serverOptions = arg3 as ServerOptions; @@ -157,7 +172,7 @@ export class LanguageClient extends BaseLanguageClient { let server = this._serverOptions; // We got a function. - if (is.func(server)) { + if (Is.func(server)) { return server().then((result) => { if (MessageTransports.is(result)) { return result; @@ -167,7 +182,7 @@ export class LanguageClient extends BaseLanguageClient { return { reader: new StreamMessageReader(info.reader), writer: new StreamMessageWriter(info.writer) }; } else { let cp = result as ChildProcess; - cp.stderr.on('data', data => this.outputChannel.append(is.string(data) ? data : data.toString(encoding))); + cp.stderr.on('data', data => this.outputChannel.append(Is.string(data) ? data : data.toString(encoding))); return { reader: new StreamMessageReader(cp.stdout), writer: new StreamMessageWriter(cp.stdin) }; } }); @@ -210,6 +225,8 @@ export class LanguageClient extends BaseLanguageClient { } else if (transport === TransportKind.pipe) { pipeName = generateRandomPipeName(); args.push(`--pipe=${pipeName}`); + } else if (Transport.isSocket(transport)) { + args.push(`--socket=${transport.port}`); } args.push(`--clientProcessId=${process.pid.toString()}`); if (transport === TransportKind.ipc || transport === TransportKind.stdio) { @@ -218,9 +235,9 @@ export class LanguageClient extends BaseLanguageClient { return Promise.reject(`Launching server using runtime ${node.runtime} failed.`); } this._serverProcess = serverProcess; - serverProcess.stderr.on('data', data => this.outputChannel.append(is.string(data) ? data : data.toString(encoding))); + serverProcess.stderr.on('data', data => this.outputChannel.append(Is.string(data) ? data : data.toString(encoding))); if (transport === TransportKind.ipc) { - serverProcess.stdout.on('data', data => this.outputChannel.append(is.string(data) ? data : data.toString(encoding))); + serverProcess.stdout.on('data', data => this.outputChannel.append(Is.string(data) ? data : data.toString(encoding))); return Promise.resolve({ reader: new IPCMessageReader(serverProcess), writer: new IPCMessageWriter(serverProcess) }); } else { return Promise.resolve({ reader: new StreamMessageReader(serverProcess.stdout), writer: new StreamMessageWriter(serverProcess.stdin) }); @@ -232,12 +249,25 @@ export class LanguageClient extends BaseLanguageClient { return Promise.reject(`Launching server using runtime ${node.runtime} failed.`); } this._serverProcess = process; - process.stderr.on('data', data => this.outputChannel.append(is.string(data) ? data : data.toString(encoding))); - process.stdout.on('data', data => this.outputChannel.append(is.string(data) ? data : data.toString(encoding))); + process.stderr.on('data', data => this.outputChannel.append(Is.string(data) ? data : data.toString(encoding))); + process.stdout.on('data', data => this.outputChannel.append(Is.string(data) ? data : data.toString(encoding))); return transport.onConnected().then((protocol) => { return { reader: protocol[0], writer: protocol[1] }; }); }) + } else if (Transport.isSocket(transport)) { + return createClientSocketTransport(transport.port).then((transport) => { + let process = cp.spawn(node.runtime!, args, execOptions); + if (!process || !process.pid) { + return Promise.reject(`Launching server using runtime ${node.runtime} failed.`); + } + this._serverProcess = process; + process.stderr.on('data', data => this.outputChannel.append(Is.string(data) ? data : data.toString(encoding))); + process.stdout.on('data', data => this.outputChannel.append(Is.string(data) ? data : data.toString(encoding))); + return transport.onConnected().then((protocol) => { + return { reader: protocol[0], writer: protocol[1] }; + }); + }); } } else { let pipeName: string | undefined = undefined; @@ -250,6 +280,8 @@ export class LanguageClient extends BaseLanguageClient { } else if (transport === TransportKind.pipe) { pipeName = generateRandomPipeName(); args.push(`--pipe=${pipeName}`); + } else if (Transport.isSocket(transport)) { + args.push(`--socket=${transport.port}`); } args.push(`--clientProcessId=${process.pid.toString()}`); let options: ForkOptions = node.options || Object.create(null); @@ -261,9 +293,9 @@ export class LanguageClient extends BaseLanguageClient { reject(error); } else { this._serverProcess = serverProcess; - serverProcess.stderr.on('data', data => this.outputChannel.append(is.string(data) ? data : data.toString(encoding))); + serverProcess.stderr.on('data', data => this.outputChannel.append(Is.string(data) ? data : data.toString(encoding))); if (transport === TransportKind.ipc) { - serverProcess.stdout.on('data', data => this.outputChannel.append(is.string(data) ? data : data.toString(encoding))); + serverProcess.stdout.on('data', data => this.outputChannel.append(Is.string(data) ? data : data.toString(encoding))); resolve({ reader: new IPCMessageReader(this._serverProcess), writer: new IPCMessageWriter(this._serverProcess) }); } else { resolve({ reader: new StreamMessageReader(serverProcess.stdout), writer: new StreamMessageWriter(serverProcess.stdin) }); @@ -277,8 +309,23 @@ export class LanguageClient extends BaseLanguageClient { reject(error); } else { this._serverProcess = cp; - cp.stderr.on('data', data => this.outputChannel.append(is.string(data) ? data : data.toString(encoding))); - cp.stdout.on('data', data => this.outputChannel.append(is.string(data) ? data : data.toString(encoding))); + cp.stderr.on('data', data => this.outputChannel.append(Is.string(data) ? data : data.toString(encoding))); + cp.stdout.on('data', data => this.outputChannel.append(Is.string(data) ? data : data.toString(encoding))); + transport.onConnected().then((protocol) => { + resolve({ reader: protocol[0], writer: protocol[1]}); + }); + } + }); + }); + } else if (Transport.isSocket(transport)) { + createClientSocketTransport(transport.port).then((transport) => { + electron.fork(node.module, args || [], options, (error, cp) => { + if (error || !cp) { + reject(error); + } else { + this._serverProcess = cp; + cp.stderr.on('data', data => this.outputChannel.append(Is.string(data) ? data : data.toString(encoding))); + cp.stdout.on('data', data => this.outputChannel.append(Is.string(data) ? data : data.toString(encoding))); transport.onConnected().then((protocol) => { resolve({ reader: protocol[0], writer: protocol[1]}); }); @@ -297,7 +344,7 @@ export class LanguageClient extends BaseLanguageClient { if (!serverProcess || !serverProcess.pid) { return Promise.reject(`Launching server using command ${command.command} failed.`); } - serverProcess.stderr.on('data', data => this.outputChannel.append(is.string(data) ? data : data.toString(encoding))); + serverProcess.stderr.on('data', data => this.outputChannel.append(Is.string(data) ? data : data.toString(encoding))); this._serverProcess = serverProcess; return Promise.resolve({ reader: new StreamMessageReader(serverProcess.stdout), writer: new StreamMessageWriter(serverProcess.stdin) }); } diff --git a/jsonrpc/package.json b/jsonrpc/package.json index aa442a426..c032f9264 100644 --- a/jsonrpc/package.json +++ b/jsonrpc/package.json @@ -1,7 +1,7 @@ { "name": "vscode-jsonrpc", "description": "A json rpc implementation over streams", - "version": "3.4.1", + "version": "3.5.0-next.1", "author": "Microsoft Corporation", "license": "MIT", "repository": { diff --git a/jsonrpc/src/main.ts b/jsonrpc/src/main.ts index bc0c83a1c..8fb170c30 100644 --- a/jsonrpc/src/main.ts +++ b/jsonrpc/src/main.ts @@ -38,6 +38,7 @@ export { Disposable, Event, Emitter } export * from './pipeSupport'; +export * from './socketSupport'; interface CancelParams { /** diff --git a/jsonrpc/src/socketSupport.ts b/jsonrpc/src/socketSupport.ts new file mode 100644 index 000000000..68551942b --- /dev/null +++ b/jsonrpc/src/socketSupport.ts @@ -0,0 +1,45 @@ +/* -------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + * ------------------------------------------------------------------------------------------ */ +'use strict'; + +import { Server, Socket, createServer, createConnection } from 'net'; + +import { MessageReader, SocketMessageReader } from './messageReader'; +import { MessageWriter, SocketMessageWriter } from './messageWriter'; + +export interface SocketTransport { + onConnected(): Thenable<[MessageReader, MessageWriter]>; +} + +export function createClientSocketTransport(port: number, encoding: string = 'utf-8'): Thenable { + let connectResolve: any; + let connected = new Promise<[MessageReader, MessageWriter]>((resolve, _reject) => { + connectResolve = resolve; + }); + return new Promise((resolve, reject) => { + let server: Server = createServer((socket: Socket) => { + server.close(); + connectResolve([ + new SocketMessageReader(socket, encoding), + new SocketMessageWriter(socket, encoding) + ]); + }); + server.on('error', reject); + server.listen(port, '127.0.0.1', () => { + server.removeListener('error', reject); + resolve({ + onConnected: () => { return connected; } + }); + }); + }); +} + +export function createServerSocketTransport(port: number, encoding: string = 'utf-8'): [MessageReader, MessageWriter] { + const socket: Socket = createConnection(port, '127.0.0.1'); + return [ + new SocketMessageReader(socket, encoding), + new SocketMessageWriter(socket, encoding) + ]; +} \ No newline at end of file diff --git a/protocol/package.json b/protocol/package.json index b492469f5..86598361b 100644 --- a/protocol/package.json +++ b/protocol/package.json @@ -1,7 +1,7 @@ { "name": "vscode-languageserver-protocol", "description": "VSCode Language Server Protocol implementation", - "version": "3.4.4", + "version": "3.5.0-next.1", "author": "Microsoft Corporation", "license": "MIT", "repository": { @@ -14,7 +14,7 @@ "main": "./lib/main.js", "typings": "./lib/main", "dependencies": { - "vscode-jsonrpc": "^3.4.1", + "vscode-jsonrpc": "^3.5.0-next.1", "vscode-languageserver-types": "^3.4.0" }, "scripts": { diff --git a/protocol/src/main.ts b/protocol/src/main.ts index 5fc2e8596..e8bbee3d8 100644 --- a/protocol/src/main.ts +++ b/protocol/src/main.ts @@ -13,6 +13,7 @@ import { MessageReader, MessageWriter, Logger, ConnectionStrategy, StreamMessageReader, StreamMessageWriter, IPCMessageReader, IPCMessageWriter, createClientPipeTransport, createServerPipeTransport, generateRandomPipeName, DataCallback, + createClientSocketTransport, createServerSocketTransport, createMessageConnection, Tracer } from 'vscode-jsonrpc'; @@ -26,6 +27,7 @@ export { StreamMessageReader, StreamMessageWriter, IPCMessageReader, IPCMessageWriter, createClientPipeTransport, createServerPipeTransport, generateRandomPipeName, DataCallback, + createClientSocketTransport, createServerSocketTransport, Tracer } export * from 'vscode-languageserver-types'; @@ -61,7 +63,7 @@ export namespace Proposed { export type MiddlewareSignature = folders.DidChangeWorkspaceFoldersNotification.MiddlewareSignature; } - + export type ColorProviderOptions = color.ColorProviderOptions; export type DocumentColorParams = color.DocumentColorParams; export type ColorPresentationParams = color.ColorPresentationParams; diff --git a/server/package.json b/server/package.json index b8ff2a332..c07a9c725 100644 --- a/server/package.json +++ b/server/package.json @@ -1,7 +1,7 @@ { "name": "vscode-languageserver", "description": "Language server implementation for node", - "version": "3.4.3", + "version": "3.5.0-next.1", "author": "Microsoft Corporation", "license": "MIT", "repository": { @@ -18,7 +18,7 @@ "typings": "./lib/main", "dependencies": { "vscode-uri": "^1.0.1", - "vscode-languageserver-protocol": "^3.4.1" + "vscode-languageserver-protocol": "^3.5.0-next.1" }, "scripts": { "prepublish": "npm run compile", diff --git a/server/src/main.ts b/server/src/main.ts index 0426ca6b9..14ef5290d 100644 --- a/server/src/main.ts +++ b/server/src/main.ts @@ -14,7 +14,7 @@ import { NotificationType, NotificationType0, NotificationHandler, NotificationHandler0, GenericNotificationHandler, StarNotificationHandler, RPCMessageType, ResponseError, Logger, MessageReader, IPCMessageReader, - MessageWriter, IPCMessageWriter, createServerPipeTransport, + MessageWriter, IPCMessageWriter, createServerPipeTransport, createServerSocketTransport, CancellationToken, CancellationTokenSource, Disposable, Event, Emitter, Trace, SetTraceNotification, LogTraceNotification, ConnectionStrategy, @@ -54,8 +54,6 @@ export * from 'vscode-languageserver-protocol'; export { Event } import * as fm from './files'; -import * as net from 'net'; -import * as stream from 'stream'; export namespace Files { export let uriToFilePath = fm.uriToFilePath; @@ -1478,17 +1476,13 @@ function _createConnection { - server.close(); - socket.pipe(output as stream.PassThrough); - (input as stream.PassThrough).pipe(socket); - }).listen(port); + let transport = createServerSocketTransport(port); + input = transport[0]; + output = transport[1]; } else if (pipeName) { - let protocol = createServerPipeTransport(pipeName); - input = protocol[0]; - output = protocol[1]; + let transport = createServerPipeTransport(pipeName); + input = transport[0]; + output = transport[1]; } } var commandLineMessage = "Use arguments of createConnection or set command line parameters: '--node-ipc', '--stdio' or '--socket={number}'";