需求分析
Visual Studio Code Remote Development是一个允许用户使用容器、远程计算机或Windows Linux子系统来搭建开发环境的重要功能拓展。实际开发工作中,一个项目可能需要在特定的环境下进行调试与运行,或者需要共享开发环境以提升团队合作的效率。
该扩展设定用户在使用时,会有以下的基本需求:
`远程连接`:用户通过VSCode远程连接插件,与远程服务器建立连接。
`建立远程工作区`:用户在本地的VSCode界面中打开远程工作区,可以编辑、调试和运行远程服务器上的代码。
`文件传输`:用户具有从本机上传文件、从远程OS下载文件的能力,可以在远程主机中编辑、保存文件。
`终端访问`:用户可以使用VSCode 的`集成终端`功能访问命令行终端
`远程调试`:用户能够在远程服务器上进行代码运行和调试。
用例分析
需求用例通常有以下三种类型::
`抽象用例`(Abstract use case)。用简单的动名词短语精简地指明一个用例,在本扩展程序中是打开、编辑文件、调试代码、上传与下载文件等动作。
`高层用例`(High level use case)。划定一个边界,指明用例在何时何地开始,在何时何地结束,又叫起始边界(TUCBW) 和结束边界 (TUCEW)。
例如,编剧文件的高层用例为:
TUCBW:远程网络检测,检测是否成功连接到远程OS.
TUCEW:编辑效果检测,检测用户期望的编辑是否被落实到远程OS中。
`扩展用例`(Expanded use case)。将业务任务的交互过程详细地描述出来,包含所有的交互步骤。
例如,编剧文件的拓展用例为:
远程网络检测,检测是否成功连接到远程OS.
用户进行编辑,VSCode为用户提供编辑器的相关功能。
编辑效果检测,检测用户期望的编辑是否被落实到远程OS中。
逆向工程
VSCode基于Electron实现,主要使用的语言是TypeScript,下面对Remote Development的重要源码进行分析。
workbench存放了VSCode的界面参数,其数据结构如下,包含了远程链接中的权限、令牌,网络连接、资源定位,文件系统、工作区服务等参数。
interface IWorkbenchConstructionOptions {
/**
* The remote authority is the IP:PORT from where the workbench is served
* from. It is for example being used for the websocket connections as address.
*/
readonly remoteAuthority?: string;
/**
* The connection token to send to the server.
*/
readonly connectionToken?: string;
/**
* An endpoint to serve iframe content ("webview") from. This is required
* to provide full security isolation from the workbench host.
*/
readonly webviewEndpoint?: string;
/**
* A handler for opening workspaces and providing the initial workspace.
*/
readonly workspaceProvider?: IWorkspaceProvider;
/**
* The user data provider is used to handle user specific application
* state like settings, keybindings, UI state (e.g. opened editors) and snippets.
*/
userDataProvider?: IFileSystemProvider;
/**
* A factory for web sockets.
*/
readonly webSocketFactory?: IWebSocketFactory;
/**
* A provider for resource URIs.
*/
readonly resourceUriProvider?: IResourceUriProvider;
/**
* The credentials provider to store and retrieve secrets.
*/
readonly credentialsProvider?: ICredentialsProvider;
/**
* Add static extensions that cannot be uninstalled but only be disabled.
*/
readonly staticExtensions?: ReadonlyArray<IStaticExtension>;
/**
* Support for URL callbacks.
*/
readonly urlCallbackProvider?: IURLCallbackProvider;
/**
* Support for update reporting.
*/
readonly updateProvider?: IUpdateProvider;
/**
* Support adding additional properties to telemetry.
*/
readonly resolveCommonTelemetryProperties?: ICommontTelemetryPropertiesResolver;
/**
* Resolves an external uri before it is opened.
*/
readonly resolveExternalUri?: IExternalUriResolver;
/**
* Current logging level. Default is `LogLevel.Info`.
*/
readonly logLevel?: LogLevel;
/**
* Whether to enable the smoke test driver.
*/
readonly driver?: boolean;
}
远程开发需要开启三种服务:远程权限管理`remoteAuthorityResolverService`;远程代理服务`remoteAgentService`;文件系统服务`fileSystemProvider`
// Remote
const remoteAuthorityResolverService = new RemoteAuthorityResolverService(this.configuration.resourceUriProvider);
serviceCollection.set(IRemoteAuthorityResolverService, remoteAuthorityResolverService);
// Remote Agent
const remoteAgentService = this._register(new RemoteAgentService(this.configuration.webSocketFactory, environmentService, productService, remoteAuthorityResolverService, signService, logService));
serviceCollection.set(IRemoteAgentService, remoteAgentService);
// Files
const fileService = this._register(new FileService(logService));
serviceCollection.set(IFileService, fileService);
this.registerFileSystemProviders(environmentService, fileService, remoteAgentService, logService, logsPath);
设置全局变量 RemoteAuthorities,以方便进行资源定位符的解析。
resolveAuthority(authority: string): Promise<ResolverResult> {
if (authority.indexOf(':') >= 0) {
const pieces = authority.split(':');
return Promise.resolve(this._createResolvedAuthority(authority, pieces[0], parseInt(pieces[1], 10)));
} else {
return Promise.resolve(this._createResolvedAuthority(authority, authority, 80));
}
}
private _createResolvedAuthority(authority: string, host: string, port: number): ResolverResult {
RemoteAuthorities.set(authority, host, port);
return { authority: { authority, host, port } };
}
连接进程remoteExtensionHost,在获取足够权限的前提下,操作远程OS上运行的插件,其本质是进程的通信。
const options: IConnectionOptions = {
commit: this._productService.commit,
socketFactory: this._socketFactory,
addressProvider: {
getAddress: async () => {
const { authority } = await this.remoteAuthorityResolverService.resolveAuthority(this._initDataProvider.remoteAuthority);
return { host: authority.host, port: authority.port };
}
},
signService: this._signService,
logService: this._logService
};
采用IPC进程通信可以对远程OS上的文件系统进行获取状态、打开、关闭等操作。
stat(resource: URI): Promise<IStat> {
return this.channel.call('stat', [resource]);
}
open(resource: URI, opts: FileOpenOptions): Promise<number> {
return this.channel.call('open', [resource, opts]);
}
close(fd: number): Promise<void> {
return this.channel.call('close', [fd]);
}