Skip to content

Commit 68fe085

Browse files
committed
Add channels
1 parent 4861405 commit 68fe085

6 files changed

Lines changed: 261 additions & 36 deletions

File tree

channel.ts

Lines changed: 137 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,137 @@
1+
import * as path from "path";
2+
3+
import { Emitter, Event } from "vs/base/common/event";
4+
import { OS } from "vs/base/common/platform";
5+
import { URI } from "vs/base/common/uri";
6+
import { IServerChannel } from "vs/base/parts/ipc/common/ipc";
7+
import { IDiagnosticInfo } from "vs/platform/diagnostics/common/diagnosticsService";
8+
import { IEnvironmentService } from "vs/platform/environment/common/environment";
9+
import { FileDeleteOptions, FileOverwriteOptions, FileType, IStat, IWatchOptions, FileOpenOptions } from "vs/platform/files/common/files";
10+
import { IRemoteAgentEnvironment } from "vs/platform/remote/common/remoteAgentEnvironment";
11+
12+
/**
13+
* See: src/vs/platform/remote/common/remoteAgentFileSystemChannel.ts.
14+
*/
15+
export class FileProviderChannel implements IServerChannel {
16+
public listen(_context: any, event: string): Event<any> {
17+
switch (event) {
18+
case "filechange":
19+
// TODO: not sure what to do here yet
20+
return new Emitter().event;
21+
}
22+
23+
throw new Error(`Invalid listen "${event}"`);
24+
}
25+
26+
public call(_: unknown, command: string, args?: any): Promise<any> {
27+
console.log("got call", command, args);
28+
switch (command) {
29+
case "stat": return this.stat(args[0]);
30+
case "open": return this.open(args[0], args[1]);
31+
case "close": return this.close(args[0]);
32+
case "read": return this.read(args[0], args[1], args[2], args[3], args[4]);
33+
case "write": return this.write(args[0], args[1], args[2], args[3], args[4]);
34+
case "delete": return this.delete(args[0], args[1]);
35+
case "mkdir": return this.mkdir(args[0]);
36+
case "readdir": return this.readdir(args[0]);
37+
case "rename": return this.rename(args[0], args[1], args[2]);
38+
case "copy": return this.copy(args[0], args[1], args[2]);
39+
case "watch": return this.watch(args[0], args[1]);
40+
case "unwatch": return this.unwatch(args[0]), args[1];
41+
}
42+
43+
throw new Error(`Invalid call "${command}"`);
44+
}
45+
46+
private async stat(resource: URI): Promise<IStat> {
47+
throw new Error("not implemented");
48+
}
49+
50+
private async open(resource: URI, opts: FileOpenOptions): Promise<number> {
51+
throw new Error("not implemented");
52+
}
53+
54+
private async close(fd: number): Promise<void> {
55+
throw new Error("not implemented");
56+
}
57+
58+
private async read(fd: number, pos: number, data: Uint8Array, offset: number, length: number): Promise<number> {
59+
throw new Error("not implemented");
60+
}
61+
62+
private async write(fd: number, pos: number, data: Uint8Array, offset: number, length: number): Promise<number> {
63+
throw new Error("not implemented");
64+
}
65+
66+
private async delete(resource: URI, opts: FileDeleteOptions): Promise<void> {
67+
throw new Error("not implemented");
68+
}
69+
70+
private async mkdir(resource: URI): Promise<void> {
71+
throw new Error("not implemented");
72+
}
73+
74+
private async readdir(resource: URI): Promise<[string, FileType][]> {
75+
throw new Error("not implemented");
76+
}
77+
78+
private async rename(resource: URI, target: URI, opts: FileOverwriteOptions): Promise<void> {
79+
throw new Error("not implemented");
80+
}
81+
82+
private copy(resource: URI, target: URI, opts: FileOverwriteOptions): Promise<void> {
83+
throw new Error("not implemented");
84+
}
85+
86+
private watch(resource: URI, opts: IWatchOptions): Promise<void> {
87+
throw new Error("not implemented");
88+
}
89+
90+
private unwatch(resource: URI): void {
91+
throw new Error("not implemented");
92+
}
93+
}
94+
95+
/**
96+
* See: src/vs/workbench/services/remote/common/remoteAgentEnvironmentChannel.ts.
97+
*/
98+
export class ExtensionEnvironmentChannel implements IServerChannel {
99+
public constructor(private readonly environment: IEnvironmentService) {}
100+
101+
public listen(_context: any, event: string): Event<any> {
102+
throw new Error(`Invalid listen "${event}"`);
103+
}
104+
105+
public call(_: unknown, command: string, args?: any): Promise<any> {
106+
switch (command) {
107+
case "getEnvironmentData": return this.getEnvironmentData();
108+
case "getDiagnosticInfo": return this.getDiagnosticInfo();
109+
case "disableTelemetry": return this.disableTelemetry();
110+
}
111+
throw new Error(`Invalid call "${command}"`);
112+
}
113+
114+
private async getEnvironmentData(): Promise<IRemoteAgentEnvironment> {
115+
return {
116+
pid: process.pid,
117+
appRoot: URI.file(this.environment.appRoot),
118+
appSettingsHome: this.environment.appSettingsHome,
119+
settingsPath: this.environment.machineSettingsHome,
120+
logsPath: URI.file(this.environment.logsPath),
121+
extensionsPath: URI.file(this.environment.extensionsPath),
122+
extensionHostLogsPath: URI.file(path.join(this.environment.logsPath, "extension-host")), // TODO
123+
globalStorageHome: URI.file(this.environment.globalStorageHome),
124+
userHome: URI.file(this.environment.userHome),
125+
extensions: [], // TODO
126+
os: OS,
127+
};
128+
}
129+
130+
private getDiagnosticInfo(): Promise<IDiagnosticInfo> {
131+
throw new Error("not implemented");
132+
}
133+
134+
private disableTelemetry(): Promise<void> {
135+
throw new Error("not implemented");
136+
}
137+
}

