Skip to content

Commit 8605a76

Browse files
gabivljmrbbot
andcommitted
Add Content-Length and FixedLengthStream to R2 get response (#712)
* Add Content-Length and FixedLengthStream to R2 get response Before this, we would see that the following code wouldn't work: ``` const res = await env.BUCKET.get(name); await env.BUCKET.put(name, res!.body); ``` As it would throw: TypeError: Provided readable stream must have a known length (request/response body or readable half of FixedLengthStream) * Test fixed length R2 get responses --------- Co-authored-by: bcoll <[email protected]>
1 parent 0b11310 commit 8605a76

File tree

3 files changed

+51
-3
lines changed

3 files changed

+51
-3
lines changed

packages/miniflare/src/workers/r2/bucket.worker.ts

+1
Original file line numberDiff line numberDiff line change
@@ -184,6 +184,7 @@ function encodeResult(
184184
headers: {
185185
[R2Headers.METADATA_SIZE]: `${encoded.metadataSize}`,
186186
"Content-Type": "application/json",
187+
"Content-Length": `${encoded.size}`,
187188
},
188189
});
189190
}

packages/miniflare/src/workers/r2/r2Object.worker.ts

+6-3
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import {
99
export interface EncodedMetadata {
1010
metadataSize: number;
1111
value: ReadableStream<Uint8Array>;
12+
size: number;
1213
}
1314

1415
export class InternalR2Object {
@@ -68,7 +69,7 @@ export class InternalR2Object {
6869
encode(): EncodedMetadata {
6970
const json = JSON.stringify(this.#rawProperties());
7071
const blob = new Blob([json]);
71-
return { metadataSize: blob.size, value: blob.stream() };
72+
return { metadataSize: blob.size, value: blob.stream(), size: blob.size };
7273
}
7374

7475
static encodeMultiple(objects: InternalR2Objects): EncodedMetadata {
@@ -77,7 +78,7 @@ export class InternalR2Object {
7778
objects: objects.objects.map((o) => o.#rawProperties()),
7879
});
7980
const blob = new Blob([json]);
80-
return { metadataSize: blob.size, value: blob.stream() };
81+
return { metadataSize: blob.size, value: blob.stream(), size: blob.size };
8182
}
8283
}
8384

@@ -92,13 +93,15 @@ export class InternalR2ObjectBody extends InternalR2Object {
9293

9394
encode(): EncodedMetadata {
9495
const { metadataSize, value: metadata } = super.encode();
95-
const identity = new IdentityTransformStream();
96+
const size = this.range?.length ?? this.size;
97+
const identity = new FixedLengthStream(size + metadataSize);
9698
void metadata
9799
.pipeTo(identity.writable, { preventClose: true })
98100
.then(() => this.body.pipeTo(identity.writable));
99101
return {
100102
metadataSize: metadataSize,
101103
value: identity.readable,
104+
size,
102105
};
103106
}
104107
}

packages/miniflare/test/plugins/r2/index.spec.ts

+44
Original file line numberDiff line numberDiff line change
@@ -549,6 +549,50 @@ test("put: validates metadata size", async (t) => {
549549
expectations
550550
);
551551
});
552+
test("put: can copy values", async (t) => {
553+
const mf = new Miniflare({
554+
r2Buckets: ["BUCKET"],
555+
modules: true,
556+
script: `export default {
557+
async fetch(request, env, ctx) {
558+
await env.BUCKET.put("key", "0123456789");
559+
560+
let object = await env.BUCKET.get("key");
561+
await env.BUCKET.put("key-copy", object.body);
562+
const copy = await (await env.BUCKET.get("key-copy"))?.text();
563+
564+
object = await env.BUCKET.get("key", { range: { offset: 1, length: 4 } });
565+
await env.BUCKET.put("key-copy-range-1", object.body);
566+
const copyRange1 = await (await env.BUCKET.get("key-copy-range-1"))?.text();
567+
568+
object = await env.BUCKET.get("key", { range: { length: 3 } });
569+
await env.BUCKET.put("key-copy-range-2", object.body);
570+
const copyRange2 = await (await env.BUCKET.get("key-copy-range-2"))?.text();
571+
572+
object = await env.BUCKET.get("key", { range: { suffix: 5 } });
573+
await env.BUCKET.put("key-copy-range-3", object.body);
574+
const copyRange3 = await (await env.BUCKET.get("key-copy-range-3"))?.text();
575+
576+
const range = new Headers();
577+
range.set("Range", "bytes=0-5");
578+
object = await env.BUCKET.get("key", { range });
579+
await env.BUCKET.put("key-copy-range-4", object.body);
580+
const copyRange4 = await (await env.BUCKET.get("key-copy-range-4"))?.text();
581+
582+
return Response.json({ copy, copyRange1, copyRange2, copyRange3, copyRange4 });
583+
}
584+
}`,
585+
});
586+
t.teardown(() => mf.dispose());
587+
const res = await mf.dispatchFetch("http://localhost");
588+
t.deepEqual(await res.json(), {
589+
copy: "0123456789",
590+
copyRange1: "1234",
591+
copyRange2: "012",
592+
copyRange3: "56789",
593+
copyRange4: "012345",
594+
});
595+
});
552596

553597
test("delete: deletes existing keys", async (t) => {
554598
const { r2, ns, object } = t.context;

0 commit comments

Comments
 (0)