Add protocol to tunnel API

Part of #124816
This commit is contained in:
Alex Ross 2021-06-17 16:12:06 +02:00
parent f0a0c734f3
commit a414bf2973
No known key found for this signature in database
GPG key ID: 89DDDBA66CBA7840
11 changed files with 54 additions and 35 deletions

View file

@ -382,13 +382,14 @@ async function tunnelFactory(tunnelOptions: vscode.TunnelOptions, tunnelCreation
return createTunnelService();
function newTunnel(localAddress: { host: string, port: number }) {
function newTunnel(localAddress: { host: string, port: number }): vscode.Tunnel {
const onDidDispose: vscode.EventEmitter<void> = new vscode.EventEmitter();
let isDisposed = false;
return {
localAddress,
remoteAddress: tunnelOptions.remoteAddress,
public: !!vscode.workspace.getConfiguration('testresolver').get('supportPublicPorts') && tunnelOptions.public,
protocol: tunnelOptions.protocol,
onDidDispose: onDidDispose.event,
dispose: () => {
if (!isDisposed) {

View file

@ -20,6 +20,7 @@ export interface RemoteTunnel {
readonly tunnelLocalPort?: number;
readonly localAddress: string;
readonly public: boolean;
readonly protocol?: string;
dispose(silent?: boolean): Promise<void>;
}
@ -28,6 +29,12 @@ export interface TunnelOptions {
localAddressPort?: number;
label?: string;
public?: boolean;
protocol?: string;
}
export enum TunnelProtocol {
Http = 'http',
Https = 'https'
}
export interface TunnelCreationOptions {
@ -70,6 +77,8 @@ export interface ITunnel {
public?: boolean;
protocol?: string;
/**
* Implementers of Tunnel should fire onDidDispose when dispose is called.
*/
@ -90,7 +99,7 @@ export interface ITunnelService {
readonly onAddedTunnelProvider: Event<void>;
canTunnel(uri: URI): boolean;
openTunnel(addressProvider: IAddressProvider | undefined, remoteHost: string | undefined, remotePort: number, localPort?: number, elevateIfNeeded?: boolean, isPublic?: boolean): Promise<RemoteTunnel | undefined> | undefined;
openTunnel(addressProvider: IAddressProvider | undefined, remoteHost: string | undefined, remotePort: number, localPort?: number, elevateIfNeeded?: boolean, isPublic?: boolean, protocol?: string): Promise<RemoteTunnel | undefined> | undefined;
closeTunnel(remoteHost: string, remotePort: number): Promise<void>;
setTunnelProvider(provider: ITunnelProvider | undefined, features: TunnelProviderFeatures): IDisposable;
}
@ -207,7 +216,7 @@ export abstract class AbstractTunnelService implements ITunnelService {
this._tunnels.clear();
}
openTunnel(addressProvider: IAddressProvider | undefined, remoteHost: string | undefined, remotePort: number, localPort?: number, elevateIfNeeded: boolean = false, isPublic: boolean = false): Promise<RemoteTunnel | undefined> | undefined {
openTunnel(addressProvider: IAddressProvider | undefined, remoteHost: string | undefined, remotePort: number, localPort?: number, elevateIfNeeded: boolean = false, isPublic: boolean = false, protocol?: string): Promise<RemoteTunnel | undefined> | undefined {
this.logService.trace(`ForwardedPorts: (TunnelService) openTunnel request for ${remoteHost}:${remotePort} on local port ${localPort}.`);
if (!addressProvider) {
return undefined;
@ -217,7 +226,7 @@ export abstract class AbstractTunnelService implements ITunnelService {
remoteHost = 'localhost';
}
const resolvedTunnel = this.retainOrCreateTunnel(addressProvider, remoteHost, remotePort, localPort, elevateIfNeeded, isPublic);
const resolvedTunnel = this.retainOrCreateTunnel(addressProvider, remoteHost, remotePort, localPort, elevateIfNeeded, isPublic, protocol);
if (!resolvedTunnel) {
this.logService.trace(`ForwardedPorts: (TunnelService) Tunnel was not created.`);
return resolvedTunnel;
@ -246,6 +255,7 @@ export abstract class AbstractTunnelService implements ITunnelService {
tunnelLocalPort: tunnel.tunnelLocalPort,
localAddress: tunnel.localAddress,
public: tunnel.public,
protocol: tunnel.protocol,
dispose: async () => {
this.logService.trace(`ForwardedPorts: (TunnelService) dispose request for ${tunnel.tunnelRemoteHost}:${tunnel.tunnelRemotePort} `);
const existingHost = this._tunnels.get(tunnel.tunnelRemoteHost);
@ -333,14 +343,14 @@ export abstract class AbstractTunnelService implements ITunnelService {
return !!extractLocalHostUriMetaDataForPortMapping(uri);
}
protected abstract retainOrCreateTunnel(addressProvider: IAddressProvider, remoteHost: string, remotePort: number, localPort: number | undefined, elevateIfNeeded: boolean, isPublic: boolean): Promise<RemoteTunnel | undefined> | undefined;
protected abstract retainOrCreateTunnel(addressProvider: IAddressProvider, remoteHost: string, remotePort: number, localPort: number | undefined, elevateIfNeeded: boolean, isPublic: boolean, protocol?: string): Promise<RemoteTunnel | undefined> | undefined;
protected createWithProvider(tunnelProvider: ITunnelProvider, remoteHost: string, remotePort: number, localPort: number | undefined, elevateIfNeeded: boolean, isPublic: boolean): Promise<RemoteTunnel | undefined> | undefined {
protected createWithProvider(tunnelProvider: ITunnelProvider, remoteHost: string, remotePort: number, localPort: number | undefined, elevateIfNeeded: boolean, isPublic: boolean, protocol?: string): Promise<RemoteTunnel | undefined> | undefined {
this.logService.trace(`ForwardedPorts: (TunnelService) Creating tunnel with provider ${remoteHost}:${remotePort} on local port ${localPort}.`);
const preferredLocalPort = localPort === undefined ? remotePort : localPort;
const creationInfo = { elevationRequired: elevateIfNeeded ? isPortPrivileged(preferredLocalPort) : false };
const tunnelOptions: TunnelOptions = { remoteAddress: { host: remoteHost, port: remotePort }, localAddressPort: localPort, public: isPublic };
const tunnelOptions: TunnelOptions = { remoteAddress: { host: remoteHost, port: remotePort }, localAddressPort: localPort, public: isPublic, protocol };
const tunnel = tunnelProvider.forwardPort(tunnelOptions, creationInfo);
this.logService.trace('ForwardedPorts: (TunnelService) Tunnel created by provider.');
if (tunnel) {

View file

@ -148,7 +148,7 @@ export class BaseTunnelService extends AbstractTunnelService {
return (this.configurationService.getValue('remote.localPortHost') === 'localhost') ? '127.0.0.1' : '0.0.0.0';
}
protected retainOrCreateTunnel(addressProvider: IAddressProvider, remoteHost: string, remotePort: number, localPort: number | undefined, elevateIfNeeded: boolean, isPublic: boolean): Promise<RemoteTunnel | undefined> | undefined {
protected retainOrCreateTunnel(addressProvider: IAddressProvider, remoteHost: string, remotePort: number, localPort: number | undefined, elevateIfNeeded: boolean, isPublic: boolean, protocol?: string): Promise<RemoteTunnel | undefined> | undefined {
const existing = this.getTunnelFromMap(remoteHost, remotePort);
if (existing) {
++existing.refcount;
@ -156,7 +156,7 @@ export class BaseTunnelService extends AbstractTunnelService {
}
if (this._tunnelProvider) {
return this.createWithProvider(this._tunnelProvider, remoteHost, remotePort, localPort, elevateIfNeeded, isPublic);
return this.createWithProvider(this._tunnelProvider, remoteHost, remotePort, localPort, elevateIfNeeded, isPublic, protocol);
} else {
this.logService.trace(`ForwardedPorts: (TunnelService) Creating tunnel without provider ${remoteHost}:${remotePort} on local port ${localPort}.`);
const options: IConnectionOptions = {

View file

@ -92,6 +92,7 @@ declare module 'vscode' {
localAddressPort?: number;
label?: string;
public?: boolean;
protocol?: string;
}
export interface TunnelDescription {
@ -99,6 +100,8 @@ declare module 'vscode' {
//The complete local address(ex. localhost:1234)
localAddress: { port: number, host: string; } | string;
public?: boolean;
// If protocol is not provided it is assumed to be http, regardless of the localAddress.
protocol?: string;
}
export interface Tunnel extends TunnelDescription {

View file

@ -8,7 +8,7 @@ import { MainThreadTunnelServiceShape, IExtHostContext, MainContext, ExtHostCont
import { TunnelDto } from 'vs/workbench/api/common/extHostTunnelService';
import { extHostNamedCustomer } from 'vs/workbench/api/common/extHostCustomers';
import { CandidatePort, IRemoteExplorerService, makeAddress, PORT_AUTO_FORWARD_SETTING, PORT_AUTO_SOURCE_SETTING, PORT_AUTO_SOURCE_SETTING_OUTPUT, PORT_AUTO_SOURCE_SETTING_PROCESS } from 'vs/workbench/services/remote/common/remoteExplorerService';
import { ITunnelProvider, ITunnelService, TunnelCreationOptions, TunnelProviderFeatures, TunnelOptions, RemoteTunnel, isPortPrivileged, ProvidedPortAttributes, PortAttributesProvider } from 'vs/platform/remote/common/tunnel';
import { ITunnelProvider, ITunnelService, TunnelCreationOptions, TunnelProviderFeatures, TunnelOptions, RemoteTunnel, isPortPrivileged, ProvidedPortAttributes, PortAttributesProvider, TunnelProtocol } from 'vs/platform/remote/common/tunnel';
import { Disposable } from 'vs/base/common/lifecycle';
import type { TunnelDescription } from 'vs/platform/remote/common/remoteAuthorityResolver';
import { INotificationService, Severity } from 'vs/platform/notification/common/notification';
@ -159,6 +159,7 @@ export class MainThreadTunnelService extends Disposable implements MainThreadTun
localAddress: typeof tunnel.localAddress === 'string' ? tunnel.localAddress : makeAddress(tunnel.localAddress.host, tunnel.localAddress.port),
tunnelLocalPort: typeof tunnel.localAddress !== 'string' ? tunnel.localAddress.port : undefined,
public: tunnel.public,
protocol: tunnel.protocol ?? TunnelProtocol.Http,
dispose: async (silent?: boolean) => {
this.logService.trace(`ForwardedPorts: (MainThreadTunnelService) Closing tunnel from tunnel provider: ${tunnel?.remoteAddress.host}:${tunnel?.remoteAddress.port}`);
return this._proxy.$closeTunnel({ host: tunnel.remoteAddress.host, port: tunnel.remoteAddress.port }, silent);

View file

@ -17,11 +17,12 @@ export interface TunnelDto {
remoteAddress: { port: number, host: string };
localAddress: { port: number, host: string } | string;
public: boolean;
protocol: string | undefined;
}
export namespace TunnelDto {
export function fromApiTunnel(tunnel: vscode.Tunnel): TunnelDto {
return { remoteAddress: tunnel.remoteAddress, localAddress: tunnel.localAddress, public: !!tunnel.public };
return { remoteAddress: tunnel.remoteAddress, localAddress: tunnel.localAddress, public: !!tunnel.public, protocol: tunnel.protocol };
}
export function fromServiceTunnel(tunnel: RemoteTunnel): TunnelDto {
return {
@ -30,7 +31,8 @@ export namespace TunnelDto {
port: tunnel.tunnelRemotePort
},
localAddress: tunnel.localAddress,
public: tunnel.public
public: tunnel.public,
protocol: tunnel.protocol
};
}
}

View file

@ -23,7 +23,7 @@ import { IconLabel } from 'vs/base/browser/ui/iconLabel/iconLabel';
import { ActionRunner, IAction } from 'vs/base/common/actions';
import { IMenuService, MenuId, MenuRegistry, ILocalizedString } from 'vs/platform/actions/common/actions';
import { createAndFillInContextMenuActions, createAndFillInActionBarActions, createActionViewItem } from 'vs/platform/actions/browser/menuEntryActionViewItem';
import { IRemoteExplorerService, TunnelModel, makeAddress, TunnelType, ITunnelItem, Tunnel, TUNNEL_VIEW_ID, parseAddress, CandidatePort, TunnelPrivacy, TunnelEditId, mapHasAddressLocalhostOrAllInterfaces, TunnelProtocol, Attributes } from 'vs/workbench/services/remote/common/remoteExplorerService';
import { IRemoteExplorerService, TunnelModel, makeAddress, TunnelType, ITunnelItem, Tunnel, TUNNEL_VIEW_ID, parseAddress, CandidatePort, TunnelPrivacy, TunnelEditId, mapHasAddressLocalhostOrAllInterfaces, Attributes } from 'vs/workbench/services/remote/common/remoteExplorerService';
import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService';
import { INotificationService, Severity } from 'vs/platform/notification/common/notification';
import { InputBox, MessageType } from 'vs/base/browser/ui/inputbox/inputBox';
@ -34,7 +34,7 @@ import { IThemeService, registerThemingParticipant, ThemeIcon } from 'vs/platfor
import { IKeyboardEvent } from 'vs/base/browser/keyboardEvent';
import { ViewPane, IViewPaneOptions } from 'vs/workbench/browser/parts/views/viewPane';
import { URI } from 'vs/base/common/uri';
import { isPortPrivileged, ITunnelService, RemoteTunnel } from 'vs/platform/remote/common/tunnel';
import { isPortPrivileged, ITunnelService, RemoteTunnel, TunnelProtocol } from 'vs/platform/remote/common/tunnel';
import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors';
import { KeybindingsRegistry, KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry';
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
@ -877,7 +877,7 @@ export class TunnelPanel extends ViewPane {
this.tunnelTypeContext.set(item.tunnelType);
this.tunnelCloseableContext.set(!!item.closeable);
this.tunnelPrivacyContext.set(item.privacy);
this.tunnelProtocolContext.set(item.protocol);
this.tunnelProtocolContext.set(item.protocol === TunnelProtocol.Https ? TunnelProtocol.Https : TunnelProtocol.Https);
this.portChangableContextKey.set(!!item.localPort);
} else {
this.tunnelTypeContext.reset();
@ -1397,11 +1397,7 @@ namespace SetTunnelProtocolAction {
const attributes: Partial<Attributes> = {
protocol
};
// Remove tunnel close/forward when protocol is part of the API https://github.com/microsoft/vscode/issues/124816
await remoteExplorerService.close({ host: arg.remoteHost, port: arg.remotePort });
await remoteExplorerService.tunnelModel.configPortsAttributes.addAttributes(arg.remotePort, attributes);
const isPublic = arg.privacy === TunnelPrivacy.Public;
return remoteExplorerService.forward({ host: arg.remoteHost, port: arg.remotePort }, arg.localPort, arg.name, arg.source, isPublic, isPublic);
return remoteExplorerService.tunnelModel.configPortsAttributes.addAttributes(arg.remotePort, attributes);
}
}

View file

@ -3,7 +3,7 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { ITunnelService, TunnelOptions, RemoteTunnel, TunnelCreationOptions, ITunnel } from 'vs/platform/remote/common/tunnel';
import { ITunnelService, TunnelOptions, RemoteTunnel, TunnelCreationOptions, ITunnel, TunnelProtocol } from 'vs/platform/remote/common/tunnel';
import { Disposable } from 'vs/base/common/lifecycle';
import { IWorkbenchContribution } from 'vs/workbench/common/contributions';
import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService';
@ -54,6 +54,7 @@ export class TunnelFactoryContribution extends Disposable implements IWorkbenchC
// To make sure this doesn't happen, resolve the uri immediately.
localAddress: await this.resolveExternalUri(localAddress),
public: !!tunnel.public,
protocol: tunnel.protocol ?? TunnelProtocol.Http,
dispose: async () => { await tunnel.dispose(); }
};
resolve(remoteTunnel);

View file

@ -18,7 +18,7 @@ export class TunnelService extends AbstractTunnelService {
super(logService);
}
protected retainOrCreateTunnel(_addressProvider: IAddressProvider, remoteHost: string, remotePort: number, localPort: number | undefined, elevateIfNeeded: boolean, isPublic: boolean): Promise<RemoteTunnel | undefined> | undefined {
protected retainOrCreateTunnel(_addressProvider: IAddressProvider, remoteHost: string, remotePort: number, localPort: number | undefined, elevateIfNeeded: boolean, isPublic: boolean, protocol?: string): Promise<RemoteTunnel | undefined> | undefined {
const existing = this.getTunnelFromMap(remoteHost, remotePort);
if (existing) {
++existing.refcount;
@ -26,7 +26,7 @@ export class TunnelService extends AbstractTunnelService {
}
if (this._tunnelProvider) {
return this.createWithProvider(this._tunnelProvider, remoteHost, remotePort, localPort, elevateIfNeeded, isPublic);
return this.createWithProvider(this._tunnelProvider, remoteHost, remotePort, localPort, elevateIfNeeded, isPublic, protocol);
}
return undefined;
}

View file

@ -8,7 +8,7 @@ import { Event, Emitter } from 'vs/base/common/event';
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
import { registerSingleton } from 'vs/platform/instantiation/common/extensions';
import { IStorageService, StorageScope, StorageTarget } from 'vs/platform/storage/common/storage';
import { ALL_INTERFACES_ADDRESSES, isAllInterfaces, isLocalhost, ITunnelService, LOCALHOST_ADDRESSES, PortAttributesProvider, ProvidedOnAutoForward, ProvidedPortAttributes, RemoteTunnel } from 'vs/platform/remote/common/tunnel';
import { ALL_INTERFACES_ADDRESSES, isAllInterfaces, isLocalhost, ITunnelService, LOCALHOST_ADDRESSES, PortAttributesProvider, ProvidedOnAutoForward, ProvidedPortAttributes, RemoteTunnel, TunnelProtocol } from 'vs/platform/remote/common/tunnel';
import { Disposable, IDisposable } from 'vs/base/common/lifecycle';
import { IEditableData } from 'vs/workbench/common/views';
import { ConfigurationTarget, IConfigurationService } from 'vs/platform/configuration/common/configuration';
@ -150,11 +150,6 @@ export enum OnPortForward {
Ignore = 'ignore'
}
export enum TunnelProtocol {
Http = 'http',
Https = 'https'
}
export interface Attributes {
label: string | undefined;
onAutoForward: OnPortForward | undefined,
@ -563,9 +558,12 @@ export class TunnelModel extends Disposable {
const key = makeAddress(remote.host, remote.port);
this.inProgress.set(key, true);
const tunnel = await this.tunnelService.openTunnel(addressProvider, remote.host, remote.port, localPort, (!elevateIfNeeded) ? attributes?.elevateIfNeeded : elevateIfNeeded, isPublic);
const tunnel = await this.tunnelService.openTunnel(addressProvider, remote.host, remote.port, localPort, (!elevateIfNeeded) ? attributes?.elevateIfNeeded : elevateIfNeeded, isPublic, attributes?.protocol);
if (tunnel && tunnel.localAddress) {
const matchingCandidate = mapHasAddressLocalhostOrAllInterfaces<CandidatePort>(this._candidates ?? new Map(), remote.host, remote.port);
const protocol = (tunnel.protocol ?
((tunnel.protocol === TunnelProtocol.Https) ? TunnelProtocol.Https : TunnelProtocol.Http)
: (attributes?.protocol ?? TunnelProtocol.Http));
const newForward: Tunnel = {
remoteHost: tunnel.tunnelRemoteHost,
remotePort: tunnel.tunnelRemotePort,
@ -573,7 +571,7 @@ export class TunnelModel extends Disposable {
name: attributes?.label ?? name,
closeable: true,
localAddress: tunnel.localAddress,
protocol: attributes?.protocol ?? TunnelProtocol.Http,
protocol,
localUri: await this.makeLocalUri(tunnel.localAddress, attributes),
runningProcess: matchingCandidate?.detail,
hasRunningProcess: !!matchingCandidate,
@ -591,12 +589,12 @@ export class TunnelModel extends Disposable {
return tunnel;
}
} else {
if (attributes?.label ?? name) {
existingTunnel.name = attributes?.label ?? name;
const newName = attributes?.label ?? name;
if (newName !== existingTunnel.name) {
existingTunnel.name = newName;
this._onForwardPort.fire();
}
// Remove tunnel provider check when protocol is part of the API https://github.com/microsoft/vscode/issues/124816
if (!this.tunnelService.hasTunnelProvider && attributes?.protocol && (attributes.protocol !== existingTunnel.protocol)) {
if (attributes?.protocol && (attributes.protocol !== existingTunnel.protocol)) {
await this.close(existingTunnel.remoteHost, existingTunnel.remotePort);
await this.forward({ host: existingTunnel.remoteHost, port: existingTunnel.remotePort }, local, name, source, elevateIfNeeded, isPublic, restore, attributes);
}

View file

@ -72,6 +72,8 @@ interface ITunnelOptions {
label?: string;
public?: boolean;
protocol?: string;
}
export interface TunnelCreationOptions {
@ -93,6 +95,11 @@ interface ITunnel {
public?: boolean;
/**
* If protocol is not provided, it is assumed to be http, regardless of the localAddress
*/
protocol?: string;
/**
* Implementers of Tunnel should fire onDidDispose when dispose is called.
*/