connection.ts

Lines changed: 47 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,68 @@
1+
import { ClientConnectionEvent } from "vs/base/parts/ipc/common/ipc";
2+
import { ConnectionType } from "vs/platform/remote/common/remoteAgentConnection";
13
import { Emitter } from "vs/base/common/event";
24
import { PersistentProtocol, ISocket } from "vs/base/parts/ipc/common/ipc.net";
35
import { VSBuffer } from "vs/base/common/buffer";
46

7+
export interface Server {
8+
readonly _onDidClientConnect: Emitter<ClientConnectionEvent>;
9+
readonly connections: Map<ConnectionType, Map<string, Connection>>;
10+
}
11+
512
export abstract class Connection {
6-
protected readonly _onClose = new Emitter<void>();
13+
private readonly _onClose = new Emitter<void>();
714
public readonly onClose = this._onClose.event;
815

9-
public constructor(private readonly protocol: PersistentProtocol) {
16+
private timeout: NodeJS.Timeout | undefined;
17+
private readonly wait = 1000 * 60 * 60;
18+
19+
public constructor(
20+
protected readonly server: Server,
21+
private readonly protocol: PersistentProtocol,
22+
) {
23+
// onClose seems to mean we want to disconnect, so dispose immediately.
24+
this.protocol.onClose(() => this.dispose());
25+
26+
// If the socket closes, we want to wait before disposing so we can
27+
// reconnect.
1028
this.protocol.onSocketClose(() => {
11-
// TODO: eventually we'll want to clean up the connection if nothing
12-
// ever connects back to it
29+
this.timeout = setTimeout(() => {
30+
this.dispose();
31+
}, this.wait);
1332
});
1433
}
1534

35+
/**
36+
* Completely close and clean up the connection. Should only do this once we
37+
* don't need or want the connection. It cannot be re-used after this.
38+
*/
39+
public dispose(): void {
40+
this.protocol.sendDisconnect();
41+
this.protocol.getSocket().end();
42+
this.protocol.dispose();
43+
this._onClose.fire();
44+
}
45+
1646
public reconnect(socket: ISocket, buffer: VSBuffer): void {
47+
clearTimeout(this.timeout as any); // Not sure why the type doesn't work.
1748
this.protocol.beginAcceptReconnection(socket, buffer);
1849
this.protocol.endAcceptReconnection();
1950
}
2051
}
2152

53+
/**
54+
* The management connection is used for all the IPC channels.
55+
*/
2256
export class ManagementConnection extends Connection {
23-
// in here they accept the connection
24-
// to the ipc of the RemoteServer
57+
public constructor(server: Server, protocol: PersistentProtocol) {
58+
super(server, protocol);
59+
// This will communicate back to the IPCServer that a new client has
60+
// connected.
61+
this.server._onDidClientConnect.fire({
62+
protocol,
63+
onDidClientDisconnect: this.onClose,
64+
});
65+
}
2566
}
2667

2768
export class ExtensionHostConnection extends Connection {

entry.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
import { Server } from "./server";
2+
3+
const server = new Server();
4+
server.listen();

main.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
require("../../bootstrap-amd").load("vs/server/server");
1+
require("../../bootstrap-amd").load("vs/server/entry");

server.ts

Lines changed: 65 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -5,19 +5,29 @@ import * as path from "path";
55
import * as util from "util";
66
import * as url from "url";
77

8-
import { Connection } from "vs/server/connection";
9-
import { ConnectionType } from "vs/platform/remote/common/remoteAgentConnection";
108
import { Emitter } from "vs/base/common/event";
11-
import { ClientConnectionEvent } from "vs/base/parts/ipc/common/ipc";
12-
import { Socket, Server as IServer } from "vs/server/socket";
9+
import { IPCServer, ClientConnectionEvent } from "vs/base/parts/ipc/common/ipc";
10+
import { validatePaths } from "vs/code/node/paths";
11+
import { parseMainProcessArgv } from "vs/platform/environment/node/argvHelper";
12+
import { ParsedArgs } from "vs/platform/environment/common/environment";
13+
import { EnvironmentService } from "vs/platform/environment/node/environmentService";
14+
import { InstantiationService } from "vs/platform/instantiation/common/instantiationService";
15+
import { ConsoleLogMainService } from "vs/platform/log/common/log";
16+
import { LogLevelSetterChannel } from "vs/platform/log/common/logIpc";
17+
import { ConnectionType } from "vs/platform/remote/common/remoteAgentConnection";
18+
import { REMOTE_FILE_SYSTEM_CHANNEL_NAME } from "vs/platform/remote/common/remoteAgentFileSystemChannel";
19+
20+
import { Connection, Server as IServer } from "vs/server/connection";
21+
import { ExtensionEnvironmentChannel, FileProviderChannel } from "vs/server/channel";
22+
import { Socket } from "vs/server/socket";
1323

14-
enum HttpCode {
24+
export enum HttpCode {
1525
Ok = 200,
1626
NotFound = 404,
1727
BadRequest = 400,
1828
}
1929

20-
class HttpError extends Error {
30+
export class HttpError extends Error {
2131
public constructor(message: string, public readonly code: number) {
2232
super(message);
2333
// @ts-ignore
@@ -26,14 +36,24 @@ class HttpError extends Error {
2636
}
2737
}
2838

29-
class Server implements IServer {
30-
private readonly _onDidClientConnect = new Emitter<ClientConnectionEvent>();
39+
export class Server implements IServer {
40+
// When a new client connects, it will fire this event which is used in the
41+
// IPC server which manages channels.
42+
public readonly _onDidClientConnect = new Emitter<ClientConnectionEvent>();
3143
public readonly onDidClientConnect = this._onDidClientConnect.event;
3244

3345
private readonly rootPath = path.resolve(__dirname, "../../..");
3446

47+
// This is separate instead of just extending this class since we can't
48+
// use properties in the super call. This manages channels.
49+
private readonly ipc = new IPCServer(this.onDidClientConnect);
50+
51+
// The web server.
3552
private readonly server: http.Server;
3653

54+
// Persistent connections. These can reconnect within a timeout. Individual
55+
// sockets will add connections made through them to this map and remove them
56+
// when they close.
3757
public readonly connections = new Map<ConnectionType, Map<string, Connection>>();
3858

3959
public constructor() {
@@ -52,17 +72,45 @@ class Server implements IServer {
5272
});
5373

5474
this.server.on("upgrade", (request, socket) => {
55-
this.handleUpgrade(request, socket);
75+
try {
76+
const nodeSocket = this.handleUpgrade(request, socket);
77+
nodeSocket.handshake(this);
78+
} catch (error) {
79+
socket.end(error.message);
80+
}
5681
});
5782

5883
this.server.on("error", (error) => {
5984
console.error(error);
6085
process.exit(1);
6186
});
62-
}
6387

64-
public dispose(): void {
65-
this.connections.clear();
88+
let args: ParsedArgs;
89+
try {
90+
args = parseMainProcessArgv(process.argv);
91+
args = validatePaths(args);
92+
} catch (error) {
93+
console.error(error.message);
94+
return process.exit(1);
95+
}
96+
97+
const environmentService = new EnvironmentService(args, process.execPath);
98+
99+
// TODO: might want to use spdlog.
100+
const logService = new ConsoleLogMainService();
101+
this.ipc.registerChannel("loglevel", new LogLevelSetterChannel(logService));
102+
103+
const instantiationService = new InstantiationService();
104+
instantiationService.invokeFunction(() => {
105+
this.ipc.registerChannel(
106+
REMOTE_FILE_SYSTEM_CHANNEL_NAME,
107+
new FileProviderChannel(),
108+
);
109+
this.ipc.registerChannel(
110+
"remoteextensionsenvironment",
111+
new ExtensionEnvironmentChannel(environmentService),
112+
);
113+
});
66114
}
67115

68116
private async handleRequest(request: http.IncomingMessage): Promise<string | Buffer> {
@@ -118,9 +166,9 @@ class Server implements IServer {
118166
}
119167
}
120168

121-
private handleUpgrade(request: http.IncomingMessage, socket: net.Socket): void {
169+
private handleUpgrade(request: http.IncomingMessage, socket: net.Socket): Socket {
122170
if (request.headers.upgrade !== "websocket") {
123-
return socket.end("HTTP/1.1 400 Bad Request");
171+
throw new Error("HTTP/1.1 400 Bad Request");
124172
}
125173

126174
const options = {
@@ -144,11 +192,11 @@ class Server implements IServer {
144192

145193
const nodeSocket = new Socket(socket, options);
146194
nodeSocket.upgrade(request.headers["sec-websocket-key"] as string);
147-
nodeSocket.handshake(this);
195+
196+
return nodeSocket;
148197
}
149198

150-
public listen(): void {
151-
const port = 8443;
199+
public listen(port: number = 8443): void {
152200
this.server.listen(port, () => {
153201
const address = this.server.address();
154202
const location = typeof address === "string"
@@ -159,6 +207,3 @@ class Server implements IServer {
159207
});
160208
}
161209
}
162-
163-
const server = new Server();
164-
server.listen();

0 commit comments

Comments
 (0)