Skip to content

Commit 67675e1

Browse files
authored
Merge pull request #178 from kernel/hypeship/direct-vm-fs-logs
Route browser fs and logs endpoints directly to the VM
2 parents 94794c7 + c4d564c commit 67675e1

2 files changed

Lines changed: 521 additions & 25 deletions

File tree

‎src/lib/browser-routing.ts‎

Lines changed: 27 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,8 @@ const DEFAULT_BROWSER_ROUTING_SUBRESOURCES = [
4747
'computer',
4848
'playwright',
4949
'process',
50+
'fs',
51+
'logs/stream',
5052
];
5153
const BROWSER_ROUTE_CACHEABLE_PATH = /^\/(?:v\d+\/)?browsers(?:\/[^/]+)?\/?$/;
5254
const BROWSER_POOL_ACQUIRE_PATH = /^\/(?:v\d+\/)?browser_pools\/[^/]+\/acquire\/?$/;
@@ -278,9 +280,13 @@ async function routeRequest(
278280

279281
const headers = new Headers(request.headers);
280282
headers.delete('authorization');
283+
const body = requestBodyForFetch(request, init);
281284
const routed = await innerFetch(target.toString(), buildRoutedInit(input, request, init, headers));
282285
if ((routed.status === 401 || routed.status === 403) && target.searchParams.get('jwt')) {
283286
cache.deleteIfJwt(sessionId, target.searchParams.get('jwt') ?? '');
287+
if (isStreamingBody(body)) {
288+
return routed;
289+
}
284290
await CancelReadableStream(routed.body);
285291
return innerFetch(input, init);
286292
}
@@ -294,6 +300,14 @@ function buildRoutedInit(
294300
headers: Headers,
295301
): RequestInit {
296302
const method = request.method.toUpperCase();
303+
const body = method === 'GET' || method === 'HEAD' ? undefined : requestBodyForFetch(request, originalInit);
304+
if (derivesOwnContentType(body) && !new Headers(originalInit?.headers).get('content-type')) {
305+
// `request` was constructed from the same body, so its content-type carries
306+
// that construction's multipart boundary. The routed fetch re-encodes the
307+
// body with a new boundary, so let it derive the header again.
308+
headers.delete('content-type');
309+
}
310+
297311
const routedInit = {
298312
...((originalInit ?? {}) as Record<string, unknown>),
299313
method,
@@ -305,14 +319,13 @@ function buildRoutedInit(
305319
delete routedInit['body'];
306320
delete routedInit['duplex'];
307321

322+
if (body !== undefined) {
323+
routedInit.body = body;
324+
}
308325
if (method !== 'GET' && method !== 'HEAD') {
309-
const body = requestBodyForFetch(request, originalInit);
310-
if (body !== undefined) {
311-
routedInit.body = body;
312-
}
313326
if (originalInit?.duplex !== undefined) {
314327
routedInit.duplex = originalInit.duplex;
315-
} else if (requiresHalfDuplex(body)) {
328+
} else if (isStreamingBody(body)) {
316329
routedInit.duplex = 'half';
317330
}
318331
}
@@ -331,7 +344,15 @@ function requestBodyForFetch(
331344
return request.body ?? undefined;
332345
}
333346

334-
function requiresHalfDuplex(body: RequestInit['body'] | undefined): boolean {
347+
function derivesOwnContentType(body: RequestInit['body'] | undefined): boolean {
348+
return (
349+
((globalThis as any).FormData && body instanceof (globalThis as any).FormData) ||
350+
((globalThis as any).URLSearchParams && body instanceof (globalThis as any).URLSearchParams) ||
351+
((globalThis as any).Blob && body instanceof (globalThis as any).Blob)
352+
);
353+
}
354+
355+
function isStreamingBody(body: RequestInit['body'] | undefined): boolean {
335356
return (
336357
((globalThis as any).ReadableStream && body instanceof (globalThis as any).ReadableStream) ||
337358
(typeof body === 'object' && body !== null && Symbol.asyncIterator in body)

0 commit comments

Comments
 (0)