Skip to content

Commit e0c25d6

Browse files
authored
feat: support default checksums (#1191)
1 parent 52e4439 commit e0c25d6

File tree

24 files changed

+865
-351
lines changed

24 files changed

+865
-351
lines changed

codegen/smithy-kotlin-codegen/src/main/kotlin/software/amazon/smithy/kotlin/codegen/core/RuntimeTypes.kt

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,7 @@ object RuntimeTypes {
8181
object Interceptors : RuntimeTypePackage(KotlinDependency.HTTP, "interceptors") {
8282
val ContinueInterceptor = symbol("ContinueInterceptor")
8383
val HttpInterceptor = symbol("HttpInterceptor")
84-
val Md5ChecksumInterceptor = symbol("Md5ChecksumInterceptor")
84+
val HttpChecksumRequiredInterceptor = symbol("HttpChecksumRequiredInterceptor")
8585
val FlexibleChecksumsRequestInterceptor = symbol("FlexibleChecksumsRequestInterceptor")
8686
val FlexibleChecksumsResponseInterceptor = symbol("FlexibleChecksumsResponseInterceptor")
8787
val ResponseLengthValidationInterceptor = symbol("ResponseLengthValidationInterceptor")
@@ -231,6 +231,9 @@ object RuntimeTypes {
231231
object Config : RuntimeTypePackage(KotlinDependency.SMITHY_CLIENT, "config") {
232232
val RequestCompressionConfig = symbol("RequestCompressionConfig")
233233
val CompressionClientConfig = symbol("CompressionClientConfig")
234+
val HttpChecksumConfig = symbol("HttpChecksumConfig")
235+
val RequestHttpChecksumConfig = symbol("RequestHttpChecksumConfig")
236+
val ResponseHttpChecksumConfig = symbol("ResponseHttpChecksumConfig")
234237
}
235238

236239
object Endpoints : RuntimeTypePackage(KotlinDependency.SMITHY_CLIENT, "endpoints") {
@@ -395,6 +398,7 @@ object RuntimeTypes {
395398
val TelemetryContextElement = symbol("TelemetryContextElement", "context")
396399
val TraceSpan = symbol("TraceSpan", "trace")
397400
val withSpan = symbol("withSpan", "trace")
401+
val warn = symbol("warn", "logging")
398402
}
399403
object TelemetryDefaults : RuntimeTypePackage(KotlinDependency.TELEMETRY_DEFAULTS) {
400404
val Global = symbol("Global")
@@ -409,6 +413,7 @@ object RuntimeTypes {
409413

410414
val CompletableDeferred = "kotlinx.coroutines.CompletableDeferred".toSymbol()
411415
val job = "kotlinx.coroutines.job".toSymbol()
416+
val runBlocking = "kotlinx.coroutines.runBlocking".toSymbol()
412417

413418
object Flow {
414419
// NOTE: smithy-kotlin core has an API dependency on this already
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
package software.amazon.smithy.kotlin.codegen.rendering.checksums
2+
3+
import software.amazon.smithy.aws.traits.HttpChecksumTrait
4+
import software.amazon.smithy.kotlin.codegen.KotlinSettings
5+
import software.amazon.smithy.kotlin.codegen.core.KotlinWriter
6+
import software.amazon.smithy.kotlin.codegen.core.RuntimeTypes
7+
import software.amazon.smithy.kotlin.codegen.integration.KotlinIntegration
8+
import software.amazon.smithy.kotlin.codegen.model.hasTrait
9+
import software.amazon.smithy.kotlin.codegen.rendering.protocol.ProtocolGenerator
10+
import software.amazon.smithy.kotlin.codegen.rendering.protocol.ProtocolMiddleware
11+
import software.amazon.smithy.model.Model
12+
import software.amazon.smithy.model.shapes.OperationShape
13+
import software.amazon.smithy.model.traits.HttpChecksumRequiredTrait
14+
15+
/**
16+
* Handles the `httpChecksumRequired` trait.
17+
* See: https://smithy.io/2.0/spec/http-bindings.html#httpchecksumrequired-trait
18+
*/
19+
class HttpChecksumRequiredIntegration : KotlinIntegration {
20+
override fun enabledForService(model: Model, settings: KotlinSettings): Boolean =
21+
model.isTraitApplied(HttpChecksumRequiredTrait::class.java)
22+
23+
override fun customizeMiddleware(
24+
ctx: ProtocolGenerator.GenerationContext,
25+
resolved: List<ProtocolMiddleware>,
26+
): List<ProtocolMiddleware> = resolved + httpChecksumRequiredDefaultAlgorithmMiddleware + httpChecksumRequiredMiddleware
27+
}
28+
29+
/**
30+
* Adds default checksum algorithm to the execution context
31+
*/
32+
private val httpChecksumRequiredDefaultAlgorithmMiddleware = object : ProtocolMiddleware {
33+
override val name: String = "httpChecksumRequiredDefaultAlgorithmMiddleware"
34+
override val order: Byte = -2 // Before S3 Express (possibly) changes the default (-1) and before calculating checksum (0)
35+
36+
override fun isEnabledFor(ctx: ProtocolGenerator.GenerationContext, op: OperationShape): Boolean =
37+
op.hasTrait<HttpChecksumRequiredTrait>() && !op.hasTrait<HttpChecksumTrait>()
38+
39+
override fun render(ctx: ProtocolGenerator.GenerationContext, op: OperationShape, writer: KotlinWriter) {
40+
writer.write(
41+
"op.context[#T.DefaultChecksumAlgorithm] = #S",
42+
RuntimeTypes.HttpClient.Operation.HttpOperationContext,
43+
"MD5",
44+
)
45+
}
46+
}
47+
48+
/**
49+
* Adds interceptor to calculate request checksums.
50+
* The `httpChecksum` trait supersedes the `httpChecksumRequired` trait. If both are applied to an operation use `httpChecksum`.
51+
*
52+
* See: https://smithy.io/2.0/aws/aws-core.html#behavior-with-httpchecksumrequired
53+
*/
54+
private val httpChecksumRequiredMiddleware = object : ProtocolMiddleware {
55+
override val name: String = "httpChecksumRequiredMiddleware"
56+
57+
override fun isEnabledFor(ctx: ProtocolGenerator.GenerationContext, op: OperationShape): Boolean =
58+
op.hasTrait<HttpChecksumRequiredTrait>() && !op.hasTrait<HttpChecksumTrait>()
59+
60+
override fun render(ctx: ProtocolGenerator.GenerationContext, op: OperationShape, writer: KotlinWriter) {
61+
writer.write(
62+
"op.interceptors.add(#T())",
63+
RuntimeTypes.HttpClient.Interceptors.HttpChecksumRequiredInterceptor,
64+
)
65+
}
66+
}

codegen/smithy-kotlin-codegen/src/main/kotlin/software/amazon/smithy/kotlin/codegen/rendering/protocol/HttpProtocolClientGenerator.kt

Lines changed: 0 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@
44
*/
55
package software.amazon.smithy.kotlin.codegen.rendering.protocol
66

7-
import software.amazon.smithy.aws.traits.HttpChecksumTrait
87
import software.amazon.smithy.codegen.core.Symbol
98
import software.amazon.smithy.kotlin.codegen.core.*
109
import software.amazon.smithy.kotlin.codegen.integration.SectionId
@@ -22,7 +21,6 @@ import software.amazon.smithy.model.knowledge.OperationIndex
2221
import software.amazon.smithy.model.knowledge.TopDownIndex
2322
import software.amazon.smithy.model.shapes.OperationShape
2423
import software.amazon.smithy.model.traits.EndpointTrait
25-
import software.amazon.smithy.model.traits.HttpChecksumRequiredTrait
2624

2725
/**
2826
* Renders an implementation of a service interface for HTTP protocol
@@ -318,8 +316,6 @@ open class HttpProtocolClientGenerator(
318316
.forEach { middleware ->
319317
middleware.render(ctx, op, writer)
320318
}
321-
322-
op.renderIsMd5ChecksumRequired(writer)
323319
}
324320

325321
/**
@@ -336,27 +332,6 @@ open class HttpProtocolClientGenerator(
336332
*/
337333
protected open fun renderAdditionalMethods(writer: KotlinWriter) { }
338334

339-
/**
340-
* Render optionally installing Md5ChecksumMiddleware.
341-
* The Md5 middleware will only be installed if the operation requires a checksum and the user has not opted-in to flexible checksums.
342-
*/
343-
private fun OperationShape.renderIsMd5ChecksumRequired(writer: KotlinWriter) {
344-
val httpChecksumTrait = getTrait<HttpChecksumTrait>()
345-
346-
// the checksum requirement can be modeled in either HttpChecksumTrait's `requestChecksumRequired` or the HttpChecksumRequired trait
347-
if (!hasTrait<HttpChecksumRequiredTrait>() && httpChecksumTrait == null) {
348-
return
349-
}
350-
351-
if (hasTrait<HttpChecksumRequiredTrait>() || httpChecksumTrait?.isRequestChecksumRequired == true) {
352-
val interceptorSymbol = RuntimeTypes.HttpClient.Interceptors.Md5ChecksumInterceptor
353-
val inputSymbol = ctx.symbolProvider.toSymbol(ctx.model.expectShape(inputShape))
354-
writer.withBlock("op.interceptors.add(#T<#T> {", "})", interceptorSymbol, inputSymbol) {
355-
writer.write("op.context.getOrNull(#T.ChecksumAlgorithm) == null", RuntimeTypes.HttpClient.Operation.HttpOperationContext)
356-
}
357-
}
358-
}
359-
360335
/**
361336
* render a utility function to populate an operation's ExecutionContext with defaults from service config, environment, etc
362337
*/

codegen/smithy-kotlin-codegen/src/main/resources/META-INF/services/software.amazon.smithy.kotlin.codegen.integration.KotlinIntegration

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,3 +13,4 @@ software.amazon.smithy.kotlin.codegen.rendering.endpoints.SdkEndpointBuiltinInte
1313
software.amazon.smithy.kotlin.codegen.rendering.compression.RequestCompressionIntegration
1414
software.amazon.smithy.kotlin.codegen.rendering.auth.SigV4AsymmetricAuthSchemeIntegration
1515
software.amazon.smithy.kotlin.codegen.rendering.smoketests.SmokeTestsIntegration
16+
software.amazon.smithy.kotlin.codegen.rendering.checksums.HttpChecksumRequiredIntegration

runtime/protocol/http-client/api/http-client.api

Lines changed: 8 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -255,7 +255,7 @@ public final class aws/smithy/kotlin/runtime/http/engine/internal/ManagedHttpCli
255255
public static final fun manage (Laws/smithy/kotlin/runtime/http/engine/HttpClientEngine;)Laws/smithy/kotlin/runtime/http/engine/HttpClientEngine;
256256
}
257257

258-
public abstract class aws/smithy/kotlin/runtime/http/interceptors/AbstractChecksumInterceptor : aws/smithy/kotlin/runtime/client/Interceptor {
258+
public abstract class aws/smithy/kotlin/runtime/http/interceptors/CachingChecksumInterceptor : aws/smithy/kotlin/runtime/client/Interceptor {
259259
public fun <init> ()V
260260
public abstract fun applyChecksum (Laws/smithy/kotlin/runtime/client/ProtocolRequestInterceptorContext;Ljava/lang/String;)Laws/smithy/kotlin/runtime/http/request/HttpRequest;
261261
public abstract fun calculateChecksum (Laws/smithy/kotlin/runtime/client/ProtocolRequestInterceptorContext;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
@@ -331,19 +331,17 @@ public final class aws/smithy/kotlin/runtime/http/interceptors/DiscoveredEndpoin
331331
public fun readBeforeTransmit (Laws/smithy/kotlin/runtime/client/ProtocolRequestInterceptorContext;)V
332332
}
333333

334-
public final class aws/smithy/kotlin/runtime/http/interceptors/FlexibleChecksumsRequestInterceptor : aws/smithy/kotlin/runtime/http/interceptors/AbstractChecksumInterceptor {
335-
public fun <init> ()V
336-
public fun <init> (Lkotlin/jvm/functions/Function1;)V
337-
public synthetic fun <init> (Lkotlin/jvm/functions/Function1;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
334+
public final class aws/smithy/kotlin/runtime/http/interceptors/FlexibleChecksumsRequestInterceptor : aws/smithy/kotlin/runtime/http/interceptors/CachingChecksumInterceptor {
335+
public fun <init> (ZLaws/smithy/kotlin/runtime/client/config/RequestHttpChecksumConfig;Ljava/lang/String;)V
338336
public fun applyChecksum (Laws/smithy/kotlin/runtime/client/ProtocolRequestInterceptorContext;Ljava/lang/String;)Laws/smithy/kotlin/runtime/http/request/HttpRequest;
339337
public fun calculateChecksum (Laws/smithy/kotlin/runtime/client/ProtocolRequestInterceptorContext;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
340338
public fun modifyBeforeSigning (Laws/smithy/kotlin/runtime/client/ProtocolRequestInterceptorContext;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
341-
public fun readAfterSerialization (Laws/smithy/kotlin/runtime/client/ProtocolRequestInterceptorContext;)V
342339
}
343340

344-
public final class aws/smithy/kotlin/runtime/http/interceptors/FlexibleChecksumsResponseInterceptor : aws/smithy/kotlin/runtime/client/Interceptor {
341+
public class aws/smithy/kotlin/runtime/http/interceptors/FlexibleChecksumsResponseInterceptor : aws/smithy/kotlin/runtime/client/Interceptor {
345342
public static final field Companion Laws/smithy/kotlin/runtime/http/interceptors/FlexibleChecksumsResponseInterceptor$Companion;
346-
public fun <init> (Lkotlin/jvm/functions/Function1;)V
343+
public fun <init> (ZLaws/smithy/kotlin/runtime/client/config/ResponseHttpChecksumConfig;)V
344+
public fun ignoreChecksum (Ljava/lang/String;Laws/smithy/kotlin/runtime/client/ProtocolResponseInterceptorContext;)Z
347345
public fun modifyBeforeAttemptCompletion-gIAlu-s (Laws/smithy/kotlin/runtime/client/ResponseInterceptorContext;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
348346
public fun modifyBeforeCompletion-gIAlu-s (Laws/smithy/kotlin/runtime/client/ResponseInterceptorContext;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
349347
public fun modifyBeforeDeserialization (Laws/smithy/kotlin/runtime/client/ProtocolResponseInterceptorContext;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
@@ -369,10 +367,8 @@ public final class aws/smithy/kotlin/runtime/http/interceptors/FlexibleChecksums
369367
public final fun getChecksumHeaderValidated ()Laws/smithy/kotlin/runtime/collections/AttributeKey;
370368
}
371369

372-
public final class aws/smithy/kotlin/runtime/http/interceptors/Md5ChecksumInterceptor : aws/smithy/kotlin/runtime/http/interceptors/AbstractChecksumInterceptor {
370+
public final class aws/smithy/kotlin/runtime/http/interceptors/HttpChecksumRequiredInterceptor : aws/smithy/kotlin/runtime/http/interceptors/CachingChecksumInterceptor {
373371
public fun <init> ()V
374-
public fun <init> (Lkotlin/jvm/functions/Function1;)V
375-
public synthetic fun <init> (Lkotlin/jvm/functions/Function1;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
376372
public fun applyChecksum (Laws/smithy/kotlin/runtime/client/ProtocolRequestInterceptorContext;Ljava/lang/String;)Laws/smithy/kotlin/runtime/http/request/HttpRequest;
377373
public fun calculateChecksum (Laws/smithy/kotlin/runtime/client/ProtocolRequestInterceptorContext;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
378374
public fun modifyBeforeSigning (Laws/smithy/kotlin/runtime/client/ProtocolRequestInterceptorContext;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
@@ -516,9 +512,9 @@ public abstract interface class aws/smithy/kotlin/runtime/http/operation/HttpDes
516512

517513
public final class aws/smithy/kotlin/runtime/http/operation/HttpOperationContext {
518514
public static final field INSTANCE Laws/smithy/kotlin/runtime/http/operation/HttpOperationContext;
519-
public final fun getChecksumAlgorithm ()Laws/smithy/kotlin/runtime/collections/AttributeKey;
520515
public final fun getClockSkew ()Laws/smithy/kotlin/runtime/collections/AttributeKey;
521516
public final fun getClockSkewApproximateSigningTime ()Laws/smithy/kotlin/runtime/collections/AttributeKey;
517+
public final fun getDefaultChecksumAlgorithm ()Laws/smithy/kotlin/runtime/collections/AttributeKey;
522518
public final fun getHostPrefix ()Laws/smithy/kotlin/runtime/collections/AttributeKey;
523519
public final fun getHttpCallList ()Laws/smithy/kotlin/runtime/collections/AttributeKey;
524520
public final fun getOperationAttributes ()Laws/smithy/kotlin/runtime/collections/AttributeKey;
Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,13 +9,21 @@ import aws.smithy.kotlin.runtime.InternalApi
99
import aws.smithy.kotlin.runtime.client.ProtocolRequestInterceptorContext
1010
import aws.smithy.kotlin.runtime.http.request.HttpRequest
1111

12+
/**
13+
* Enables inheriting [HttpInterceptor]s to use checksums caching
14+
*/
1215
@InternalApi
13-
public abstract class AbstractChecksumInterceptor : HttpInterceptor {
16+
public abstract class CachingChecksumInterceptor : HttpInterceptor {
1417
private var cachedChecksum: String? = null
1518

1619
override suspend fun modifyBeforeSigning(context: ProtocolRequestInterceptorContext<Any, HttpRequest>): HttpRequest {
17-
cachedChecksum ?: calculateChecksum(context).also { cachedChecksum = it }
18-
return cachedChecksum?.let { applyChecksum(context, it) } ?: context.protocolRequest
20+
cachedChecksum = cachedChecksum ?: calculateChecksum(context)
21+
22+
return if (cachedChecksum != null) {
23+
applyChecksum(context, cachedChecksum!!)
24+
} else {
25+
context.protocolRequest
26+
}
1927
}
2028

2129
public abstract suspend fun calculateChecksum(context: ProtocolRequestInterceptorContext<Any, HttpRequest>): String?
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
package aws.smithy.kotlin.runtime.http.interceptors
2+
3+
import aws.smithy.kotlin.runtime.businessmetrics.emitBusinessMetric
4+
import aws.smithy.kotlin.runtime.client.ProtocolRequestInterceptorContext
5+
import aws.smithy.kotlin.runtime.hashing.HashFunction
6+
import aws.smithy.kotlin.runtime.hashing.resolveChecksumAlgorithmHeaderName
7+
import aws.smithy.kotlin.runtime.hashing.toBusinessMetric
8+
import aws.smithy.kotlin.runtime.http.operation.HttpOperationContext
9+
import aws.smithy.kotlin.runtime.http.request.HttpRequest
10+
import aws.smithy.kotlin.runtime.http.request.toBuilder
11+
import aws.smithy.kotlin.runtime.http.toCompletingBody
12+
import aws.smithy.kotlin.runtime.http.toHashingBody
13+
import kotlinx.coroutines.CompletableDeferred
14+
import kotlinx.coroutines.job
15+
16+
/**
17+
* Configures [HttpRequest] with AWS chunked streaming to calculate checksum during transmission
18+
* @return [HttpRequest]
19+
*/
20+
internal fun calculateAwsChunkedStreamingChecksum(
21+
context: ProtocolRequestInterceptorContext<Any, HttpRequest>,
22+
checksumAlgorithm: HashFunction,
23+
): HttpRequest {
24+
val request = context.protocolRequest.toBuilder()
25+
val deferredChecksum = CompletableDeferred<String>(context.executionContext.coroutineContext.job)
26+
val checksumHeader = checksumAlgorithm.resolveChecksumAlgorithmHeaderName()
27+
28+
request.body = request.body
29+
.toHashingBody(checksumAlgorithm, request.body.contentLength)
30+
.toCompletingBody(deferredChecksum)
31+
32+
request.headers.append("x-amz-trailer", checksumHeader)
33+
request.trailingHeaders.append(checksumHeader, deferredChecksum)
34+
35+
context.executionContext.emitBusinessMetric(checksumAlgorithm.toBusinessMetric())
36+
37+
return request.build()
38+
}
39+
40+
/**
41+
* @return The default checksum algorithm name in the execution context, null if default checksums are disabled.
42+
*/
43+
internal val ProtocolRequestInterceptorContext<Any, HttpRequest>.defaultChecksumAlgorithmName: String?
44+
get() = executionContext.getOrNull(HttpOperationContext.DefaultChecksumAlgorithm)

0 commit comments

Comments
 (0)