Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
microsoft
GitHub Repository: microsoft/vscode
Path: blob/main/extensions/copilot/src/platform/networking/vscode-node/fetcherServiceImpl.ts
13400 views
1
/*---------------------------------------------------------------------------------------------
2
* Copyright (c) Microsoft Corporation. All rights reserved.
3
* Licensed under the MIT License. See License.txt in the project root for license information.
4
*--------------------------------------------------------------------------------------------*/
5
6
import { Emitter } from '../../../util/vs/base/common/event';
7
import { Disposable } from '../../../util/vs/base/common/lifecycle';
8
import { Config, ConfigKey, ExperimentBasedConfig, ExperimentBasedConfigType, IConfigurationService } from '../../configuration/common/configurationService';
9
import { INTEGRATION_ID } from '../../endpoint/common/licenseAgreement';
10
import { IEnvService } from '../../env/common/envService';
11
import { ILogService } from '../../log/common/logService';
12
import { IExperimentationService } from '../../telemetry/common/nullExperimentationService';
13
import { ITelemetryService } from '../../telemetry/common/telemetry';
14
import { FetchEvent, FetchOptions, FetchTelemetryEvent, IAbortController, IFetcherService, NO_FETCH_TELEMETRY, PaginationOptions, ReportFetchEvent, Response, safeGetHostname, WebSocketConnection, WebSocketConnectOptions } from '../common/fetcherService';
15
import { IFetcher } from '../common/networking';
16
import { fetchWithFallbacks } from '../node/fetcherFallback';
17
import { NodeFetcher } from '../node/nodeFetcher';
18
import { createWebSocket, NodeFetchFetcher } from '../node/nodeFetchFetcher';
19
import { ElectronFetcher } from './electronFetcher';
20
21
export class FetcherService extends Disposable implements IFetcherService {
22
23
declare readonly _serviceBrand: undefined;
24
private _availableFetchers: readonly IFetcher[] | undefined;
25
private _knownBadFetchers = new Set<string>();
26
private _experimentationService: IExperimentationService | undefined;
27
private _telemetryService: ITelemetryService | undefined;
28
private readonly _onDidFetch = this._register(new Emitter<FetchEvent>());
29
readonly onDidFetch = this._onDidFetch.event;
30
private readonly _onDidCompleteFetch = this._register(new Emitter<FetchTelemetryEvent>());
31
readonly onDidCompleteFetch = this._onDidCompleteFetch.event;
32
33
constructor(
34
fetcher: IFetcher | undefined,
35
@ILogService private readonly _logService: ILogService,
36
@IEnvService private readonly _envService: IEnvService,
37
@IConfigurationService private readonly _configurationService: IConfigurationService,
38
) {
39
super();
40
this._availableFetchers = fetcher ? [fetcher] : undefined;
41
}
42
43
async fetchWithPagination<T>(baseUrl: string, options: PaginationOptions<T>): Promise<T[]> {
44
const items: T[] = [];
45
const pageSize = options.pageSize ?? 20;
46
let page = options.startPage ?? 1;
47
let hasNextPage = false;
48
49
do {
50
const url = options.buildUrl(baseUrl, pageSize, page);
51
const response = await this.fetch(url, options);
52
53
if (!response.ok) {
54
// Return what we've collected so far if request fails
55
return items;
56
}
57
58
const data = await response.json();
59
const pageItems = options.getItemsFromResponse(data);
60
items.push(...pageItems);
61
62
hasNextPage = pageItems.length === pageSize;
63
page++;
64
} while (hasNextPage);
65
66
return items;
67
}
68
69
setExperimentationService(experimentationService: IExperimentationService) {
70
this._experimentationService = experimentationService;
71
}
72
73
setTelemetryService(telemetryService: ITelemetryService) {
74
this._telemetryService = telemetryService;
75
}
76
77
private _getAvailableFetchers(): readonly IFetcher[] {
78
if (!this._availableFetchers) {
79
if (!this._experimentationService) {
80
this._logService.info('FetcherService: Experimentation service not available yet, using default fetcher configuration.');
81
} else {
82
this._logService.debug('FetcherService: Using experimentation service to determine fetcher configuration.');
83
}
84
this._availableFetchers = this._getFetchers(this._configurationService, this._experimentationService, this._envService);
85
}
86
return this._availableFetchers;
87
}
88
89
private _getFetchers(configurationService: IConfigurationService, experimentationService: IExperimentationService | undefined, envService: IEnvService): IFetcher[] {
90
const reportEvent: ReportFetchEvent = e => this._onDidFetch.fire(e);
91
const useElectronFetcher = getShadowedConfig<boolean>(configurationService, experimentationService, ConfigKey.Shared.DebugUseElectronFetcher, ConfigKey.TeamInternal.DebugExpUseElectronFetcher);
92
const electronFetcher = ElectronFetcher.create(envService, reportEvent);
93
const useNodeFetcher = !(useElectronFetcher && electronFetcher) && getShadowedConfig<boolean>(configurationService, experimentationService, ConfigKey.Shared.DebugUseNodeFetcher, ConfigKey.TeamInternal.DebugExpUseNodeFetcher); // Node https wins over Node fetch. (historical order)
94
const useNodeFetchFetcher = !(useElectronFetcher && electronFetcher) && !useNodeFetcher && getShadowedConfig<boolean>(configurationService, experimentationService, ConfigKey.Shared.DebugUseNodeFetchFetcher, ConfigKey.TeamInternal.DebugExpUseNodeFetchFetcher);
95
96
const fetchers = [];
97
if (electronFetcher) {
98
fetchers.push(electronFetcher);
99
}
100
if (useElectronFetcher) {
101
if (electronFetcher) {
102
this._logService.info(`Using the Electron fetcher.`);
103
} else {
104
this._logService.info(`Can't use the Electron fetcher in this environment.`);
105
}
106
}
107
108
// Node fetch preferred over Node https in fallbacks. (HTTP2 support)
109
const nodeFetchFetcher = new NodeFetchFetcher(envService, reportEvent);
110
if (useNodeFetchFetcher) {
111
this._logService.info(`Using the Node fetch fetcher.`);
112
fetchers.unshift(nodeFetchFetcher);
113
} else {
114
fetchers.push(nodeFetchFetcher);
115
}
116
117
const nodeFetcher = new NodeFetcher(envService, reportEvent);
118
if (useNodeFetcher || (!(useElectronFetcher && electronFetcher) && !useNodeFetchFetcher)) { // Node https used when none is configured. (historical)
119
this._logService.info(`Using the Node fetcher.`);
120
fetchers.unshift(nodeFetcher);
121
} else {
122
fetchers.push(nodeFetcher);
123
}
124
125
return fetchers;
126
}
127
128
getUserAgentLibrary(): string {
129
return this._getAvailableFetchers()[0].getUserAgentLibrary();
130
}
131
132
createWebSocket(url: string, options?: WebSocketConnectOptions): WebSocketConnection {
133
if (options?.headers) {
134
delete options.headers['Request-Hmac'];
135
options.headers['Copilot-Integration-Id'] = INTEGRATION_ID;
136
}
137
return createWebSocket(url, options);
138
}
139
140
async fetch(url: string, options: FetchOptions): Promise<Response> {
141
const start = Date.now();
142
try {
143
const { response: res, updatedFetchers, updatedKnownBadFetchers } = await fetchWithFallbacks(this._getAvailableFetchers(), url, options, this._knownBadFetchers, this._configurationService, this._logService, this._telemetryService, this._experimentationService);
144
if (updatedFetchers) {
145
this._availableFetchers = updatedFetchers;
146
}
147
if (updatedKnownBadFetchers) {
148
this._knownBadFetchers = updatedKnownBadFetchers;
149
}
150
if (options.callSite !== NO_FETCH_TELEMETRY) {
151
this._onDidCompleteFetch.fire({
152
callSite: options.callSite,
153
hostname: safeGetHostname(url),
154
latencyMs: Date.now() - start,
155
statusCode: res.status,
156
success: res.ok,
157
});
158
}
159
return res;
160
} catch (err) {
161
// Apply fetcher demotion if fetchWithFallbacks detected a network process crash
162
const demotion = (err as any)?._fetcherDemotion;
163
if (demotion) {
164
if (demotion.updatedFetchers) {
165
this._availableFetchers = demotion.updatedFetchers;
166
}
167
if (demotion.updatedKnownBadFetchers) {
168
this._knownBadFetchers = demotion.updatedKnownBadFetchers;
169
}
170
}
171
if (options.callSite !== NO_FETCH_TELEMETRY) {
172
this._onDidCompleteFetch.fire({
173
callSite: options.callSite,
174
hostname: safeGetHostname(url),
175
latencyMs: Date.now() - start,
176
statusCode: undefined,
177
success: false,
178
});
179
}
180
throw err;
181
}
182
}
183
184
disconnectAll(): Promise<unknown> {
185
return this._getAvailableFetchers()[0].disconnectAll();
186
}
187
makeAbortController(): IAbortController {
188
return this._getAvailableFetchers()[0].makeAbortController();
189
}
190
isAbortError(e: any): boolean {
191
return this._getAvailableFetchers()[0].isAbortError(e);
192
}
193
isInternetDisconnectedError(e: any): boolean {
194
return this._getAvailableFetchers()[0].isInternetDisconnectedError(e);
195
}
196
isFetcherError(e: any): boolean {
197
return !!e?.fetcherId || this._getAvailableFetchers().some(f => f.isFetcherError(e));
198
}
199
isNetworkProcessCrashedError(e: any): boolean {
200
return this._getAvailableFetchers().some(f => f.isNetworkProcessCrashedError(e));
201
}
202
getUserMessageForFetcherError(err: any): string {
203
// Use the fetcher that recognizes the error, falling back to the primary
204
const recognizing = this._getAvailableFetchers().find(f => f.isFetcherError(err));
205
return (recognizing ?? this._getAvailableFetchers()[0]).getUserMessageForFetcherError(err);
206
}
207
}
208
209
export function getShadowedConfig<T extends ExperimentBasedConfigType>(configurationService: IConfigurationService, experimentationService: IExperimentationService | undefined, configKey: Config<T>, expKey: ExperimentBasedConfig<T | undefined>): T {
210
if (!experimentationService) {
211
return configurationService.getConfig<T>(configKey);
212
}
213
214
const inspect = configurationService.inspectConfig<T>(configKey);
215
if (inspect?.globalValue !== undefined) {
216
return inspect.globalValue;
217
}
218
const expValue = configurationService.getExperimentBasedConfig(expKey, experimentationService);
219
if (expValue !== undefined) {
220
return expValue;
221
}
222
return configurationService.getConfig<T>(configKey);
223
}
224
225