Skip to content

Commit 29bdf44

Browse files
committed
debugging range request header
1 parent 39c837a commit 29bdf44

4 files changed

Lines changed: 86 additions & 62 deletions

File tree

package-lock.json

Lines changed: 4 additions & 4 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@
4747
"dependencies": {
4848
"@octokit/webhooks": "^7.15.1",
4949
"@webqit/backpack": "^0.1.12",
50-
"@webqit/fetch-plus": "^0.1.30",
50+
"@webqit/fetch-plus": "^0.1.31",
5151
"@webqit/keyval": "^0.2.17",
5252
"@webqit/observer": "^3.8.14",
5353
"@webqit/oohtml-ssr": "^2.2.4",

src/webflo-runtime/AppRuntime.js

Lines changed: 65 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -182,34 +182,39 @@ export class AppRuntime {
182182
const reader = stream.getReader();
183183
return new ReadableStream({
184184
async pull(controller) {
185-
while (true) {
186-
const { done, value } = await reader.read();
187-
if (done) {
188-
controller.close();
189-
break;
190-
}
191-
let chunk = value;
192-
let chunkStart = bytesRead;
193-
let chunkEnd = bytesRead + chunk.length;
194-
if (chunkEnd <= start) {
195-
// skip entire chunk
196-
bytesRead += chunk.length;
197-
continue;
198-
}
199-
if (chunkStart > end) {
200-
// past requested range
201-
controller.close();
202-
break;
203-
}
204-
// slice relevant part
205-
let sliceStart = Math.max(start - chunkStart, 0);
206-
let sliceEnd = Math.min(end - chunkStart + 1, chunk.length);
207-
controller.enqueue(chunk.subarray(sliceStart, sliceEnd));
208-
bytesRead += chunk.length;
209-
if (chunkEnd > end) {
210-
controller.close();
211-
break;
212-
}
185+
// Read one chunk at a time
186+
const { done, value } = await reader.read();
187+
188+
if (done) {
189+
controller.close();
190+
return;
191+
}
192+
193+
let chunk = value;
194+
let chunkStart = bytesRead;
195+
let chunkEnd = bytesRead + chunk.length;
196+
bytesRead += chunk.length; // Correctly increment for every chunk seen
197+
198+
if (chunkEnd <= start) {
199+
// Not at the start yet, just return and wait for next pull
200+
return;
201+
}
202+
if (chunkStart > end) {
203+
// Already past the requested range
204+
controller.close();
205+
reader.releaseLock();
206+
return;
207+
}
208+
209+
// Calculate the sub-slice of the current chunk
210+
let sliceStart = Math.max(start - chunkStart, 0);
211+
let sliceEnd = Math.min(end - chunkStart + 1, chunk.length);
212+
213+
controller.enqueue(chunk.subarray(sliceStart, sliceEnd));
214+
215+
if (chunkEnd > end) {
216+
controller.close();
217+
reader.releaseLock();
213218
}
214219
},
215220
cancel(reason) {
@@ -219,51 +224,55 @@ export class AppRuntime {
219224
}
220225

221226
streamJoin(streams, contentType, totalLength) {
222-
// Single stream?
223227
if (streams.length === 1) {
224228
return streams[0];
225229
}
226-
// Multipart!
230+
227231
const boundary = `WEBFLO_BOUNDARY_${Math.random().toString(36).slice(2)}`;
228232
const encoder = new TextEncoder();
229-
// Generator for multipart chunks
233+
let gen; // Declare generator holder
234+
235+
const self = this;
230236
async function* generateMultipart() {
231237
for (const { stream, start, end } of streams) {
232-
// Boundary + headers for each part
233238
yield encoder.encode(`--${boundary}\r\n`);
234239
yield encoder.encode(
235240
`Content-Type: ${contentType}\r\n` +
236241
`Content-Range: bytes ${start}-${end}/${totalLength}\r\n\r\n`
237242
);
238-
// Stream the sliced body
243+
239244
const reader = stream.getReader();
240-
while (true) {
241-
const { done, value } = await reader.read();
242-
if (done) break;
243-
yield value;
245+
try {
246+
while (true) {
247+
const { done, value } = await reader.read();
248+
if (done) break;
249+
yield value;
250+
}
251+
} finally {
252+
reader.releaseLock();
244253
}
245254

246255
yield encoder.encode('\r\n');
247256
}
248-
// Final boundary
249257
yield encoder.encode(`--${boundary}--\r\n`);
250258
}
251-
// Create ReadableStream from async generator
259+
252260
return {
253261
stream: new ReadableStream({
262+
start() {
263+
// Initialize the generator ONCE when the stream starts
264+
gen = generateMultipart();
265+
},
254266
async pull(controller) {
255-
const gen = generateMultipart();
256-
while (true) {
257-
const { done, value } = await gen.next();
258-
if (done) {
259-
controller.close();
260-
break;
261-
}
267+
const { done, value } = await gen.next();
268+
if (done) {
269+
controller.close();
270+
} else {
262271
controller.enqueue(value);
263272
}
264273
},
265274
cancel(reason) {
266-
// Handle cancellation if needed
275+
// Logic to cancel underlying streams if necessary
267276
}
268277
}),
269278
boundary,
@@ -273,19 +282,20 @@ export class AppRuntime {
273282
createStreamingResponse(httpEvent, readStream, stats) {
274283
let response;
275284
const requestRange = httpEvent.request.headers.get('Range', true); // Parses the Range header
276-
285+
277286
if (requestRange.length) {
278287
const streams = requestRange.reduce((streams, range) => {
279-
if (!streams) return;
280-
const currentStart = (streams[streams.length - 1]?.end || -1) + 1;
288+
if (!streams) return null;
289+
const currentStart = 0; // (streams[streams.length - 1]?.end || -1) + 1;
281290

282-
if (!range.canResolveAgainst(currentStart, stats.size)) return; // Only after rendering()
291+
if (!range.canResolveAgainst(currentStart, stats.size)) return null; // Only after rendering()
283292
const [start, end] = range.resolveAgainst(stats.size); // Resolve offsets
284293

285294
try {
286295
return streams.concat({ start, end, stream: readStream({ start, end }) });
287-
} catch(e) {
288-
console.log('_______', httpEvent.request.headers.get('Range'), requestRange, stats.size, [start, end]);
296+
} catch (e) {
297+
console.error('Failed to create range stream:', e);
298+
return null
289299
}
290300
}, []);
291301

@@ -299,6 +309,7 @@ export class AppRuntime {
299309

300310
const streamJoin = this.streamJoin(streams, stats.mime, stats.size);
301311
response = new Response(streamJoin.stream, { status: 206, statusText: 'Partial Content' });
312+
302313
if (streamJoin.boundary) {
303314
response.headers.set('Content-Type', `multipart/byteranges; boundary=${streamJoin.boundary}`);
304315
} else {
@@ -311,7 +322,7 @@ export class AppRuntime {
311322
response.headers.set('Content-Type', stats.mime);
312323
response.headers.set('Content-Length', stats.size);
313324
}
314-
325+
315326
return response;
316327
}
317328
}

src/webflo-runtime/webflo-server/WebfloServer.js

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -698,8 +698,21 @@ export class WebfloServer extends AppRuntime {
698698

699699
// Range support
700700
const readStream = (params = {}) => {
701-
if (scopeObj.fileContents) return Readable.from(scopeObj.fileContents);
702-
return Fs.createReadStream(scopeObj.filename, { ...params });
701+
const { start, end } = params;
702+
if (scopeObj.fileContents) {
703+
let data = scopeObj.fileContents;
704+
// Ensure strings are handled as bytes for accurate ranging
705+
if (typeof data === 'string') {
706+
data = new TextEncoder().encode(data);
707+
}
708+
// subarray is inclusive of start, exclusive of end (hence end + 1)
709+
const source = (start !== undefined && end !== undefined)
710+
? data.subarray(start, end + 1)
711+
: data;
712+
return Readable.from(source);
713+
}
714+
// Fs is naturally inclusive of both start and end
715+
return Fs.createReadStream(scopeObj.filename, params);
703716
};
704717
scopeObj.response = this.createStreamingResponse(httpEvent, readStream, scopeObj.stats);
705718
const statusCode = scopeObj.response.status;
@@ -965,7 +978,7 @@ export class WebfloServer extends AppRuntime {
965978
}, { diff: true });
966979
}
967980

968-
await new Promise(res => setTimeout(res, 300));
981+
await new Promise(res => setTimeout(res, 100));
969982
}
970983

971984
for (const name of ['X-Message-Port', 'X-Webflo-Dev-Mode']) {

0 commit comments

Comments
 (0)