Add (unimplemented) webview server

This commit is contained in:
Asher 2019-07-02 16:15:41 -05:00
parent 3a78c0964f
commit 6a35ab1dc0
No known key found for this signature in database
GPG Key ID: D63C1EF81242354A
2 changed files with 110 additions and 73 deletions

View File

@ -1,4 +1,12 @@
import { Server } from "./server"; import { MainServer, WebviewServer } from "./server";
const server = new Server(); const webviewServer = new WebviewServer();
server.listen(); const server = new MainServer(webviewServer);
webviewServer.listen(8444).then(async () => {
await server.listen(8443);
console.log(`Main server serving ${server.address}`);
console.log(`Webview server serving ${webviewServer.address}`);
}).catch((error) => {
console.error(error);
process.exit(1);
});

169
server.ts
View File

@ -54,30 +54,24 @@ export class HttpError extends Error {
} }
} }
export class Server { export abstract class Server {
// Used to notify the IPC server that there is a new client. // The underlying web server.
public readonly _onDidClientConnect = new Emitter<ClientConnectionEvent>(); protected readonly server: http.Server;
public readonly onDidClientConnect = this._onDidClientConnect.event;
private readonly rootPath = path.resolve(__dirname, "../../..");
// This is separate instead of just extending this class since we can't
// use properties in the super call. This manages channels.
private readonly ipc = new IPCServer(this.onDidClientConnect);
// The web server.
private readonly server: http.Server;
private readonly environmentService: EnvironmentService;
private readonly logService: ILogService;
// Persistent connections. These can reconnect within a timeout.
private readonly connections = new Map<ConnectionType, Map<string, Connection>>();
public constructor() { public constructor() {
this.server = http.createServer(async (request, response): Promise<void> => { this.server = http.createServer(async (request, response): Promise<void> => {
try { try {
const [content, headers] = await this.handleRequest(request); if (request.method !== "GET") {
throw new HttpError(
`Unsupported method ${request.method}`,
HttpCode.BadRequest,
);
}
const parsedUrl = url.parse(request.url || "", true);
const requestPath = parsedUrl.pathname || "/";
const [content, headers] = await this.handleRequest(request, parsedUrl, requestPath);
response.writeHead(HttpCode.Ok, { response.writeHead(HttpCode.Ok, {
"Cache-Control": "max-age=86400", "Cache-Control": "max-age=86400",
// TODO: ETag? // TODO: ETag?
@ -89,6 +83,48 @@ export class Server {
response.end(error.message); response.end(error.message);
} }
}); });
}
protected abstract handleRequest(
request: http.IncomingMessage,
parsedUrl: url.UrlWithParsedQuery,
requestPath: string,
): Promise<[string | Buffer, http.OutgoingHttpHeaders]>;
public listen(port: number): Promise<void> {
return new Promise((resolve, reject) => {
this.server.on("error", reject);
this.server.listen(port, resolve);
});
}
public get address(): string {
const address = this.server.address();
const endpoint = typeof address !== "string"
? ((address.address === "::" ? "localhost" : address.address) + ":" + address.port)
: address;
return `http://${endpoint}`;
}
}
export class MainServer extends Server {
// Used to notify the IPC server that there is a new client.
public readonly _onDidClientConnect = new Emitter<ClientConnectionEvent>();
public readonly onDidClientConnect = this._onDidClientConnect.event;
private readonly rootPath = path.resolve(__dirname, "../../..");
// This is separate instead of just extending this class since we can't
// use properties in the super call. This manages channels.
private readonly ipc = new IPCServer(this.onDidClientConnect);
// Persistent connections. These can reconnect within a timeout.
private readonly connections = new Map<ConnectionType, Map<string, Connection>>();
private readonly services = new ServiceCollection();
public constructor(private readonly webviewServer: WebviewServer) {
super();
this.server.on("upgrade", async (request, socket) => { this.server.on("upgrade", async (request, socket) => {
const protocol = this.createProtocol(request, socket); const protocol = this.createProtocol(request, socket);
@ -98,56 +134,44 @@ export class Server {
protocol.dispose(error); protocol.dispose(error);
} }
}); });
this.server.on("error", (error) => {
console.error(error);
process.exit(1);
});
let args: ParsedArgs;
try {
args = parseMainProcessArgv(process.argv);
args = validatePaths(args);
} catch (error) {
console.error(error.message);
return process.exit(1);
} }
const services = new ServiceCollection(); public async listen(port: number): Promise<void> {
this.environmentService = new EnvironmentService(args, process.execPath); const args = validatePaths(parseMainProcessArgv(process.argv));
services.set(IEnvironmentService, this.environmentService);
this.logService = new SpdLogService( const environmentService = new EnvironmentService(args, process.execPath);
this.services.set(IEnvironmentService, environmentService);
const logService = new SpdLogService(
RemoteExtensionLogFileName, RemoteExtensionLogFileName,
this.environmentService.logsPath, environmentService.logsPath,
getLogLevel(this.environmentService), getLogLevel(environmentService),
); );
this.ipc.registerChannel("loglevel", new LogLevelSetterChannel(this.logService)); this.services.set(ILogService, logService);
const instantiationService = new InstantiationService(services); this.ipc.registerChannel("loglevel", new LogLevelSetterChannel(logService));
const instantiationService = new InstantiationService(this.services);
instantiationService.invokeFunction(() => { instantiationService.invokeFunction(() => {
instantiationService.createInstance(LogsDataCleaner); instantiationService.createInstance(LogsDataCleaner);
this.ipc.registerChannel( this.ipc.registerChannel(
REMOTE_FILE_SYSTEM_CHANNEL_NAME, REMOTE_FILE_SYSTEM_CHANNEL_NAME,
new FileProviderChannel(this.logService), new FileProviderChannel(logService),
); );
this.ipc.registerChannel( this.ipc.registerChannel(
"remoteextensionsenvironment", "remoteextensionsenvironment",
new ExtensionEnvironmentChannel(this.environmentService, this.logService), new ExtensionEnvironmentChannel(environmentService, logService),
); );
}); });
await super.listen(port);
} }
private async handleRequest(request: http.IncomingMessage): Promise<[string | Buffer, http.OutgoingHttpHeaders]> { protected async handleRequest(
if (request.method !== "GET") { request: http.IncomingMessage,
throw new HttpError( parsedUrl: url.UrlWithParsedQuery,
`Unsupported method ${request.method}`, requestPath: string,
HttpCode.BadRequest, ): Promise<[string | Buffer, http.OutgoingHttpHeaders]> {
);
}
const parsedUrl = url.parse(request.url || "", true);
const requestPath = parsedUrl.pathname || "/";
if (requestPath === "/") { if (requestPath === "/") {
const htmlPath = path.join( const htmlPath = path.join(
this.rootPath, this.rootPath,
@ -159,7 +183,7 @@ export class Server {
const remoteAuthority = request.headers.host as string; const remoteAuthority = request.headers.host as string;
const transformer = getUriTransformer(remoteAuthority); const transformer = getUriTransformer(remoteAuthority);
const webviewEndpoint = ""; const webviewEndpoint = this.webviewServer.address;
const cwd = process.env.VSCODE_CWD || process.cwd(); const cwd = process.env.VSCODE_CWD || process.cwd();
const workspacePath = parsedUrl.query.workspace as string | undefined; const workspacePath = parsedUrl.query.workspace as string | undefined;
@ -167,13 +191,21 @@ export class Server {
const options: Options = { const options: Options = {
WORKBENCH_WEB_CONGIGURATION: { WORKBENCH_WEB_CONGIGURATION: {
workspaceUri: workspacePath ? transformer.transformOutgoing(URI.file(sanitizeFilePath(workspacePath, cwd))) : undefined, workspaceUri: workspacePath
folderUri: folderPath ? transformer.transformOutgoing(URI.file(sanitizeFilePath(folderPath, cwd))) : undefined, ? transformer.transformOutgoing(URI.file(sanitizeFilePath(workspacePath, cwd)))
: undefined,
folderUri: folderPath
? transformer.transformOutgoing(URI.file(sanitizeFilePath(folderPath, cwd)))
: undefined,
remoteAuthority, remoteAuthority,
webviewEndpoint, webviewEndpoint,
}, },
REMOTE_USER_DATA_URI: transformer.transformOutgoing(this.environmentService.webUserDataHome), REMOTE_USER_DATA_URI: transformer.transformOutgoing(
PRODUCT_CONFIGURATION: require.__$__nodeRequire(path.resolve(getPathFromAmdModule(require, ""), "../product.json")), (this.services.get(IEnvironmentService) as EnvironmentService).webUserDataHome,
),
PRODUCT_CONFIGURATION: require.__$__nodeRequire(
path.resolve(getPathFromAmdModule(require, ""), "../product.json"),
),
CONNECTION_AUTH_TOKEN: "", CONNECTION_AUTH_TOKEN: "",
}; };
@ -239,17 +271,6 @@ export class Server {
); );
} }
public listen(port: number = 8443): void {
this.server.listen(port, () => {
const address = this.server.address();
const location = typeof address === "string"
? address
: `port ${address.port}`;
console.log(`Listening on ${location}`);
console.log(`Serving ${this.rootPath}`);
});
}
private async connect(message: ConnectionTypeRequest, protocol: Protocol): Promise<void> { private async connect(message: ConnectionTypeRequest, protocol: Protocol): Promise<void> {
switch (message.desiredConnectionType) { switch (message.desiredConnectionType) {
case ConnectionType.ExtensionHost: case ConnectionType.ExtensionHost:
@ -290,7 +311,9 @@ export class Server {
onDidClientDisconnect: connection.onClose, onDidClientDisconnect: connection.onClose,
}); });
} else { } else {
connection = new ExtensionHostConnection(protocol, this.logService); connection = new ExtensionHostConnection(
protocol, this.services.get(ILogService) as ILogService,
);
} }
connections.set(protocol.options.reconnectionToken, connection); connections.set(protocol.options.reconnectionToken, connection);
connection.onClose(() => { connection.onClose(() => {
@ -309,3 +332,9 @@ export class Server {
return undefined; return undefined;
} }
} }
export class WebviewServer extends Server {
protected async handleRequest(): Promise<[string | Buffer, http.OutgoingHttpHeaders]> {
throw new Error("not implemented");
}
}