loopbackProxyServer.ts ×12

Frontier kind: Code frontier

unlabeled · c_79701d4a0efe

722 tests · 7997 LOC · 38 files · introduces 0 tests · 169 LOC · 1 file

Introduces — evidence that enters the hierarchy at this concept

Code
12 ranges169 lines · 1 files
Tests
0 tests

Contains — complete concept membership

All code (extent)
1252 ranges7997 lines · 38 files · Browse complete extent
All tests (intent)
722 testsBrowse complete intent

Neighbourhood graph

The orange circle is the focus. Violet and green circles are every ancestor and descendant, broader and narrower, at any distance; blue squares and pink diamonds are the introduced files and exact introduced tests of every visible concept, not only the focus's. Arrows point from broader to narrower concepts and bridge only concepts omitted from this view. Undirected links show source or test introduction. Concept and file size follows LOC; exact test nodes use test-count units.

Introduced files, introduced tests, and structurally relevant concept specialization

In the embedded map, ordinary wheel input scrolls the page; use the visible controls to zoom and drag to pan. Open the full-screen map for canvas navigation: wheel pans, Ctrl/Command plus wheel zooms, and arrow keys pan when this region is focused. On touch screens, open the full-screen map to pan or pinch. If JavaScript or WebGL is unavailable, use the native relationship evidence on this page.

Graph controls are ready.

Interactive rendering requires JavaScript and WebGL. Use the native relationship evidence on this page while the interactive map is unavailable.

Native relationship evidence

Every exact file and test below is linked only from the concept that introduces it.

Introduced tests

Every collected test enters the hierarchy at exactly one concept.

No tests are introduced at this concept. Its intent tests are introduced by other concepts.

Introduced code

Every collected source range enters the hierarchy at exactly one concept.

1 file ranked by introduced lines: 169 introduced LOC across 12 ranges. Expand a file to inspect source; the > gutter marks introduced lines.

src/vs/platform/agentHost/node/shared/loopbackProxyServer.ts 169 introduced LOC · 12 ranges

Open complete file

