提交 8e2b3047 编写于 作者: A Alex Ross

Enable more than just localhost for port forwarding providers

Part of #81388
上级 c7ab68cc
......@@ -33,10 +33,10 @@ export interface ITunnelService {
readonly tunnels: Promise<readonly RemoteTunnel[]>;
readonly onTunnelOpened: Event<RemoteTunnel>;
readonly onTunnelClosed: Event<number>;
readonly onTunnelClosed: Event<{ host: string, port: number }>;
openTunnel(remotePort: number, localPort?: number): Promise<RemoteTunnel> | undefined;
closeTunnel(remotePort: number): Promise<void>;
openTunnel(remoteHost: string | undefined, remotePort: number, localPort?: number): Promise<RemoteTunnel> | undefined;
closeTunnel(remoteHost: string, remotePort: number): Promise<void>;
setTunnelProvider(provider: ITunnelProvider | undefined): IDisposable;
}
......
......@@ -13,12 +13,12 @@ export class NoOpTunnelService implements ITunnelService {
public readonly tunnels: Promise<readonly RemoteTunnel[]> = Promise.resolve([]);
private _onTunnelOpened: Emitter<RemoteTunnel> = new Emitter();
public onTunnelOpened: Event<RemoteTunnel> = this._onTunnelOpened.event;
private _onTunnelClosed: Emitter<number> = new Emitter();
public onTunnelClosed: Event<number> = this._onTunnelClosed.event;
openTunnel(_remotePort: number): Promise<RemoteTunnel> | undefined {
private _onTunnelClosed: Emitter<{ host: string, port: number }> = new Emitter();
public onTunnelClosed: Event<{ host: string, port: number }> = this._onTunnelClosed.event;
openTunnel(_remoteHost: string, _remotePort: number): Promise<RemoteTunnel> | undefined {
return undefined;
}
async closeTunnel(_remotePort: number): Promise<void> {
async closeTunnel(_remoteHost: string, _remotePort: number): Promise<void> {
}
setTunnelProvider(provider: ITunnelProvider | undefined): IDisposable {
throw new Error('Method not implemented.');
......
......@@ -22,15 +22,15 @@ export class MainThreadTunnelService implements MainThreadTunnelServiceShape {
}
async $openTunnel(tunnelOptions: TunnelOptions): Promise<TunnelDto | undefined> {
const tunnel = await this.remoteExplorerService.forward(tunnelOptions.remote.port, tunnelOptions.localPort, tunnelOptions.name);
const tunnel = await this.remoteExplorerService.forward(tunnelOptions.remote, tunnelOptions.localPort, tunnelOptions.name);
if (tunnel) {
return TunnelDto.fromServiceTunnel(tunnel);
}
return undefined;
}
async $closeTunnel(remotePort: number): Promise<void> {
return this.remoteExplorerService.close(remotePort);
async $closeTunnel(remote: { host: string, port: number }): Promise<void> {
return this.remoteExplorerService.close(remote);
}
async $registerCandidateFinder(): Promise<void> {
......
......@@ -774,7 +774,7 @@ export interface MainThreadWindowShape extends IDisposable {
export interface MainThreadTunnelServiceShape extends IDisposable {
$openTunnel(tunnelOptions: TunnelOptions): Promise<TunnelDto | undefined>;
$closeTunnel(remotePort: number): Promise<void>;
$closeTunnel(remote: { host: string, port: number }): Promise<void>;
$registerCandidateFinder(): Promise<void>;
$setTunnelProvider(): Promise<void>;
}
......@@ -1395,7 +1395,7 @@ export interface ExtHostStorageShape {
export interface ExtHostTunnelServiceShape {
$findCandidatePorts(): Promise<{ port: number, detail: string }[]>;
$findCandidatePorts(): Promise<{ host: string, port: number, detail: string }[]>;
$forwardPort(tunnelOptions: TunnelOptions): Promise<TunnelDto> | undefined;
$closeTunnel(remote: { host: string, port: number }): Promise<void>;
}
......
......@@ -48,7 +48,7 @@ export class ExtHostTunnelService implements IExtHostTunnelService {
async makeTunnel(forward: TunnelOptions): Promise<vscode.Tunnel | undefined> {
return undefined;
}
async $findCandidatePorts(): Promise<{ port: number; detail: string; }[]> {
async $findCandidatePorts(): Promise<{ host: string, port: number; detail: string; }[]> {
return [];
}
async setForwardPortProvider(provider: vscode.RemoteAuthorityResolver | undefined): Promise<IDisposable> { return { dispose: () => { } }; }
......
......@@ -52,7 +52,7 @@ export class ExtHostTunnelService extends Disposable implements IExtHostTunnelSe
const tunnel = await this._proxy.$openTunnel(forward);
if (tunnel) {
const disposableTunnel: vscode.Tunnel = new ExtensionTunnel(tunnel.remote, tunnel.localAddress, () => {
return this._proxy.$closeTunnel(tunnel.remote.port);
return this._proxy.$closeTunnel(tunnel.remote);
});
this._register(disposableTunnel);
return disposableTunnel;
......@@ -95,7 +95,7 @@ export class ExtHostTunnelService extends Disposable implements IExtHostTunnelSe
this._extensionTunnels.set(tunnelOptions.remote.host, new Map());
}
this._extensionTunnels.get(tunnelOptions.remote.host)!.set(tunnelOptions.remote.port, tunnel);
this._register(tunnel.onDispose(() => this._proxy.$closeTunnel(tunnel.remote.port)));
this._register(tunnel.onDispose(() => this._proxy.$closeTunnel(tunnel.remote)));
return Promise.resolve(TunnelDto.fromApiTunnel(tunnel));
});
}
......@@ -104,12 +104,12 @@ export class ExtHostTunnelService extends Disposable implements IExtHostTunnelSe
}
async $findCandidatePorts(): Promise<{ port: number, detail: string }[]> {
async $findCandidatePorts(): Promise<{ host: string, port: number, detail: string }[]> {
if (!isLinux) {
return [];
}
const ports: { port: number, detail: string }[] = [];
const ports: { host: string, port: number, detail: string }[] = [];
const tcp: string = fs.readFileSync('/proc/net/tcp', 'utf8');
const tcp6: string = fs.readFileSync('/proc/net/tcp6', 'utf8');
const procSockets: string = await (new Promise(resolve => {
......@@ -150,7 +150,7 @@ export class ExtHostTunnelService extends Disposable implements IExtHostTunnelSe
connections.filter((connection => socketMap[connection.socket])).forEach(({ socket, ip, port }) => {
const command = processMap[socketMap[socket].pid].cmd;
if (!command.match('.*\.vscode\-server\-[a-zA-Z]+\/bin.*') && (command.indexOf('out/vs/server/main.js') === -1)) {
ports.push({ port, detail: processMap[socketMap[socket].pid].cmd });
ports.push({ host: ip, port, detail: processMap[socketMap[socket].pid].cmd });
}
});
......
......@@ -26,7 +26,7 @@ import { IconLabel } from 'vs/base/browser/ui/iconLabel/iconLabel';
import { ActionRunner, IAction } from 'vs/base/common/actions';
import { IMenuService, MenuId, IMenu, MenuRegistry, MenuItemAction } from 'vs/platform/actions/common/actions';
import { createAndFillInContextMenuActions, createAndFillInActionBarActions, ContextAwareMenuEntryActionViewItem } from 'vs/platform/actions/browser/menuEntryActionViewItem';
import { IRemoteExplorerService, TunnelModel } from 'vs/workbench/services/remote/common/remoteExplorerService';
import { IRemoteExplorerService, TunnelModel, MakeAddress } from 'vs/workbench/services/remote/common/remoteExplorerService';
import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService';
import { INotificationService } from 'vs/platform/notification/common/notification';
import { InputBox, MessageType } from 'vs/base/browser/ui/inputbox/inputBox';
......@@ -105,13 +105,13 @@ export class TunnelViewModel extends Disposable implements ITunnelViewModel {
get forwarded(): TunnelItem[] {
return Array.from(this.model.forwarded.values()).map(tunnel => {
return new TunnelItem(TunnelType.Forwarded, tunnel.remote, tunnel.localAddress, tunnel.closeable, tunnel.name, tunnel.description);
return new TunnelItem(TunnelType.Forwarded, tunnel.remoteHost, tunnel.remotePort, tunnel.localAddress, tunnel.closeable, tunnel.name, tunnel.description);
});
}
get detected(): TunnelItem[] {
return Array.from(this.model.detected.values()).map(tunnel => {
return new TunnelItem(TunnelType.Detected, tunnel.remote, tunnel.localAddress, false, tunnel.name, tunnel.description);
return new TunnelItem(TunnelType.Detected, tunnel.remoteHost, tunnel.remotePort, tunnel.localAddress, false, tunnel.name, tunnel.description);
});
}
......@@ -119,8 +119,9 @@ export class TunnelViewModel extends Disposable implements ITunnelViewModel {
return this.model.candidates.then(values => {
const candidates: TunnelItem[] = [];
values.forEach(value => {
if (!this.model.forwarded.has(value.port) && !this.model.detected.has(value.port)) {
candidates.push(new TunnelItem(TunnelType.Candidate, value.port, undefined, false, undefined, value.detail));
const key = MakeAddress(value.host, value.port);
if (!this.model.forwarded.has(key) && !this.model.detected.has(key)) {
candidates.push(new TunnelItem(TunnelType.Candidate, value.host, value.port, undefined, false, undefined, value.detail));
}
});
return candidates;
......@@ -185,7 +186,7 @@ class TunnelTreeRenderer extends Disposable implements ITreeRenderer<ITunnelGrou
}
private isTunnelItem(item: ITunnelGroup | ITunnelItem): item is ITunnelItem {
return !!((<ITunnelItem>item).remote);
return !!((<ITunnelItem>item).remotePort);
}
renderElement(element: ITreeNode<ITunnelGroup | ITunnelItem, ITunnelGroup | ITunnelItem>, index: number, templateData: ITunnelTemplateData): void {
......@@ -196,7 +197,7 @@ class TunnelTreeRenderer extends Disposable implements ITreeRenderer<ITunnelGrou
templateData.actionBar.clear();
let editableData: IEditableData | undefined;
if (this.isTunnelItem(node)) {
editableData = this.remoteExplorerService.getEditableData(node.remote);
editableData = this.remoteExplorerService.getEditableData(node.remoteHost, node.remotePort);
if (editableData) {
templateData.iconLabel.element.style.display = 'none';
this.renderInputBox(templateData.container, editableData);
......@@ -204,7 +205,7 @@ class TunnelTreeRenderer extends Disposable implements ITreeRenderer<ITunnelGrou
templateData.iconLabel.element.style.display = 'flex';
this.renderTunnel(node, templateData);
}
} else if ((node.tunnelType === TunnelType.Add) && (editableData = this.remoteExplorerService.getEditableData(undefined))) {
} else if ((node.tunnelType === TunnelType.Add) && (editableData = this.remoteExplorerService.getEditableData(undefined, undefined))) {
templateData.iconLabel.element.style.display = 'none';
this.renderInputBox(templateData.container, editableData);
} else {
......@@ -338,7 +339,8 @@ interface ITunnelGroup {
interface ITunnelItem {
tunnelType: TunnelType;
remote: number;
remoteHost: string;
remotePort: number;
localAddress?: string;
name?: string;
closeable?: boolean;
......@@ -349,7 +351,8 @@ interface ITunnelItem {
class TunnelItem implements ITunnelItem {
constructor(
public tunnelType: TunnelType,
public remote: number,
public remoteHost: string,
public remotePort: number,
public localAddress?: string,
public closeable?: boolean,
public name?: string,
......@@ -359,9 +362,9 @@ class TunnelItem implements ITunnelItem {
if (this.name) {
return nls.localize('remote.tunnelsView.forwardedPortLabel0', "{0}", this.name);
} else if (this.localAddress) {
return nls.localize('remote.tunnelsView.forwardedPortLabel2', "{0} to {1}", this.remote, this.localAddress);
return nls.localize('remote.tunnelsView.forwardedPortLabel2', "{0} to {1}", this.remotePort, this.localAddress);
} else {
return nls.localize('remote.tunnelsView.forwardedPortLabel3', "{0} not forwarded", this.remote);
return nls.localize('remote.tunnelsView.forwardedPortLabel3', "{0} not forwarded", this.remotePort);
}
}
......@@ -369,7 +372,7 @@ class TunnelItem implements ITunnelItem {
if (this._description) {
return this._description;
} else if (this.name) {
return nls.localize('remote.tunnelsView.forwardedPortDescription0', "{0} to {1}", this.remote, this.localAddress);
return nls.localize('remote.tunnelsView.forwardedPortDescription0', "{0} to {1}", this.remotePort, this.localAddress);
}
return undefined;
}
......@@ -474,7 +477,7 @@ export class TunnelPanel extends ViewPane {
}));
this._register(this.remoteExplorerService.onDidChangeEditable(async e => {
const isEditing = !!this.remoteExplorerService.getEditableData(e);
const isEditing = !!this.remoteExplorerService.getEditableData(e.host, e.port);
if (!isEditing) {
dom.removeClass(treeContainer, 'highlight');
......@@ -575,12 +578,12 @@ namespace LabelTunnelAction {
return async (accessor, arg) => {
if (arg instanceof TunnelItem) {
const remoteExplorerService = accessor.get(IRemoteExplorerService);
remoteExplorerService.setEditable(arg.remote, {
remoteExplorerService.setEditable(arg.remoteHost, arg.remotePort, {
onFinish: (value, success) => {
if (success) {
remoteExplorerService.tunnelModel.name(arg.remote, value);
remoteExplorerService.tunnelModel.name(arg.remoteHost, arg.remotePort, value);
}
remoteExplorerService.setEditable(arg.remote, null);
remoteExplorerService.setEditable(arg.remoteHost, arg.remotePort, null);
},
validationMessage: () => null,
placeholder: nls.localize('remote.tunnelsView.labelPlaceholder', "Port label"),
......@@ -596,24 +599,32 @@ namespace ForwardPortAction {
export const ID = 'remote.tunnel.forward';
export const LABEL = nls.localize('remote.tunnel.forward', "Forward a Port");
function parseInput(value: string): { host: string, port: number } | undefined {
const matches = value.match(/^([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+\:|localhost:)?([0-9]+)$/);
if (!matches) {
return undefined;
}
return { host: matches[1]?.substring(0, matches[1].length - 1) || 'localhost', port: Number(matches[2]) };
}
export function handler(): ICommandHandler {
return async (accessor, arg) => {
const remoteExplorerService = accessor.get(IRemoteExplorerService);
if (arg instanceof TunnelItem) {
remoteExplorerService.tunnelModel.forward(arg.remote);
remoteExplorerService.forward({ host: arg.remoteHost, port: arg.remotePort });
} else {
const viewsService = accessor.get(IViewsService);
await viewsService.openView(TunnelPanel.ID, true);
remoteExplorerService.setEditable(undefined, {
remoteExplorerService.setEditable(undefined, undefined, {
onFinish: (value, success) => {
if (success) {
remoteExplorerService.tunnelModel.forward(Number(value));
let parsed: { host: string, port: number } | undefined;
if (success && (parsed = parseInput(value))) {
remoteExplorerService.forward({ host: parsed.host, port: parsed.port });
}
remoteExplorerService.setEditable(undefined, null);
remoteExplorerService.setEditable(undefined, undefined, null);
},
validationMessage: (value) => {
const asNumber = Number(value);
if ((value === '') || isNaN(asNumber) || (asNumber < 0) || (asNumber > 65535)) {
if (!parseInput(value)) {
return nls.localize('remote.tunnelsView.portNumberValid', "Port number is invalid");
}
return null;
......@@ -633,7 +644,7 @@ namespace ClosePortAction {
return async (accessor, arg) => {
if (arg instanceof TunnelItem) {
const remoteExplorerService = accessor.get(IRemoteExplorerService);
await remoteExplorerService.tunnelModel.close(arg.remote);
await remoteExplorerService.close({ host: arg.remoteHost, port: arg.remotePort });
}
};
}
......@@ -648,9 +659,10 @@ namespace OpenPortInBrowserAction {
if (arg instanceof TunnelItem) {
const model = accessor.get(IRemoteExplorerService).tunnelModel;
const openerService = accessor.get(IOpenerService);
const tunnel = model.forwarded.has(arg.remote) ? model.forwarded.get(arg.remote) : model.detected.get(arg.remote);
const key = MakeAddress(arg.remoteHost, arg.remotePort);
const tunnel = model.forwarded.get(key) || model.detected.get(key);
let address: string | undefined;
if (tunnel && tunnel.localAddress && (address = model.address(tunnel.remote))) {
if (tunnel && tunnel.localAddress && (address = model.address(tunnel.remoteHost, tunnel.remotePort))) {
return openerService.open(URI.parse('http://' + address));
}
return Promise.resolve();
......@@ -668,7 +680,7 @@ namespace CopyAddressAction {
if (arg instanceof TunnelItem) {
const model = accessor.get(IRemoteExplorerService).tunnelModel;
const clipboard = accessor.get(IClipboardService);
const address = model.address(arg.remote);
const address = model.address(arg.remoteHost, arg.remotePort);
if (address) {
await clipboard.writeText(address.toString());
}
......
......@@ -68,7 +68,7 @@ export class WebviewPortMappingManager extends Disposable {
if (existing) {
return existing;
}
const tunnel = this.tunnelService.openTunnel(remotePort);
const tunnel = this.tunnelService.openTunnel(undefined, remotePort);
if (tunnel) {
this._tunnels.set(remotePort, tunnel);
}
......
......@@ -453,7 +453,7 @@ export class ElectronWindow extends Disposable {
if (options?.allowTunneling) {
const portMappingRequest = extractLocalHostUriMetaDataForPortMapping(uri);
if (portMappingRequest) {
const tunnel = await this.tunnelService.openTunnel(portMappingRequest.port);
const tunnel = await this.tunnelService.openTunnel(undefined, portMappingRequest.port);
if (tunnel) {
return {
resolved: uri.with({ authority: `127.0.0.1:${tunnel.tunnelLocalPort}` }),
......
......@@ -18,24 +18,32 @@ export const IRemoteExplorerService = createDecorator<IRemoteExplorerService>('r
export const REMOTE_EXPLORER_TYPE_KEY: string = 'remote.explorerType';
export interface Tunnel {
remote: number;
remoteHost: string;
remotePort: number;
localAddress: string;
local?: number;
localPort?: number;
name?: string;
description?: string;
closeable?: boolean;
}
export function MakeAddress(host: string, port: number): string {
if (host = '127.0.0.1') {
host = 'localhost';
}
return host + ':' + port;
}
export class TunnelModel extends Disposable {
readonly forwarded: Map<number, Tunnel>;
readonly detected: Map<number, Tunnel>;
readonly forwarded: Map<string, Tunnel>;
readonly detected: Map<string, Tunnel>;
private _onForwardPort: Emitter<Tunnel> = new Emitter();
public onForwardPort: Event<Tunnel> = this._onForwardPort.event;
private _onClosePort: Emitter<number> = new Emitter();
public onClosePort: Event<number> = this._onClosePort.event;
private _onPortName: Emitter<number> = new Emitter();
public onPortName: Event<number> = this._onPortName.event;
private _candidateFinder: (() => Promise<{ port: number, detail: string }[]>) | undefined;
private _onClosePort: Emitter<{ host: string, port: number }> = new Emitter();
public onClosePort: Event<{ host: string, port: number }> = this._onClosePort.event;
private _onPortName: Emitter<{ host: string, port: number }> = new Emitter();
public onPortName: Event<{ host: string, port: number }> = this._onPortName.event;
private _candidateFinder: (() => Promise<{ host: string, port: number, detail: string }[]>) | undefined;
constructor(
@ITunnelService private readonly tunnelService: ITunnelService
......@@ -45,10 +53,11 @@ export class TunnelModel extends Disposable {
this.tunnelService.tunnels.then(tunnels => {
tunnels.forEach(tunnel => {
if (tunnel.localAddress) {
this.forwarded.set(tunnel.tunnelRemotePort, {
remote: tunnel.tunnelRemotePort,
this.forwarded.set(MakeAddress(tunnel.tunnelRemoteHost, tunnel.tunnelRemotePort), {
remotePort: tunnel.tunnelRemotePort,
remoteHost: tunnel.tunnelRemoteHost,
localAddress: tunnel.localAddress,
local: tunnel.tunnelLocalPort
localPort: tunnel.tunnelLocalPort
});
}
});
......@@ -56,75 +65,83 @@ export class TunnelModel extends Disposable {
this.detected = new Map();
this._register(this.tunnelService.onTunnelOpened(tunnel => {
if (!this.forwarded.has(tunnel.tunnelRemotePort) && tunnel.localAddress) {
this.forwarded.set(tunnel.tunnelRemotePort, {
remote: tunnel.tunnelRemotePort,
const key = MakeAddress(tunnel.tunnelRemoteHost, tunnel.tunnelRemotePort);
if ((!this.forwarded.has(key)) && tunnel.localAddress) {
this.forwarded.set(key, {
remoteHost: tunnel.tunnelRemoteHost,
remotePort: tunnel.tunnelRemotePort,
localAddress: tunnel.localAddress,
local: tunnel.tunnelLocalPort,
localPort: tunnel.tunnelLocalPort,
closeable: true
});
}
this._onForwardPort.fire(this.forwarded.get(tunnel.tunnelRemotePort)!);
this._onForwardPort.fire(this.forwarded.get(key)!);
}));
this._register(this.tunnelService.onTunnelClosed(remotePort => {
if (this.forwarded.has(remotePort)) {
this.forwarded.delete(remotePort);
this._onClosePort.fire(remotePort);
this._register(this.tunnelService.onTunnelClosed(address => {
const key = MakeAddress(address.host, address.port);
if (this.forwarded.has(key)) {
this.forwarded.delete(key);
this._onClosePort.fire(address);
}
}));
}
async forward(remote: number, local?: number, name?: string): Promise<RemoteTunnel | void> {
if (!this.forwarded.has(remote)) {
const tunnel = await this.tunnelService.openTunnel(remote, local);
async forward(remote: { host: string, port: number }, local?: number, name?: string): Promise<RemoteTunnel | void> {
const key = MakeAddress(remote.host, remote.port);
if (!this.forwarded.has(key)) {
const tunnel = await this.tunnelService.openTunnel(remote.host, remote.port, local);
if (tunnel && tunnel.localAddress) {
const newForward: Tunnel = {
remote: tunnel.tunnelRemotePort,
local: tunnel.tunnelLocalPort,
remoteHost: tunnel.tunnelRemoteHost,
remotePort: tunnel.tunnelRemotePort,
localPort: tunnel.tunnelLocalPort,
name: name,
closeable: true,
localAddress: tunnel.localAddress
};
this.forwarded.set(remote, newForward);
this.forwarded.set(key, newForward);
this._onForwardPort.fire(newForward);
return tunnel;
}
}
}
name(remote: number, name: string) {
if (this.forwarded.has(remote)) {
this.forwarded.get(remote)!.name = name;
this._onPortName.fire(remote);
} else if (this.detected.has(remote)) {
this.detected.get(remote)!.name = name;
this._onPortName.fire(remote);
name(host: string, port: number, name: string) {
const key = MakeAddress(host, port);
if (this.forwarded.has(key)) {
this.forwarded.get(key)!.name = name;
this._onPortName.fire({ host, port });
} else if (this.detected.has(key)) {
this.detected.get(key)!.name = name;
this._onPortName.fire({ host, port });
}
}
async close(remote: number): Promise<void> {
return this.tunnelService.closeTunnel(remote);
async close(host: string, port: number): Promise<void> {
return this.tunnelService.closeTunnel(host, port);
}
address(remote: number): string | undefined {
return (this.forwarded.get(remote) || this.detected.get(remote))?.localAddress;
address(host: string, port: number): string | undefined {
const key = MakeAddress(host, port);
return (this.forwarded.get(key) || this.detected.get(key))?.localAddress;
}
addDetected(tunnels: { remote: { port: number, host: string }, localAddress: string }[]): void {
tunnels.forEach(tunnel => {
this.detected.set(tunnel.remote.port, {
remote: tunnel.remote.port,
this.detected.set(MakeAddress(tunnel.remote.host, tunnel.remote.port), {
remoteHost: tunnel.remote.host,
remotePort: tunnel.remote.port,
localAddress: tunnel.localAddress,
closeable: false
});
});
}
registerCandidateFinder(finder: () => Promise<{ port: number, detail: string }[]>): void {
registerCandidateFinder(finder: () => Promise<{ host: string, port: number, detail: string }[]>): void {
this._candidateFinder = finder;
}
get candidates(): Promise<{ port: number, detail: string }[]> {
get candidates(): Promise<{ host: string, port: number, detail: string }[]> {
if (this._candidateFinder) {
return this._candidateFinder();
}
......@@ -138,13 +155,13 @@ export interface IRemoteExplorerService {
targetType: string;
readonly helpInformation: HelpInformation[];
readonly tunnelModel: TunnelModel;
onDidChangeEditable: Event<number | undefined>;
setEditable(remote: number | undefined, data: IEditableData | null): void;
getEditableData(remote: number | undefined): IEditableData | undefined;
forward(remote: number, local?: number, name?: string): Promise<RemoteTunnel | void>;
close(remote: number): Promise<void>;
onDidChangeEditable: Event<{ host: string, port: number | undefined }>;
setEditable(remoteHost: string | undefined, remotePort: number | undefined, data: IEditableData | null): void;
getEditableData(remoteHost: string | undefined, remotePort: number | undefined): IEditableData | undefined;
forward(remote: { host: string, port: number }, localPort?: number, name?: string): Promise<RemoteTunnel | void>;
close(remote: { host: string, port: number }): Promise<void>;
addDetected(tunnels: { remote: { port: number, host: string }, localAddress: string }[] | undefined): void;
registerCandidateFinder(finder: () => Promise<{ port: number, detail: string }[]>): void;
registerCandidateFinder(finder: () => Promise<{ host: string, port: number, detail: string }[]>): void;
}
export interface HelpInformation {
......@@ -189,9 +206,9 @@ class RemoteExplorerService implements IRemoteExplorerService {
public readonly onDidChangeTargetType: Event<string> = this._onDidChangeTargetType.event;
private _helpInformation: HelpInformation[] = [];
private _tunnelModel: TunnelModel;
private _editable: { remote: number | undefined, data: IEditableData } | undefined;
private readonly _onDidChangeEditable: Emitter<number | undefined> = new Emitter();
public readonly onDidChangeEditable: Event<number | undefined> = this._onDidChangeEditable.event;
private _editable: { remoteHost: string, remotePort: number | undefined, data: IEditableData } | undefined;
private readonly _onDidChangeEditable: Emitter<{ host: string, port: number | undefined }> = new Emitter();
public readonly onDidChangeEditable: Event<{ host: string, port: number | undefined }> = this._onDidChangeEditable.event;
constructor(
@IStorageService private readonly storageService: IStorageService,
......@@ -246,12 +263,12 @@ class RemoteExplorerService implements IRemoteExplorerService {
return this._tunnelModel;
}
forward(remote: number, local?: number, name?: string): Promise<RemoteTunnel | void> {
forward(remote: { host: string, port: number }, local?: number, name?: string): Promise<RemoteTunnel | void> {
return this.tunnelModel.forward(remote, local, name);
}
close(remote: number): Promise<void> {
return this.tunnelModel.close(remote);
close(remote: { host: string, port: number }): Promise<void> {
return this.tunnelModel.close(remote.host, remote.port);
}
addDetected(tunnels: { remote: { port: number, host: string }, localAddress: string }[] | undefined): void {
......@@ -260,20 +277,21 @@ class RemoteExplorerService implements IRemoteExplorerService {
}
}
setEditable(remote: number | undefined, data: IEditableData | null): void {
setEditable(remoteHost: string, remotePort: number | undefined, data: IEditableData | null): void {
if (!data) {
this._editable = undefined;
} else {
this._editable = { remote, data };
this._editable = { remoteHost, remotePort, data };
}
this._onDidChangeEditable.fire(remote);
this._onDidChangeEditable.fire({ host: remoteHost, port: remotePort });
}
getEditableData(remote: number | undefined): IEditableData | undefined {
return this._editable && this._editable.remote === remote ? this._editable.data : undefined;
getEditableData(remoteHost: string | undefined, remotePort: number | undefined): IEditableData | undefined {
return (this._editable && (this._editable.remotePort === remotePort) && this._editable.remoteHost === remoteHost) ?
this._editable.data : undefined;
}
registerCandidateFinder(finder: () => Promise<{ port: number, detail: string }[]>): void {
registerCandidateFinder(finder: () => Promise<{ host: string, port: number, detail: string }[]>): void {
this.tunnelModel.registerCandidateFinder(finder);
}
......
......@@ -103,9 +103,9 @@ export class TunnelService implements ITunnelService {
private _onTunnelOpened: Emitter<RemoteTunnel> = new Emitter();
public onTunnelOpened: Event<RemoteTunnel> = this._onTunnelOpened.event;
private _onTunnelClosed: Emitter<number> = new Emitter();
public onTunnelClosed: Event<number> = this._onTunnelClosed.event;
private readonly _tunnels = new Map</* port */ number, { refcount: number, readonly value: Promise<RemoteTunnel> }>();
private _onTunnelClosed: Emitter<{ host: string, port: number }> = new Emitter();
public onTunnelClosed: Event<{ host: string, port: number }> = this._onTunnelClosed.event;
private readonly _tunnels = new Map</*host*/ string, Map</* port */ number, { refcount: number, readonly value: Promise<RemoteTunnel> }>>();
private _tunnelProvider: ITunnelProvider | undefined;
public constructor(
......@@ -130,23 +130,32 @@ export class TunnelService implements ITunnelService {
}
public get tunnels(): Promise<readonly RemoteTunnel[]> {
return Promise.all(Array.from(this._tunnels.values()).map(x => x.value));
const promises: Promise<RemoteTunnel>[] = [];
Array.from(this._tunnels.values()).forEach(portMap => Array.from(portMap.values()).forEach(x => promises.push(x.value)));
return Promise.all(promises);
}
dispose(): void {
for (const { value } of this._tunnels.values()) {
value.then(tunnel => tunnel.dispose());
for (const portMap of this._tunnels.values()) {
for (const { value } of portMap.values()) {
value.then(tunnel => tunnel.dispose());
}
portMap.clear();
}
this._tunnels.clear();
}
openTunnel(remotePort: number, localPort: number): Promise<RemoteTunnel> | undefined {
openTunnel(remoteHost: string | undefined, remotePort: number, localPort: number): Promise<RemoteTunnel> | undefined {
const remoteAuthority = this.environmentService.configuration.remoteAuthority;
if (!remoteAuthority) {
return undefined;
}
const resolvedTunnel = this.retainOrCreateTunnel(remoteAuthority, remotePort, localPort);
if (!remoteHost || (remoteHost === '127.0.0.1')) {
remoteHost = 'localhost';
}
const resolvedTunnel = this.retainOrCreateTunnel(remoteAuthority, remoteHost, remotePort, localPort);
if (!resolvedTunnel) {
return resolvedTunnel;
}
......@@ -165,48 +174,62 @@ export class TunnelService implements ITunnelService {
tunnelLocalPort: tunnel.tunnelLocalPort,
localAddress: tunnel.localAddress,
dispose: () => {
const existing = this._tunnels.get(tunnel.tunnelRemotePort);
if (existing) {
existing.refcount--;
this.tryDisposeTunnel(tunnel.tunnelRemotePort, existing);
const existingHost = this._tunnels.get(tunnel.tunnelRemoteHost);
if (existingHost) {
const existing = existingHost.get(tunnel.tunnelRemotePort);
if (existing) {
existing.refcount--;
this.tryDisposeTunnel(tunnel.tunnelRemoteHost, tunnel.tunnelRemotePort, existing);
}
}
}
};
}
private async tryDisposeTunnel(remotePort: number, tunnel: { refcount: number, readonly value: Promise<RemoteTunnel> }): Promise<void> {
private async tryDisposeTunnel(remoteHost: string, remotePort: number, tunnel: { refcount: number, readonly value: Promise<RemoteTunnel> }): Promise<void> {
if (tunnel.refcount <= 0) {
const disposePromise: Promise<void> = tunnel.value.then(tunnel => {
tunnel.dispose();
this._onTunnelClosed.fire(tunnel.tunnelRemotePort);
this._onTunnelClosed.fire({ host: tunnel.tunnelRemoteHost, port: tunnel.tunnelRemotePort });
});
this._tunnels.delete(remotePort);
if (this._tunnels.has(remoteHost)) {
this._tunnels.get(remoteHost)!.delete(remotePort);
}
return disposePromise;
}
}
async closeTunnel(remotePort: number): Promise<void> {
if (this._tunnels.has(remotePort)) {
const value = this._tunnels.get(remotePort)!;
async closeTunnel(remoteHost: string, remotePort: number): Promise<void> {
const portMap = this._tunnels.get(remoteHost);
if (portMap && portMap.has(remotePort)) {
const value = portMap.get(remotePort)!;
value.refcount = 0;
await this.tryDisposeTunnel(remotePort, value);
await this.tryDisposeTunnel(remoteHost, remotePort, value);
}
}
private addTunnelToMap(remoteHost: string, remotePort: number, tunnel: Promise<RemoteTunnel>) {
if (!this._tunnels.has(remoteHost)) {
this._tunnels.set(remoteHost, new Map());
}
this._tunnels.get(remoteHost)!.set(remotePort, { refcount: 1, value: tunnel });
}
private retainOrCreateTunnel(remoteAuthority: string, remotePort: number, localPort?: number): Promise<RemoteTunnel> | undefined {
const existing = this._tunnels.get(remotePort);
private retainOrCreateTunnel(remoteAuthority: string, remoteHost: string, remotePort: number, localPort?: number): Promise<RemoteTunnel> | undefined {
const portMap = this._tunnels.get(remoteHost);
const existing = portMap ? portMap.get(remotePort) : undefined;
if (existing) {
++existing.refcount;
return existing.value;
}
if (this._tunnelProvider) {
const tunnel = this._tunnelProvider.forwardPort({ remote: { host: 'localhost', port: remotePort } });
const tunnel = this._tunnelProvider.forwardPort({ remote: { host: remoteHost, port: remotePort } });
if (tunnel) {
this._tunnels.set(remotePort, { refcount: 1, value: tunnel });
this.addTunnelToMap(remoteHost, remotePort, tunnel);
}
return tunnel;
} else {
} else if (remoteHost === 'localhost') {
const options: IConnectionOptions = {
commit: product.commit,
socketFactory: nodeSocketFactory,
......@@ -221,9 +244,10 @@ export class TunnelService implements ITunnelService {
};
const tunnel = createRemoteTunnel(options, remotePort, localPort);
this._tunnels.set(remotePort, { refcount: 1, value: tunnel });
this.addTunnelToMap(remoteHost, remotePort, tunnel);
return tunnel;
}
return undefined;
}
}
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册