I’m developing a worker to serve audio files from R2 Storage. When I start the worker with wrangler dev and make a request using Postman (or any other similar tool), everything works just fine.
But when I put the link in an html audio tag like so:
Then on initial page load the browser makes a request to the specified resource and cancels the request pretty much instantly so it can get the headers and metadata of the audio file.
Then every time after exactly 1 minute the worker crashes. This only occurs when the client cancels a request while data is transferred.
I’ve also tried to deploy it, but then everything works fine.
Does anyone know how to fix this, or how to prevent this from happening?
I just want to get the file from R2 Storage with the RADIO_BUCKET binding and send it to the client. I also want to support Range Requests. These however seem to work perfectly fine.
I try to achieve this by doing the following:
Get the R2 object metadata
Try to get the ‘Range’ header
Try to parse the ‘Range’ header
If the Range header is present and in the correct format it will return the specified byte range of the object.
If the Range header is not present or in not correct format then return the full R2 object.
Here is my code
export default {
async fetch(request: Request, env: Env, context: ExecutionContext) {
// handle /audio/ requests
if (request.url.match(/\/audio\//)) {
const response = await audio(request, env);
return response;
} else {
return new Response("Not found", { status: 404 });
}
},
};
async function audio(request: Request, env: Env) {
// Get file name from path
const url = new URL(request.url);
const key = url.pathname.split("/")[2];
console.log(key);
// Get object metadata
const objectData = await env.RADIO_BUCKET.head(key);
if (!objectData) return new Response("Not found", { status: 404 });
// Get range header
const rangeHeader = request.headers.get("Range");
// If no range header, return full object
if (!rangeHeader) {
const object = await env.RADIO_BUCKET.get(key);
return objectResponse(object!, 200);
}
// If range header, return partial object
const range = parseRangeHeader(rangeHeader, objectData.size);
if (!range) return new Response("Range not satisfiable", { status: 416 });
const { start, end } = range;
const object = await env.RADIO_BUCKET.get(key, {
range: {
offset: start,
length: end - start + 1,
},
});
if (!object) return new Response("Not found", { status: 404 });
return objectResponse(object, 206);
}
function objectResponse(object: R2ObjectBody, status: 206 | 200): Response {
// Set headers
const headers = new Headers();
object.writeHttpMetadata(headers);
headers.set("Access-Control-Allow-Origin", "*");
headers.set("Accept-Ranges", "bytes");
headers.set("etag", object.httpEtag);
// If partial content, set content range header
if (status === 206) {
const objectRange = object.range as { offset: number; length: number };
const start = objectRange.offset;
const end = objectRange.offset + objectRange.length - 1;
const size = object.size;
headers.set("Content-Range", `bytes ${start}-${end}/${size}`);
}
return new Response(object.body, {
headers,
status: status,
});
}
function parseRangeHeader(range: string, size: number) {
if (range.startsWith("bytes=")) {
const parts = range.replace("bytes=", "").split("-");
const start = parseInt(parts[0], 10);
const end = parts[1] ? parseInt(parts[1], 10) : size - 1;
return { start, end };
}
return null;
}
Here is the error
/<pathtoproject>/node_modules/wrangler/wrangler-dist/cli.js:30488
throw a;
^
Error [ERR_HTTP2_STREAM_ERROR]: Stream closed with error code NGHTTP2_PROTOCOL_ERROR
at new NodeError (node:internal/errors:399:5)
at ClientHttp2Stream._destroy (node:internal/http2/core:2347:13)
at _destroy (node:internal/streams/destroy:109:10)
at ClientHttp2Stream.destroy (node:internal/streams/destroy:71:5)
at Http2Stream.onStreamClose (node:internal/http2/core:550:12)
Emitted 'error' event on ClientHttp2Stream instance at:
at emitErrorNT (node:internal/streams/destroy:151:8)
at emitErrorCloseNT (node:internal/streams/destroy:116:3)
at process.processTicksAndRejections (node:internal/process/task_queues:82:21) {
code: 'ERR_HTTP2_STREAM_ERROR'
}
Node.js v19.8.0