Skip to content

Commit ca83732

Browse files
sichanyooSichan Yoo
andauthored
feat: Checksum reuse between retries in chunked streaming requests (#946)
* Changes required to reuse finalized checksums in chunked stremaing. * Temporary github CI change to make logs more verbose for debugging. * Temp workflow change. * Undo temp GH workflow changes previously made for debugging purposes. --------- Co-authored-by: Sichan Yoo <[email protected]>
1 parent 7af9728 commit ca83732

File tree

3 files changed

+26
-8
lines changed

3 files changed

+26
-8
lines changed

Sources/ClientRuntime/Networking/Http/Middlewares/SignerMiddleware.swift

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,11 @@ extension SignerMiddleware: ApplySigner {
5757
updatedSigningProperties.set(key: AttributeKey(name: "SignedBodyValue"), value: bodyValue)
5858
}
5959

60+
if case .stream(let stream) = request.body, stream.isEligibleForChunkedStreaming {
61+
// Pass in context object via signing properties to reuse final checksum value in chunked streaming.
62+
updatedSigningProperties.set(key: AttributeKey(name: "Context"), value: attributes)
63+
}
64+
6065
let signed = try await signer.signRequest(
6166
requestBuilder: request.toBuilder(),
6267
identity: identity,

Sources/SmithyChecksums/ChunkedReader.swift

Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -26,13 +26,16 @@ public class ChunkedReader {
2626
private var checksumAlgorithm: ChecksumAlgorithm?
2727
private var checksum: (any Checksum)?
2828
private var checksumString: String?
29+
private let context: Smithy.Context
30+
private let cachedChecksum: String?
2931

3032
init(
3133
stream: ReadableStream,
3234
signingConfig: SigningConfig,
3335
previousSignature: String,
3436
trailingHeaders: Headers,
35-
checksumString: String? = nil // if nil, chunked encoding without checksum
37+
checksumString: String? = nil, // if nil, chunked encoding without checksum
38+
context: Smithy.Context
3639
) throws {
3740
self.stream = stream
3841
self.signingConfig = signingConfig
@@ -52,7 +55,8 @@ public class ChunkedReader {
5255
} else {
5356
checksumAlgorithm = nil
5457
}
55-
58+
self.context = context
59+
self.cachedChecksum = context.get(key: AttributeKey<String>(name: "ChunkedStreamCachedChecksum"))
5660
self.checksumAlgorithm = checksumAlgorithm // Enum for working with the checksum
5761
self.checksum = self.checksumAlgorithm?.createChecksum() // Create an instance of the Checksum
5862
}
@@ -67,10 +71,9 @@ public class ChunkedReader {
6771
let nextChunk = try await fetchNextChunk()
6872

6973
if let chunk = nextChunk {
70-
if let checksum = self.checksum {
74+
if let checksum = self.checksum, cachedChecksum == nil {
7175
try checksum.update(chunk: self.chunkBody)
7276
}
73-
7477
self.chunk = chunk // Store the current chunk
7578
return true // Chunk processed successfully
7679
} else {
@@ -86,7 +89,15 @@ public class ChunkedReader {
8689
// Add a checksum if it is not nil
8790
if let checksum = self.checksum {
8891
let headerName = "x-amz-checksum-\(checksum.checksumName)"
89-
let hashResult = try checksum.digest().toBase64String()
92+
let hashResult: String!
93+
// Use cached checksum from previous attempt if present.
94+
// Allows request to fail if file payload has changed between retries, increasing durability for S3.
95+
if let cachedChecksum {
96+
hashResult = cachedChecksum
97+
} else {
98+
hashResult = try checksum.digest().toBase64String()
99+
context.set(key: AttributeKey<String>(name: "ChunkedStreamCachedChecksum"), value: hashResult)
100+
}
90101
self.updateTrailingHeader(name: headerName, value: hashResult)
91102
}
92103

Sources/SmithyChecksums/ChunkedStream.swift

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import struct SmithyHTTPAPI.Headers
1010
import struct Foundation.Data
1111
import AwsCommonRuntimeKit
1212
import class SmithyStreams.BufferedStream
13+
import class Smithy.Context
1314

1415
/// Reads data from the input stream, chunks it, and passes it out through its output stream.
1516
///
@@ -50,20 +51,21 @@ public class ChunkedStream: @unchecked Sendable {
5051
signingConfig: SigningConfig,
5152
previousSignature: String,
5253
trailingHeaders: Headers,
53-
checksumString: String? = nil
54+
checksumString: String? = nil,
55+
context: Smithy.Context
5456
) throws {
5557
self.inputStream = inputStream
5658
self.signingConfig = signingConfig
5759
self.previousSignature = previousSignature
5860
self.trailingHeaders = trailingHeaders
5961
self.checksumString = checksumString
60-
6162
self.chunkedReader = try ChunkedReader(
6263
stream: self.inputStream,
6364
signingConfig: self.signingConfig,
6465
previousSignature: self.previousSignature,
6566
trailingHeaders: self.trailingHeaders,
66-
checksumString: self.checksumString
67+
checksumString: self.checksumString,
68+
context: context
6769
)
6870
}
6971
}

0 commit comments

Comments
 (0)