Skip to content

Commit

Permalink
Add Content-Length and FixedLengthStream to R2 get response (#712)
Browse files Browse the repository at this point in the history
* 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 <bcoll@cloudflare.com>
  • Loading branch information
gabivlj and mrbbot committed Oct 10, 2023
1 parent 6cdb3c7 commit 7ec7fa3
Show file tree
Hide file tree
Showing 3 changed files with 51 additions and 3 deletions.
1 change: 1 addition & 0 deletions packages/miniflare/src/workers/r2/bucket.worker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -184,6 +184,7 @@ function encodeResult(
headers: {
[R2Headers.METADATA_SIZE]: `${encoded.metadataSize}`,
"Content-Type": "application/json",
"Content-Length": `${encoded.size}`,
},
});
}
Expand Down
9 changes: 6 additions & 3 deletions packages/miniflare/src/workers/r2/r2Object.worker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import {
export interface EncodedMetadata {
metadataSize: number;
value: ReadableStream<Uint8Array>;
size: number;
}

export class InternalR2Object {
Expand Down Expand Up @@ -68,7 +69,7 @@ export class InternalR2Object {
encode(): EncodedMetadata {
const json = JSON.stringify(this.#rawProperties());
const blob = new Blob([json]);
return { metadataSize: blob.size, value: blob.stream() };
return { metadataSize: blob.size, value: blob.stream(), size: blob.size };
}

static encodeMultiple(objects: InternalR2Objects): EncodedMetadata {
Expand All @@ -77,7 +78,7 @@ export class InternalR2Object {
objects: objects.objects.map((o) => o.#rawProperties()),
});
const blob = new Blob([json]);
return { metadataSize: blob.size, value: blob.stream() };
return { metadataSize: blob.size, value: blob.stream(), size: blob.size };
}
}

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

encode(): EncodedMetadata {
const { metadataSize, value: metadata } = super.encode();
const identity = new IdentityTransformStream();
const size = this.range?.length ?? this.size;
const identity = new FixedLengthStream(size + metadataSize);
void metadata
.pipeTo(identity.writable, { preventClose: true })
.then(() => this.body.pipeTo(identity.writable));
return {
metadataSize: metadataSize,
value: identity.readable,
size,
};
}
}
Expand Down
44 changes: 44 additions & 0 deletions packages/miniflare/test/plugins/r2/index.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -549,6 +549,50 @@ test("put: validates metadata size", async (t) => {
expectations
);
});
test("put: can copy values", async (t) => {
const mf = new Miniflare({
r2Buckets: ["BUCKET"],
modules: true,
script: `export default {
async fetch(request, env, ctx) {
await env.BUCKET.put("key", "0123456789");
let object = await env.BUCKET.get("key");
await env.BUCKET.put("key-copy", object.body);
const copy = await (await env.BUCKET.get("key-copy"))?.text();
object = await env.BUCKET.get("key", { range: { offset: 1, length: 4 } });
await env.BUCKET.put("key-copy-range-1", object.body);
const copyRange1 = await (await env.BUCKET.get("key-copy-range-1"))?.text();
object = await env.BUCKET.get("key", { range: { length: 3 } });
await env.BUCKET.put("key-copy-range-2", object.body);
const copyRange2 = await (await env.BUCKET.get("key-copy-range-2"))?.text();
object = await env.BUCKET.get("key", { range: { suffix: 5 } });
await env.BUCKET.put("key-copy-range-3", object.body);
const copyRange3 = await (await env.BUCKET.get("key-copy-range-3"))?.text();
const range = new Headers();
range.set("Range", "bytes=0-5");
object = await env.BUCKET.get("key", { range });
await env.BUCKET.put("key-copy-range-4", object.body);
const copyRange4 = await (await env.BUCKET.get("key-copy-range-4"))?.text();
return Response.json({ copy, copyRange1, copyRange2, copyRange3, copyRange4 });
}
}`,
});
t.teardown(() => mf.dispose());
const res = await mf.dispatchFetch("http://localhost");
t.deepEqual(await res.json(), {
copy: "0123456789",
copyRange1: "1234",
copyRange2: "012",
copyRange3: "56789",
copyRange4: "012345",
});
});

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

0 comments on commit 7ec7fa3

Please sign in to comment.