Skip to content

Commit 64937a0

Browse files
committed
fix(s3): do not include body in GET or HEAD requests
1 parent 36bf426 commit 64937a0

File tree

2 files changed

+55
-11
lines changed

2 files changed

+55
-11
lines changed

src/protocols/rest-xml.ts

Lines changed: 14 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,11 @@
1-
import * as FastCheck from "effect/FastCheck";
1+
import * as Stream from "effect/Stream";
2+
import { XMLBuilder, XMLParser } from "fast-xml-parser";
23
import type { ServiceMetadata } from "../client.ts";
34
import type {
45
ParsedError,
56
ProtocolHandler,
67
ProtocolRequest,
78
} from "./interface.ts";
8-
import { XMLBuilder, XMLParser } from "fast-xml-parser";
9-
import * as Stream from "effect/Stream";
109

1110
type Writeable<T> = { -readonly [P in keyof T]: T[P] };
1211

@@ -31,18 +30,24 @@ export class RestXmlHandler implements ProtocolHandler {
3130
string,
3231
];
3332

33+
const hasBody = method !== "GET" && method !== "HEAD";
34+
3435
const request: Writeable<ProtocolRequest> = {
3536
path: urlTemplate,
3637
method,
3738
headers: {
38-
"Content-Type":
39-
operationMeta?.inputTraits?.Body === "httpStreaming"
40-
? "application/octet-stream"
41-
: this.contentType,
4239
"User-Agent": "itty-aws",
4340
},
4441
};
4542

43+
// Only set Content-Type for methods that have a body
44+
if (hasBody) {
45+
request.headers["Content-Type"] =
46+
operationMeta?.inputTraits?.Body === "httpStreaming"
47+
? "application/octet-stream"
48+
: this.contentType;
49+
}
50+
4651
let body: Record<string, unknown> = {};
4752
let streamingBody = false;
4853

@@ -63,7 +68,8 @@ export class RestXmlHandler implements ProtocolHandler {
6368
}
6469
}
6570

66-
if (!streamingBody) {
71+
// Only set body for methods that support it
72+
if (!streamingBody && hasBody) {
6773
request.body = builder.build(body);
6874
}
6975

test/smoke/s3.test.ts

Lines changed: 41 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
import { fromNodeProviderChain } from "@aws-sdk/credential-providers";
2-
import { describe, expect, it } from "@effect/vitest";
3-
import { Console, Effect, Stream } from "effect";
4-
import { S3 } from "../../src/services/s3/index.ts";
52
import { FileSystem } from "@effect/platform";
63
import { NodeFileSystem } from "@effect/platform-node";
4+
import { describe, expect, it } from "@effect/vitest";
5+
import { Console, Effect, Stream } from "effect";
76
import path from "pathe";
7+
import { S3 } from "../../src/services/s3/index.ts";
88

99
const credentials = await fromNodeProviderChain()();
1010

@@ -55,4 +55,42 @@ describe("S3 Smoke Tests", () => {
5555
yield* fs.remove(outputFilePath);
5656
}).pipe(Effect.provide(NodeFileSystem.layer)),
5757
);
58+
59+
it.live("headBucket and headObject", () =>
60+
Effect.gen(function* () {
61+
const fs = yield* FileSystem.FileSystem;
62+
63+
yield* Console.log("Step 1: Create bucket");
64+
yield* client.createBucket({ Bucket: BUCKET_NAME });
65+
66+
yield* Console.log("Step 2: HeadBucket");
67+
const bucketHead = yield* client.headBucket({ Bucket: BUCKET_NAME });
68+
expect(bucketHead.BucketRegion).toBe("us-east-1");
69+
70+
yield* Console.log("Step 3: Upload a file");
71+
const rawInput = yield* fs.readFile(inputFilePath);
72+
yield* client.putObject({
73+
Bucket: BUCKET_NAME,
74+
Key: FILE_KEY,
75+
Body: rawInput,
76+
ContentType: "image/jpeg",
77+
});
78+
79+
yield* Console.log("Step 4: HeadObject");
80+
const objectHead = yield* client.headObject({
81+
Bucket: BUCKET_NAME,
82+
Key: FILE_KEY,
83+
});
84+
expect(objectHead.ContentType).toBe("image/jpeg");
85+
expect(objectHead.ContentLength).toBe(String(rawInput.byteLength));
86+
87+
yield* Console.log("Step 5: clean up");
88+
yield* client.deleteObject({
89+
Bucket: BUCKET_NAME,
90+
Key: FILE_KEY,
91+
});
92+
yield* client.deleteBucket({ Bucket: BUCKET_NAME });
93+
}).pipe(Effect.provide(NodeFileSystem.layer)),
94+
{timeout: 1000000}
95+
);
5896
});

0 commit comments

Comments
 (0)