1 > /*--------------------------------------------------------------------------------------------- loopbackProxyServer.ts
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 type * as http from 'http';
7 > import { AddressInfo } from 'net';
8 > import { IDisposable } from '../../../../base/common/lifecycle.js';
9 > import { ILogService } from '../../../log/common/log.js';
10 >
11 > // #region Public types
12 >
13 > /**
14 > * Per-request bookkeeping shared by every loopback proxy. `clientGone`
15 > * distinguishes a client-driven disconnect (socket already closed — write
16 > * nothing) from a service-driven `dispose()` (socket still open —
17 > * `res.destroy()` to unblock the client) when the abort signal fires.
18 > */
19 > export interface IProxyInFlight {
20 > readonly ac: AbortController;
21 > readonly res: http.ServerResponse;
22 > clientGone: boolean;
23 > }
24 >
25 > /**
26 > * The shared, refcounted runtime exposed to subclasses while servicing
27 > * requests and minting handles. `state` is the subclass-owned mutable
28 > * payload (e.g. the current GitHub token) created once per bind by
29 > * {@link LoopbackProxyServer.createState} from the seed supplied to the
30 > * `acquire()` call that triggered the bind.
31 > */
32 > export interface ILoopbackProxyRuntime<TState> {
33 > /** e.g. `http://127.0.0.1:54321` — no trailing slash. */
34 > readonly baseUrl: string;
35 > /** 256-bit hex string minted for this bind. */
36 > readonly nonce: string;
37 > /** In-flight requests; aborted on teardown. */
38 > readonly inFlight: Set<IProxyInFlight>;
39 > /** Subclass-owned mutable per-bind state. */
40 > readonly state: TState;
41 > }
42 >
43 > /**
44 > * Minimal handle every loopback proxy hands back from `start()`. Subclasses
45 > * are free to widen this with extra members (e.g. `setToken`,
46 > * `providerBaseUrl`).
47 > *
48 > * **Subprocess ownership invariant.** Callers that hand `baseUrl` / `nonce`
49 > * to a subprocess MUST kill that subprocess before calling `dispose()` —
50 > * after the last handle is disposed the proxy may rebind on a different port
51 > * and the subprocess would silently lose its endpoint.
52 > */
53 > export interface ILoopbackProxyHandle extends IDisposable {
54 > /** e.g. `http://127.0.0.1:54321` — no trailing slash. */
55 > readonly baseUrl: string;
56 > /** 256-bit hex string. */
57 > readonly nonce: string;
58 > }
59 >
60 > // #endregion
61 >
62 > // #region Internal state
63 >
64 > interface IInternalRuntime<TState> extends ILoopbackProxyRuntime<TState> {
65 > readonly server: http.Server;
66 > refcount: number;
67 > }
68 >
69 > /**
70 > * Build the 256-bit hex nonce embedded in the proxy Bearer token. Web Crypto
71 > * is available in Node 18+.
72 > */
73 function generateNonce(): string {
74 const bytes = new Uint8Array(32);
80 return out;
81 }
83 > // #endregion
84 >
85 > /**
86 > * Reads the full body of an inbound request as a UTF-8 string.
87 > */
88 > export function readProxyRequestBody(req: http.IncomingMessage): Promise<string> {
89 return new Promise((resolve, reject) => {
90 const chunks: Buffer[] = [];
94 });
95 }
97 > /**
98 > * Reusable base for the agent-host loopback HTTP proxies. Owns the
99 > * full server lifecycle — lazy bind on `127.0.0.1`, nonce minting,
100 > * refcounted handles, in-flight tracking, and teardown — so each concrete
101 > * proxy only has to implement request routing (`handleRequest`) and the
102 > * shape of its `state` (`createState`).
103 > *
104 > * `TState` is the subclass-owned per-bind mutable state; `TSeed` is the
105 > * value each `acquire()` caller threads into `createState()` so the state
106 > * is born valid (e.g. with a real GitHub token rather than a placeholder).
107 > * It defaults to `void` for proxies whose state needs no seed.
108 > *
109 > * Lifecycle: the first `start()` binds a single shared server; concurrent
110 > * `start()` calls share that bind. Each handle holds a refcount; when the
111 > * last one is disposed (or `dispose()` is called explicitly) the listener
112 > * closes, in-flight requests are aborted, and the next `start()` rebinds
113 > * with a fresh port and nonce.
114 > */
115 > export abstract class LoopbackProxyServer<TState, TSeed = void> {
116 >
117 > private _runtime: IInternalRuntime<TState> | undefined;
118 > private _starting: Promise<IInternalRuntime<TState>> | undefined;
119 > private _disposed = false;
120 >
121 > constructor(
122 /** Human-readable name used in log lines and error messages. */
123 protected readonly name: string,
124 protected readonly _logService: ILogService,
125 ) { }
127 > protected get isDisposed(): boolean {
128 return this._disposed;
129 }
131 > /**
132 > * Build the subclass-owned mutable state object stored on the runtime.
133 > * Called exactly once per bind, before any request can be dispatched,
134 > * with the `seed` from the `acquire()` call that won the bind race so
135 > * the state starts out valid instead of holding a placeholder.
136 > */
137 > protected abstract createState(seed: TSeed): TState;
138 >
139 > /**
140 > * Route + service an authenticated inbound request. Invoked for every
141 > * request; any throw is caught by the base and turned into a 500.
142 > */
143 > protected abstract handleRequest(
144 > req: http.IncomingMessage,
145 > res: http.ServerResponse,
146 > runtime: ILoopbackProxyRuntime<TState>,
147 > ): Promise<void>;
148 >
149 > /**
150 > * Write the fallback "internal proxy error" response used when
151 > * {@link handleRequest} throws before sending headers. Subclasses may
152 > * override to match their wire format; the default emits a generic
153 > * JSON error envelope.
154 > */
155 > protected writeInternalError(res: http.ServerResponse): void {
156 res.writeHead(500, { 'Content-Type': 'application/json' });
157 res.end(JSON.stringify({ error: { type: 'api_error', message: 'Internal proxy error' } }));
158 }
160 > /**
161 > * Acquire a refcounted lease on the shared runtime, binding the server
162 > * if it isn't running yet. Subclasses build their public handle around
163 > * the returned `runtime` and wire its `dispose()` to `release`.
164 > *
165 > * `seed` is forwarded to {@link createState} when this call triggers the
166 > * bind; for callers that join an existing bind it is ignored (the state
167 > * already exists), so they must apply their own value to `runtime.state`
168 > * afterwards if they need last-writer-wins semantics.
169 > *
170 > * Throws if the service has been disposed (including if `dispose()`
171 > * raced the bind).
172 > */
173 > protected async acquire(seed: TSeed): Promise<{ runtime: ILoopbackProxyRuntime<TState>; release: () => void }> {
174 if (this._disposed) {
175 throw new Error(`${this.name} has been disposed`);
195 return { runtime, release };
196 }
198 > dispose(): void {
199 if (this._disposed) {
200 return;
203 this._teardownRuntime();
204 }
206 > /**
207 > * Returns the shared runtime, binding a new server if there isn't one
208 > * yet. Concurrent callers share the same in-flight bind via
209 > * {@link _starting}; this prevents two listeners from being created when
210 > * {@link acquire} is invoked twice before the first bind resolves.
211 > *
212 > * If {@link dispose} runs while the bind is in flight, the just-bound
213 > * server is torn down here and the awaiting caller sees a rejected
214 > * promise.
215 > */
216 > private _ensureRuntime(seed: TSeed): Promise<IInternalRuntime<TState>> {
217 if (this._runtime) {
218 return Promise.resolve(this._runtime);
239 return this._starting;
240 }
242 > private _releaseHandle(runtime: IInternalRuntime<TState>): void {
243 // If `dispose()` (or a later bind) already replaced the runtime, the
244 // handle's refcount no longer applies.
251 }
252 }
254 > private _teardownRuntime(): void {
255 const runtime = this._runtime;
256 if (!runtime) {
271 });
272 }
274 > private async _startServer(seed: TSeed): Promise<IInternalRuntime<TState>> {
275 const nonce = generateNonce();
276 const inFlight = new Set<IProxyInFlight>();