From 763c97247b5abe743f897ca9c86e9993c3c33ae2 Mon Sep 17 00:00:00 2001 From: Aaron J Todd Date: Mon, 15 Nov 2021 14:18:57 -0500 Subject: [PATCH 1/7] refactor middleware to conform to new interfaces --- .../kotlin/runtime/config/imds/ImdsClient.kt | 35 ++--- .../runtime/config/imds/TokenMiddleware.kt | 36 ++--- .../http/middleware/ResolveAwsEndpoint.kt | 69 ++++----- .../runtime/http/middleware/UserAgent.kt | 54 +++---- .../http/middleware/ResolveAwsEndpointTest.kt | 12 +- .../runtime/http/middleware/UserAgentTest.kt | 13 +- .../auth/signing/AwsSigV4SigningMiddleware.kt | 142 ++++++++---------- .../signing/AwsSigv4SigningMiddlewareTest.kt | 10 +- .../runtime/protocol/json/AwsJsonProtocol.kt | 68 ++++----- .../protocol/json/AwsJsonProtocolTest.kt | 16 +- .../internal/ResolvePredictEndpoint.kt | 20 +-- 11 files changed, 194 insertions(+), 281 deletions(-) diff --git a/aws-runtime/aws-config/common/src/aws/sdk/kotlin/runtime/config/imds/ImdsClient.kt b/aws-runtime/aws-config/common/src/aws/sdk/kotlin/runtime/config/imds/ImdsClient.kt index e1737505450..355a258bb4b 100644 --- a/aws-runtime/aws-config/common/src/aws/sdk/kotlin/runtime/config/imds/ImdsClient.kt +++ b/aws-runtime/aws-config/common/src/aws/sdk/kotlin/runtime/config/imds/ImdsClient.kt @@ -81,26 +81,18 @@ public class ImdsClient private constructor(builder: Builder) : InstanceMetadata } // cached middleware instances - private val middleware: List = listOf( - ResolveEndpoint.create { - resolver = ImdsEndpointResolver(platformProvider, endpointConfiguration) - }, - UserAgent.create { - staticMetadata = AwsUserAgentMetadata.fromEnvironment(ApiMetadata(SERVICE, "unknown")) - }, - RetryFeature.create { - val tokenBucket = StandardRetryTokenBucket(StandardRetryTokenBucketOptions.Default) - val delayProvider = ExponentialBackoffWithJitter(ExponentialBackoffWithJitterOptions.Default) - strategy = StandardRetryStrategy(StandardRetryStrategyOptions.Default, tokenBucket, delayProvider) - policy = ImdsRetryPolicy() - }, - // must come after retries - TokenMiddleware.create { - httpClient = this@ImdsClient.httpClient - ttl = tokenTtl - clock = this@ImdsClient.clock - }, + private val resolveEndpointMiddleware = ResolveEndpoint(ImdsEndpointResolver(platformProvider, endpointConfiguration)) + private val userAgentMiddleware = UserAgent( + staticMetadata = AwsUserAgentMetadata.fromEnvironment(ApiMetadata(SERVICE, "unknown")) ) + private val retryMiddleware = run { + val tokenBucket = StandardRetryTokenBucket(StandardRetryTokenBucketOptions.Default) + val delayProvider = ExponentialBackoffWithJitter(ExponentialBackoffWithJitterOptions.Default) + val strategy = StandardRetryStrategy(StandardRetryStrategyOptions.Default, tokenBucket, delayProvider) + val policy = ImdsRetryPolicy() + RetryFeature(strategy, policy) + } + private val tokenMiddleware = TokenMiddleware(httpClient, tokenTtl, clock) public companion object { public operator fun invoke(block: Builder.() -> Unit): ImdsClient = ImdsClient(Builder().apply(block)) @@ -142,7 +134,10 @@ public class ImdsClient private constructor(builder: Builder) : InstanceMetadata set(SdkClientOption.LogMode, sdkLogMode) } } - middleware.forEach { it.install(op) } + op.install(resolveEndpointMiddleware) + op.install(userAgentMiddleware) + op.install(retryMiddleware) + op.install(tokenMiddleware) op.execution.mutate.intercept(Phase.Order.Before) { req, next -> req.subject.url.path = path next.call(req) diff --git a/aws-runtime/aws-config/common/src/aws/sdk/kotlin/runtime/config/imds/TokenMiddleware.kt b/aws-runtime/aws-config/common/src/aws/sdk/kotlin/runtime/config/imds/TokenMiddleware.kt index da1673baa40..a6f01a75a55 100644 --- a/aws-runtime/aws-config/common/src/aws/sdk/kotlin/runtime/config/imds/TokenMiddleware.kt +++ b/aws-runtime/aws-config/common/src/aws/sdk/kotlin/runtime/config/imds/TokenMiddleware.kt @@ -8,6 +8,7 @@ package aws.sdk.kotlin.runtime.config.imds import aws.sdk.kotlin.runtime.config.CachedValue import aws.sdk.kotlin.runtime.config.ExpiringValue import aws.smithy.kotlin.runtime.http.* +import aws.smithy.kotlin.runtime.http.operation.ModifyRequestMiddleware import aws.smithy.kotlin.runtime.http.operation.SdkHttpOperation import aws.smithy.kotlin.runtime.http.operation.SdkHttpRequest import aws.smithy.kotlin.runtime.http.operation.getLogger @@ -29,33 +30,21 @@ internal const val X_AWS_EC2_METADATA_TOKEN_TTL_SECONDS = "x-aws-ec2-metadata-to internal const val X_AWS_EC2_METADATA_TOKEN = "x-aws-ec2-metadata-token" @OptIn(ExperimentalTime::class) -internal class TokenMiddleware(config: Config) : Feature { - private val ttl: Duration = config.ttl - private val httpClient = requireNotNull(config.httpClient) { "SdkHttpClient is required for token middleware to make requests" } - private val clock: Clock = config.clock +internal class TokenMiddleware( + private val httpClient: SdkHttpClient, + private val ttl: Duration = Duration.seconds(DEFAULT_TOKEN_TTL_SECONDS), + private val clock: Clock = Clock.System +) : ModifyRequestMiddleware { private var cachedToken = CachedValue(null, bufferTime = Duration.seconds(TOKEN_REFRESH_BUFFER_SECONDS), clock = clock) - public class Config { - var ttl: Duration = Duration.seconds(DEFAULT_TOKEN_TTL_SECONDS) - var httpClient: SdkHttpClient? = null - var clock: Clock = Clock.System + override fun install(op: SdkHttpOperation<*, *>) { + op.execution.finalize.register(this) } - public companion object Feature : - HttpClientFeatureFactory { - override val key: FeatureKey = FeatureKey("EC2Metadata_Token_Middleware") - override fun create(block: Config.() -> Unit): TokenMiddleware { - val config = Config().apply(block) - return TokenMiddleware(config) - } - } - - override fun install(operation: SdkHttpOperation) { - operation.execution.finalize.intercept { req, next -> - val token = cachedToken.getOrLoad { getToken(clock, req).let { ExpiringValue(it, it.expires) } } - req.subject.headers.append(X_AWS_EC2_METADATA_TOKEN, token.value.decodeToString()) - next.call(req) - } + override suspend fun modifyRequest(req: SdkHttpRequest): SdkHttpRequest { + val token = cachedToken.getOrLoad { getToken(clock, req).let { ExpiringValue(it, it.expires) } } + req.subject.headers.append(X_AWS_EC2_METADATA_TOKEN, token.value.decodeToString()) + return req } private suspend fun getToken(clock: Clock, req: SdkHttpRequest): Token { @@ -76,7 +65,6 @@ internal class TokenMiddleware(config: Config) : Feature { } } - // TODO - retries with custom policy around 400 and 403 val call = httpClient.call(tokenReq) return try { when (call.response.status) { diff --git a/aws-runtime/aws-http/common/src/aws/sdk/kotlin/runtime/http/middleware/ResolveAwsEndpoint.kt b/aws-runtime/aws-http/common/src/aws/sdk/kotlin/runtime/http/middleware/ResolveAwsEndpoint.kt index 1fa77c3f674..16372a6cdf1 100644 --- a/aws-runtime/aws-http/common/src/aws/sdk/kotlin/runtime/http/middleware/ResolveAwsEndpoint.kt +++ b/aws-runtime/aws-http/common/src/aws/sdk/kotlin/runtime/http/middleware/ResolveAwsEndpoint.kt @@ -11,7 +11,9 @@ import aws.sdk.kotlin.runtime.endpoint.AwsEndpointResolver import aws.sdk.kotlin.runtime.execution.AuthAttributes import aws.smithy.kotlin.runtime.http.* import aws.smithy.kotlin.runtime.http.middleware.setRequestEndpoint +import aws.smithy.kotlin.runtime.http.operation.ModifyRequestMiddleware import aws.smithy.kotlin.runtime.http.operation.SdkHttpOperation +import aws.smithy.kotlin.runtime.http.operation.SdkHttpRequest import aws.smithy.kotlin.runtime.http.operation.getLogger import aws.smithy.kotlin.runtime.util.get @@ -20,54 +22,39 @@ import aws.smithy.kotlin.runtime.util.get */ @InternalSdkApi public class ResolveAwsEndpoint( - config: Config -) : Feature { + /** + * The AWS service ID to resolve endpoints for + */ + private val serviceId: String, - private val serviceId: String = requireNotNull(config.serviceId) { "ServiceId must not be null" } - private val resolver: AwsEndpointResolver = requireNotNull(config.resolver) { "EndpointResolver must not be null" } + /** + * The resolver to use + */ + private val resolver: AwsEndpointResolver - public class Config { - /** - * The AWS service ID to resolve endpoints for - */ - public var serviceId: String? = null +) : ModifyRequestMiddleware { - /** - * The resolver to use - */ - public var resolver: AwsEndpointResolver? = null + override fun install(op: SdkHttpOperation<*, *>) { + op.execution.mutate.register(this) } - public companion object Feature : HttpClientFeatureFactory { - override val key: FeatureKey = FeatureKey("ServiceEndpointResolver") + override suspend fun modifyRequest(req: SdkHttpRequest): SdkHttpRequest { + val region = req.context[AwsClientOption.Region] + val endpoint = resolver.resolve(serviceId, region) + setRequestEndpoint(req, endpoint.endpoint) - override fun create(block: Config.() -> Unit): ResolveAwsEndpoint { - val config = Config().apply(block) - return ResolveAwsEndpoint(config) - } - } - - override fun install(operation: SdkHttpOperation) { - operation.execution.mutate.intercept { req, next -> - - val region = req.context[AwsClientOption.Region] - val endpoint = resolver.resolve(serviceId, region) - setRequestEndpoint(req, endpoint.endpoint) - - endpoint.credentialScope?.let { scope -> - // resolved endpoint has credential scope override(s), update the context for downstream consumers - scope.service?.let { - if (it.isNotBlank()) req.context[AuthAttributes.SigningService] = it - } - scope.region?.let { - if (it.isNotBlank()) req.context[AuthAttributes.SigningRegion] = it - } + endpoint.credentialScope?.let { scope -> + // resolved endpoint has credential scope override(s), update the context for downstream consumers + scope.service?.let { + if (it.isNotBlank()) req.context[AuthAttributes.SigningService] = it + } + scope.region?.let { + if (it.isNotBlank()) req.context[AuthAttributes.SigningRegion] = it } - - val logger = req.context.getLogger("ResolveAwsEndpoint") - logger.debug { "resolved endpoint: $endpoint" } - - next.call(req) } + + val logger = req.context.getLogger("ResolveAwsEndpoint") + logger.debug { "resolved endpoint: $endpoint" } + return req } } diff --git a/aws-runtime/aws-http/common/src/aws/sdk/kotlin/runtime/http/middleware/UserAgent.kt b/aws-runtime/aws-http/common/src/aws/sdk/kotlin/runtime/http/middleware/UserAgent.kt index 160f6e6423b..d83a37be0aa 100644 --- a/aws-runtime/aws-http/common/src/aws/sdk/kotlin/runtime/http/middleware/UserAgent.kt +++ b/aws-runtime/aws-http/common/src/aws/sdk/kotlin/runtime/http/middleware/UserAgent.kt @@ -8,10 +8,9 @@ package aws.sdk.kotlin.runtime.http.middleware import aws.sdk.kotlin.runtime.InternalSdkApi import aws.sdk.kotlin.runtime.http.AwsUserAgentMetadata import aws.sdk.kotlin.runtime.http.operation.CustomUserAgentMetadata -import aws.smithy.kotlin.runtime.http.Feature -import aws.smithy.kotlin.runtime.http.FeatureKey -import aws.smithy.kotlin.runtime.http.HttpClientFeatureFactory +import aws.smithy.kotlin.runtime.http.operation.ModifyRequestMiddleware import aws.smithy.kotlin.runtime.http.operation.SdkHttpOperation +import aws.smithy.kotlin.runtime.http.operation.SdkHttpRequest import aws.smithy.kotlin.runtime.io.middleware.Phase internal const val X_AMZ_USER_AGENT: String = "x-amz-user-agent" @@ -22,43 +21,30 @@ internal const val USER_AGENT: String = "User-Agent" */ @InternalSdkApi public class UserAgent( + /** + * Metadata that doesn't change per/request (e.g. sdk and environment related metadata) + */ private val staticMetadata: AwsUserAgentMetadata -) : Feature { +) : ModifyRequestMiddleware { - public class Config { - /** - * Metadata that doesn't change per/request (e.g. sdk and environment related metadata) - */ - public var staticMetadata: AwsUserAgentMetadata? = null + override fun install(op: SdkHttpOperation<*, *>) { + op.execution.mutate.register(this, Phase.Order.After) } - public companion object Feature : - HttpClientFeatureFactory { - override val key: FeatureKey = FeatureKey("UserAgent") + override suspend fun modifyRequest(req: SdkHttpRequest): SdkHttpRequest { + // pull dynamic values out of the context + val customMetadata = req.context.getOrNull(CustomUserAgentMetadata.ContextKey) - override fun create(block: Config.() -> Unit): UserAgent { - val config = Config().apply(block) - val metadata = requireNotNull(config.staticMetadata) { "staticMetadata is required" } - return UserAgent(metadata) - } - } - - override fun install(operation: SdkHttpOperation) { - operation.execution.mutate.intercept(Phase.Order.After) { req, next -> - - // pull dynamic values out of the context - val customMetadata = req.context.getOrNull(CustomUserAgentMetadata.ContextKey) + // resolve the metadata for the request which is a combination of the static and per/operation metadata + val requestMetadata = staticMetadata.copy(customMetadata = customMetadata) - // resolve the metadata for the request which is a combination of the static and per/operation metadata - val requestMetadata = staticMetadata.copy(customMetadata = customMetadata) + // NOTE: Due to legacy issues with processing the user agent, the original content for + // x-amz-user-agent and User-Agent is swapped here. See top note in the + // sdk-user-agent-header SEP and https://github.com/awslabs/smithy-kotlin/issues/373 + // for further details. + req.subject.headers[USER_AGENT] = requestMetadata.xAmzUserAgent + req.subject.headers[X_AMZ_USER_AGENT] = requestMetadata.userAgent - // NOTE: Due to legacy issues with processing the user agent, the original content for - // x-amz-user-agent and User-Agent is swapped here. See top note in the - // sdk-user-agent-header SEP and https://github.com/awslabs/smithy-kotlin/issues/373 - // for further details. - req.subject.headers[USER_AGENT] = requestMetadata.xAmzUserAgent - req.subject.headers[X_AMZ_USER_AGENT] = requestMetadata.userAgent - next.call(req) - } + return req } } diff --git a/aws-runtime/aws-http/common/test/aws/sdk/kotlin/runtime/http/middleware/ResolveAwsEndpointTest.kt b/aws-runtime/aws-http/common/test/aws/sdk/kotlin/runtime/http/middleware/ResolveAwsEndpointTest.kt index 9644d8bdd60..fc1a4446e11 100644 --- a/aws-runtime/aws-http/common/test/aws/sdk/kotlin/runtime/http/middleware/ResolveAwsEndpointTest.kt +++ b/aws-runtime/aws-http/common/test/aws/sdk/kotlin/runtime/http/middleware/ResolveAwsEndpointTest.kt @@ -48,10 +48,8 @@ class ResolveAwsEndpointTest { } val endpoint = AwsEndpoint("https://api.test.com") - op.install(ResolveAwsEndpoint) { - resolver = AwsEndpointResolver { _, _ -> endpoint } - serviceId = "TestService" - } + val resolver = AwsEndpointResolver { _, _ -> endpoint } + op.install(ResolveAwsEndpoint("TestService", resolver)) op.roundTrip(client, Unit) val actual = op.context[HttpOperationContext.HttpCallList].first().request @@ -78,10 +76,8 @@ class ResolveAwsEndpointTest { } val endpoint = AwsEndpoint("https://api.test.com", CredentialScope("us-west-2", "foo")) - op.install(ResolveAwsEndpoint) { - resolver = AwsEndpointResolver { _, _ -> endpoint } - serviceId = "TestService" - } + val resolver = AwsEndpointResolver { _, _ -> endpoint } + op.install(ResolveAwsEndpoint("TestService", resolver)) op.roundTrip(client, Unit) val actual = op.context[HttpOperationContext.HttpCallList].first().request diff --git a/aws-runtime/aws-http/common/test/aws/sdk/kotlin/runtime/http/middleware/UserAgentTest.kt b/aws-runtime/aws-http/common/test/aws/sdk/kotlin/runtime/http/middleware/UserAgentTest.kt index 0459462854c..2306ddf915e 100644 --- a/aws-runtime/aws-http/common/test/aws/sdk/kotlin/runtime/http/middleware/UserAgentTest.kt +++ b/aws-runtime/aws-http/common/test/aws/sdk/kotlin/runtime/http/middleware/UserAgentTest.kt @@ -50,9 +50,8 @@ class UserAgentTest { } val provider = TestPlatformProvider() - op.install(UserAgent) { - staticMetadata = loadAwsUserAgentMetadataFromEnvironment(provider, ApiMetadata("Test Service", "1.2.3")) - } + val metadata = loadAwsUserAgentMetadataFromEnvironment(provider, ApiMetadata("Test Service", "1.2.3")) + op.install(UserAgent(metadata)) op.roundTrip(client, Unit) val request = op.context[HttpOperationContext.HttpCallList].last().request @@ -75,9 +74,7 @@ class UserAgentTest { val provider = TestPlatformProvider() val staticMeta = loadAwsUserAgentMetadataFromEnvironment(provider, ApiMetadata("Test Service", "1.2.3")) - op.install(UserAgent) { - staticMetadata = staticMeta - } + op.install(UserAgent(staticMeta)) op.context.customUserAgentMetadata.add("foo", "bar") @@ -96,9 +93,7 @@ class UserAgentTest { } } - op2.install(UserAgent) { - staticMetadata = staticMeta - } + op2.install(UserAgent(staticMeta)) op2.context.customUserAgentMetadata.add("baz", "quux") diff --git a/aws-runtime/aws-signing/common/src/aws/sdk/kotlin/runtime/auth/signing/AwsSigV4SigningMiddleware.kt b/aws-runtime/aws-signing/common/src/aws/sdk/kotlin/runtime/auth/signing/AwsSigV4SigningMiddleware.kt index 169b1b825bf..9d01c9e9cf1 100644 --- a/aws-runtime/aws-signing/common/src/aws/sdk/kotlin/runtime/auth/signing/AwsSigV4SigningMiddleware.kt +++ b/aws-runtime/aws-signing/common/src/aws/sdk/kotlin/runtime/auth/signing/AwsSigV4SigningMiddleware.kt @@ -14,16 +14,18 @@ import aws.sdk.kotlin.runtime.crt.update import aws.sdk.kotlin.runtime.execution.AuthAttributes import aws.smithy.kotlin.runtime.client.ExecutionContext import aws.smithy.kotlin.runtime.http.* -import aws.smithy.kotlin.runtime.http.operation.SdkHttpOperation -import aws.smithy.kotlin.runtime.http.operation.withContext -import aws.smithy.kotlin.runtime.logging.Logger +import aws.smithy.kotlin.runtime.http.operation.* import aws.smithy.kotlin.runtime.util.get /** * HTTP request pipeline middleware that signs outgoing requests */ @InternalSdkApi -public class AwsSigV4SigningMiddleware internal constructor(private val config: Config) : Feature { +public class AwsSigV4SigningMiddleware(private val config: Config) : ModifyRequestMiddleware { + + public companion object { + public inline operator fun invoke(block: Config.() -> Unit): AwsSigV4SigningMiddleware = AwsSigV4SigningMiddleware(Config().apply(block)) + } public class Config { /** @@ -73,85 +75,71 @@ public class AwsSigV4SigningMiddleware internal constructor(private val config: public var signedBodyHeaderType: AwsSignedBodyHeaderType = AwsSignedBodyHeaderType.NONE } - public companion object Feature : HttpClientFeatureFactory { - private val logger = Logger.getLogger() - - override val key: FeatureKey = FeatureKey("AwsSigv4SigningMiddleware") - - override fun create(block: Config.() -> Unit): AwsSigV4SigningMiddleware { - val config = Config().apply(block) - - requireNotNull(config.credentialsProvider) { "AwsSigv4SigningMiddleware requires a credentialsProvider" } - requireNotNull(config.signingService) { "AwsSigv4SigningMiddleware requires a signing service" } - - return AwsSigV4SigningMiddleware(config) - } + override fun install(op: SdkHttpOperation<*, *>) { + op.execution.finalize.register(this) } - override fun install(operation: SdkHttpOperation) { - operation.execution.finalize.intercept { req, next -> - - val credentialsProvider = checkNotNull(config.credentialsProvider) - val resolvedCredentials = credentialsProvider.getCredentials() - val logger: Logger by lazy { logger.withContext(req.context) } - - val isUnsignedRequest = req.context.isUnsignedRequest() - // FIXME - an alternative here would be to just pre-compute the sha256 of the payload ourselves and set - // the signed body value on the signing config. This would prevent needing to launch a coroutine - // for streaming requests since we already have a suspend context. - val signableRequest = req.subject.toSignableCrtRequest(isUnsignedRequest) - - // SDKs are supposed to default to signed payload _always_ when possible (and when `unsignedPayload` trait isn't present). - // - // There are a few escape hatches/special cases: - // 1. Customer explicitly disables signed payload (via AuthAttributes.UnsignedPayload) - // 2. Customer provides a (potentially) unbounded stream (via HttpBody.Streaming) - // - // When an unbounded stream (2) is given we proceed as follows: - // 2.1. is it replayable? - // (2.1.1) yes -> sign the payload (stream can be consumed more than once) - // (2.1.2) no -> unsigned payload - // - // NOTE: Chunked signing is NOT enabled through this middleware. - // NOTE: 2.1.2 is handled below - - // FIXME - see: https://github.com/awslabs/smithy-kotlin/issues/296 - // if we know we have a (streaming) body and toSignableRequest() fails to convert it to a CRT equivalent - // then we must decide how to compute the payload hash ourselves (defaults to unsigned payload) - val isUnboundedStream = signableRequest.body == null && req.subject.body is HttpBody.Streaming - - // operation signing config is baseConfig + operation specific config/overrides - val opSigningConfig = AwsSigningConfig { - region = req.context[AuthAttributes.SigningRegion] - service = req.context.getOrNull(AuthAttributes.SigningService) ?: checkNotNull(config.signingService) - credentials = resolvedCredentials - algorithm = config.algorithm - date = req.context.getOrNull(AuthAttributes.SigningDate) - - signatureType = config.signatureType - omitSessionToken = config.omitSessionToken - normalizeUriPath = config.normalizeUriPath - useDoubleUriEncode = config.useDoubleUriEncode - - signedBodyHeader = config.signedBodyHeaderType - signedBodyValue = when { - isUnsignedRequest -> AwsSignedBodyValue.UNSIGNED_PAYLOAD - req.subject.body is HttpBody.Empty -> AwsSignedBodyValue.EMPTY_SHA256 - isUnboundedStream -> { - logger.warn { "unable to compute hash for unbounded stream; defaulting to unsigned payload" } - AwsSignedBodyValue.UNSIGNED_PAYLOAD - } - // use the payload to compute the hash - else -> null + override suspend fun modifyRequest(req: SdkHttpRequest): SdkHttpRequest { + val credentialsProvider = checkNotNull(config.credentialsProvider) + val resolvedCredentials = credentialsProvider.getCredentials() + val logger = req.context.getLogger("AwsSigv4SigningMiddleware") + + val isUnsignedRequest = req.context.isUnsignedRequest() + // FIXME - an alternative here would be to just pre-compute the sha256 of the payload ourselves and set + // the signed body value on the signing config. This would prevent needing to launch a coroutine + // for streaming requests since we already have a suspend context. + val signableRequest = req.subject.toSignableCrtRequest(isUnsignedRequest) + + // SDKs are supposed to default to signed payload _always_ when possible (and when `unsignedPayload` trait isn't present). + // + // There are a few escape hatches/special cases: + // 1. Customer explicitly disables signed payload (via AuthAttributes.UnsignedPayload) + // 2. Customer provides a (potentially) unbounded stream (via HttpBody.Streaming) + // + // When an unbounded stream (2) is given we proceed as follows: + // 2.1. is it replayable? + // (2.1.1) yes -> sign the payload (stream can be consumed more than once) + // (2.1.2) no -> unsigned payload + // + // NOTE: Chunked signing is NOT enabled through this middleware. + // NOTE: 2.1.2 is handled below + + // FIXME - see: https://github.com/awslabs/smithy-kotlin/issues/296 + // if we know we have a (streaming) body and toSignableRequest() fails to convert it to a CRT equivalent + // then we must decide how to compute the payload hash ourselves (defaults to unsigned payload) + val isUnboundedStream = signableRequest.body == null && req.subject.body is HttpBody.Streaming + + // operation signing config is baseConfig + operation specific config/overrides + val opSigningConfig = AwsSigningConfig { + region = req.context[AuthAttributes.SigningRegion] + service = req.context.getOrNull(AuthAttributes.SigningService) ?: checkNotNull(config.signingService) + credentials = resolvedCredentials + algorithm = config.algorithm + date = req.context.getOrNull(AuthAttributes.SigningDate) + + signatureType = config.signatureType + omitSessionToken = config.omitSessionToken + normalizeUriPath = config.normalizeUriPath + useDoubleUriEncode = config.useDoubleUriEncode + + signedBodyHeader = config.signedBodyHeaderType + signedBodyValue = when { + isUnsignedRequest -> AwsSignedBodyValue.UNSIGNED_PAYLOAD + req.subject.body is HttpBody.Empty -> AwsSignedBodyValue.EMPTY_SHA256 + isUnboundedStream -> { + logger.warn { "unable to compute hash for unbounded stream; defaulting to unsigned payload" } + AwsSignedBodyValue.UNSIGNED_PAYLOAD } + // use the payload to compute the hash + else -> null } + } - val signedRequest = AwsSigner.signRequest(signableRequest, opSigningConfig.toCrt()) - req.subject.update(signedRequest) - req.subject.body.resetStream() + val signedRequest = AwsSigner.signRequest(signableRequest, opSigningConfig.toCrt()) + req.subject.update(signedRequest) + req.subject.body.resetStream() - next.call(req) - } + return req } } diff --git a/aws-runtime/aws-signing/common/test/aws/sdk/kotlin/runtime/auth/signing/AwsSigv4SigningMiddlewareTest.kt b/aws-runtime/aws-signing/common/test/aws/sdk/kotlin/runtime/auth/signing/AwsSigv4SigningMiddlewareTest.kt index 4612c55ee5f..8881e0b2fd8 100644 --- a/aws-runtime/aws-signing/common/test/aws/sdk/kotlin/runtime/auth/signing/AwsSigv4SigningMiddlewareTest.kt +++ b/aws-runtime/aws-signing/common/test/aws/sdk/kotlin/runtime/auth/signing/AwsSigv4SigningMiddlewareTest.kt @@ -67,10 +67,12 @@ class AwsSigv4SigningMiddlewareTest { } val client = sdkHttpClient(mockEngine) - operation.install(AwsSigV4SigningMiddleware) { - credentialsProvider = TestCredentialsProvider - signingService = "demo" - } + operation.install( + AwsSigV4SigningMiddleware { + credentialsProvider = TestCredentialsProvider + signingService = "demo" + } + ) operation.roundTrip(client, Unit) return operation.context[HttpOperationContext.HttpCallList].last().request diff --git a/aws-runtime/protocols/aws-json-protocols/common/src/aws/sdk/kotlin/runtime/protocol/json/AwsJsonProtocol.kt b/aws-runtime/protocols/aws-json-protocols/common/src/aws/sdk/kotlin/runtime/protocol/json/AwsJsonProtocol.kt index f18440601f3..23d4247a749 100644 --- a/aws-runtime/protocols/aws-json-protocols/common/src/aws/sdk/kotlin/runtime/protocol/json/AwsJsonProtocol.kt +++ b/aws-runtime/protocols/aws-json-protocols/common/src/aws/sdk/kotlin/runtime/protocol/json/AwsJsonProtocol.kt @@ -8,7 +8,9 @@ import aws.sdk.kotlin.runtime.InternalSdkApi import aws.smithy.kotlin.runtime.client.SdkClientOption import aws.smithy.kotlin.runtime.http.* import aws.smithy.kotlin.runtime.http.content.ByteArrayContent +import aws.smithy.kotlin.runtime.http.operation.ModifyRequestMiddleware import aws.smithy.kotlin.runtime.http.operation.SdkHttpOperation +import aws.smithy.kotlin.runtime.http.operation.SdkHttpRequest import aws.smithy.kotlin.runtime.util.get /** @@ -21,49 +23,37 @@ import aws.smithy.kotlin.runtime.util.get * - providing an empty json {} body when no body is serialized */ @InternalSdkApi -public class AwsJsonProtocol(config: Config) : Feature { - private val serviceShapeName: String = requireNotNull(config.serviceShapeName) { "AWS JSON protocol service name must be specified" } - private val version: String = requireNotNull(config.version) { "AWS JSON protocol version must be specified" } - - public class Config { - /** - * The original service (shape) name - */ - public var serviceShapeName: String? = null - - /** - * The protocol version e.g. "1.0" - */ - public var version: String? = null +public class AwsJsonProtocol( + /** + * The original service (shape) name + */ + private val serviceShapeName: String, + + /** + * The protocol version e.g. "1.0" + */ + private val version: String +) : ModifyRequestMiddleware { + + override fun install(op: SdkHttpOperation<*, *>) { + op.execution.mutate.register(this) } - public companion object Feature : HttpClientFeatureFactory { - override val key: FeatureKey = FeatureKey("AwsJsonProtocol") - - override fun create(block: Config.() -> Unit): AwsJsonProtocol { - val config = Config().apply(block) - return AwsJsonProtocol(config) - } - } - - override fun install(operation: SdkHttpOperation) { - operation.execution.mutate.intercept { req, next -> - val context = req.context - // required context elements - val operationName = context[SdkClientOption.OperationName] - - // see: https://awslabs.github.io/smithy/1.0/spec/aws/aws-json-1_0-protocol.html#protocol-behaviors - req.subject.headers.append("X-Amz-Target", "$serviceShapeName.$operationName") - req.subject.headers.setMissing("Content-Type", "application/x-amz-json-$version") + override suspend fun modifyRequest(req: SdkHttpRequest): SdkHttpRequest { + val context = req.context + // required context elements + val operationName = context[SdkClientOption.OperationName] - if (req.subject.body is HttpBody.Empty) { - // Empty body is required by AWS JSON 1.x protocols - // https://awslabs.github.io/smithy/1.0/spec/aws/aws-json-1_0-protocol.html#empty-body-serialization - // https://awslabs.github.io/smithy/1.0/spec/aws/aws-json-1_1-protocol.html#empty-body-serialization - req.subject.body = ByteArrayContent("{}".encodeToByteArray()) - } + // see: https://awslabs.github.io/smithy/1.0/spec/aws/aws-json-1_0-protocol.html#protocol-behaviors + req.subject.headers.append("X-Amz-Target", "$serviceShapeName.$operationName") + req.subject.headers.setMissing("Content-Type", "application/x-amz-json-$version") - next.call(req) + if (req.subject.body is HttpBody.Empty) { + // Empty body is required by AWS JSON 1.x protocols + // https://awslabs.github.io/smithy/1.0/spec/aws/aws-json-1_0-protocol.html#empty-body-serialization + // https://awslabs.github.io/smithy/1.0/spec/aws/aws-json-1_1-protocol.html#empty-body-serialization + req.subject.body = ByteArrayContent("{}".encodeToByteArray()) } + return req } } diff --git a/aws-runtime/protocols/aws-json-protocols/common/test/aws/sdk/kotlin/runtime/protocol/json/AwsJsonProtocolTest.kt b/aws-runtime/protocols/aws-json-protocols/common/test/aws/sdk/kotlin/runtime/protocol/json/AwsJsonProtocolTest.kt index 6b2dfb840be..997dc783014 100644 --- a/aws-runtime/protocols/aws-json-protocols/common/test/aws/sdk/kotlin/runtime/protocol/json/AwsJsonProtocolTest.kt +++ b/aws-runtime/protocols/aws-json-protocols/common/test/aws/sdk/kotlin/runtime/protocol/json/AwsJsonProtocolTest.kt @@ -41,10 +41,8 @@ class AwsJsonProtocolTest { } } val client = sdkHttpClient(mockEngine) - op.install(AwsJsonProtocol) { - serviceShapeName = "FooService_blah" - version = "1.1" - } + val m = AwsJsonProtocol("FooService_blah", "1.1") + op.install(m) op.roundTrip(client, Unit) val request = op.context[HttpOperationContext.HttpCallList].last().request @@ -74,10 +72,7 @@ class AwsJsonProtocolTest { } } val client = sdkHttpClient(mockEngine) - op.install(AwsJsonProtocol) { - serviceShapeName = "FooService" - version = "1.1" - } + op.install(AwsJsonProtocol("FooService", "1.1")) op.roundTrip(client, Unit) val request = op.context[HttpOperationContext.HttpCallList].last().request @@ -111,10 +106,7 @@ class AwsJsonProtocolTest { } } val client = sdkHttpClient(mockEngine) - op.install(AwsJsonProtocol) { - serviceShapeName = "FooService" - version = "1.1" - } + op.install(AwsJsonProtocol("FooService", "1.1")) op.roundTrip(client, Unit) val request = op.context[HttpOperationContext.HttpCallList].last().request diff --git a/services/machinelearning/common/src/aws/sdk/kotlin/services/machinelearning/internal/ResolvePredictEndpoint.kt b/services/machinelearning/common/src/aws/sdk/kotlin/services/machinelearning/internal/ResolvePredictEndpoint.kt index a14e4f120fc..9b24c796a71 100644 --- a/services/machinelearning/common/src/aws/sdk/kotlin/services/machinelearning/internal/ResolvePredictEndpoint.kt +++ b/services/machinelearning/common/src/aws/sdk/kotlin/services/machinelearning/internal/ResolvePredictEndpoint.kt @@ -8,21 +8,15 @@ package aws.sdk.kotlin.services.machinelearning.internal import aws.sdk.kotlin.runtime.endpoint.AwsEndpoint import aws.sdk.kotlin.services.machinelearning.model.MachineLearningException import aws.sdk.kotlin.services.machinelearning.model.PredictRequest -import aws.smithy.kotlin.runtime.http.Feature -import aws.smithy.kotlin.runtime.http.FeatureKey -import aws.smithy.kotlin.runtime.http.HttpClientFeatureFactory +import aws.sdk.kotlin.services.machinelearning.model.PredictResponse import aws.smithy.kotlin.runtime.http.middleware.setRequestEndpoint +import aws.smithy.kotlin.runtime.http.operation.AutoInstall import aws.smithy.kotlin.runtime.http.operation.SdkHttpOperation -internal class ResolvePredictEndpoint : Feature { - companion object Feature : HttpClientFeatureFactory { - override val key: FeatureKey = FeatureKey("ResolvePredictEndpoint") - override fun create(block: Unit.() -> Unit): ResolvePredictEndpoint = ResolvePredictEndpoint() - } - - override fun install(operation: SdkHttpOperation) { - operation.execution.initialize.intercept { req, next -> - val input = req.subject as PredictRequest +internal class ResolvePredictEndpoint : AutoInstall { + override fun install(op: SdkHttpOperation) { + op.execution.initialize.intercept { req, next -> + val input = req.subject if (input.predictEndpoint == null || input.predictEndpoint.isBlank()) { throw MachineLearningException("Predict requires predictEnpoint to be set to a non-empty value") } @@ -32,7 +26,7 @@ internal class ResolvePredictEndpoint : Feature { next.call(req) } - operation.execution.mutate.intercept { req, next -> + op.execution.mutate.intercept { req, next -> // This should've been set by the initialize interceptor val endpoint = req.context.predictEndpoint requireNotNull(endpoint) { "Predict endpoint wasn't set by middleware." } From 0eea879344351738ee684ada491beb62e7e2d867 Mon Sep 17 00:00:00 2001 From: Aaron J Todd Date: Mon, 15 Nov 2021 15:24:06 -0500 Subject: [PATCH 2/7] refactor middleware codegen --- .../codegen/AwsDefaultRetryIntegration.kt | 18 ++++--- .../glacier/GlacierBodyChecksum.kt | 13 ++--- .../MachineLearningEndpointCustomization.kt | 10 ++-- .../customization/s3/S3SigningConfig.kt | 5 +- .../json/AwsJsonProtocolMiddleware.kt | 11 +++-- .../middleware/AwsSignatureVersion4.kt | 15 ++++-- .../middleware/MutateHeadersMiddleware.kt | 29 ++++++----- .../ResolveAwsEndpointMiddleware.kt | 11 ++--- .../middleware/UserAgentMiddleware.kt | 14 +++--- .../glacier/internal/GlacierBodyChecksum.kt | 48 +++++++------------ 10 files changed, 90 insertions(+), 84 deletions(-) diff --git a/codegen/smithy-aws-kotlin-codegen/src/main/kotlin/aws/sdk/kotlin/codegen/AwsDefaultRetryIntegration.kt b/codegen/smithy-aws-kotlin-codegen/src/main/kotlin/aws/sdk/kotlin/codegen/AwsDefaultRetryIntegration.kt index 741a1b092dd..8035e198540 100644 --- a/codegen/smithy-aws-kotlin-codegen/src/main/kotlin/aws/sdk/kotlin/codegen/AwsDefaultRetryIntegration.kt +++ b/codegen/smithy-aws-kotlin-codegen/src/main/kotlin/aws/sdk/kotlin/codegen/AwsDefaultRetryIntegration.kt @@ -8,11 +8,11 @@ package aws.sdk.kotlin.codegen import software.amazon.smithy.kotlin.codegen.core.KotlinWriter import software.amazon.smithy.kotlin.codegen.core.RuntimeTypes import software.amazon.smithy.kotlin.codegen.integration.KotlinIntegration -import software.amazon.smithy.kotlin.codegen.rendering.protocol.HttpFeatureMiddleware import software.amazon.smithy.kotlin.codegen.rendering.protocol.ProtocolGenerator import software.amazon.smithy.kotlin.codegen.rendering.protocol.ProtocolMiddleware import software.amazon.smithy.kotlin.codegen.rendering.protocol.replace import software.amazon.smithy.kotlin.codegen.retries.StandardRetryMiddleware +import software.amazon.smithy.model.shapes.OperationShape /** * Adds AWS-specific retry wrappers around operation invocations. This replaces @@ -24,16 +24,14 @@ class AwsDefaultRetryIntegration : KotlinIntegration { ctx: ProtocolGenerator.GenerationContext, resolved: List ): List = resolved.replace(middleware) { it.name == StandardRetryMiddleware.name } -} - -private val middleware = object : HttpFeatureMiddleware() { - override val name: String = "RetryFeature" - override fun renderConfigure(writer: KotlinWriter) { - writer.addImport(RuntimeTypes.Http.Middlware.RetryFeature) - writer.addImport(AwsRuntimeTypes.Http.Retries.AwsDefaultRetryPolicy) + private val middleware = object : ProtocolMiddleware { + override val name: String = "RetryFeature" - writer.write("strategy = config.retryStrategy") - writer.write("policy = AwsDefaultRetryPolicy") + override fun render(ctx: ProtocolGenerator.GenerationContext, op: OperationShape, writer: KotlinWriter) { + writer.addImport(RuntimeTypes.Http.Middlware.RetryFeature) + writer.addImport(AwsRuntimeTypes.Http.Retries.AwsDefaultRetryPolicy) + writer.write("op.install(#T(config.retryStrategy, AwsDefaultRetryPolicy))", RuntimeTypes.Http.Middlware.RetryFeature) + } } } diff --git a/codegen/smithy-aws-kotlin-codegen/src/main/kotlin/aws/sdk/kotlin/codegen/customization/glacier/GlacierBodyChecksum.kt b/codegen/smithy-aws-kotlin-codegen/src/main/kotlin/aws/sdk/kotlin/codegen/customization/glacier/GlacierBodyChecksum.kt index d8aaf3d38a3..950f0faa64e 100644 --- a/codegen/smithy-aws-kotlin-codegen/src/main/kotlin/aws/sdk/kotlin/codegen/customization/glacier/GlacierBodyChecksum.kt +++ b/codegen/smithy-aws-kotlin-codegen/src/main/kotlin/aws/sdk/kotlin/codegen/customization/glacier/GlacierBodyChecksum.kt @@ -13,7 +13,6 @@ import software.amazon.smithy.kotlin.codegen.integration.KotlinIntegration import software.amazon.smithy.kotlin.codegen.model.buildSymbol import software.amazon.smithy.kotlin.codegen.model.expectShape import software.amazon.smithy.kotlin.codegen.model.isStreaming -import software.amazon.smithy.kotlin.codegen.rendering.protocol.HttpFeatureMiddleware import software.amazon.smithy.kotlin.codegen.rendering.protocol.ProtocolGenerator import software.amazon.smithy.kotlin.codegen.rendering.protocol.ProtocolMiddleware import software.amazon.smithy.kotlin.codegen.utils.getOrNull @@ -31,7 +30,7 @@ public class GlacierBodyChecksum : KotlinIntegration { resolved: List, ): List = resolved + glacierBodyChecksumMiddleware - private val glacierBodyChecksumMiddleware = object : HttpFeatureMiddleware() { + private val glacierBodyChecksumMiddleware = object : ProtocolMiddleware { override val order: Byte = 127 // Must come after AwsSignatureVersion4 override val name: String = "GlacierBodyChecksum" @@ -40,13 +39,11 @@ public class GlacierBodyChecksum : KotlinIntegration { return input?.members()?.any { it.isStreaming || ctx.model.expectShape(it.target).isStreaming } == true } - override fun renderConfigure(writer: KotlinWriter) { + override fun render(ctx: ProtocolGenerator.GenerationContext, op: OperationShape, writer: KotlinWriter) { writer.addImport(RuntimeTypes.Utils.Sha256) - writer.addImport(glacierSymbol("GlacierBodyChecksum")) - writer.addImport(glacierSymbol("TreeHasherImpl")) - - writer.write("val chunkSizeBytes = 1024 * 1024 // 1MB") - writer.write("treeHasher = TreeHasherImpl(chunkSizeBytes) { Sha256() }") + val middleware = glacierSymbol("GlacierBodyChecksum") + writer.addImport(middleware) + writer.write("op.install(#T())", middleware) } private fun glacierSymbol(name: String) = buildSymbol { diff --git a/codegen/smithy-aws-kotlin-codegen/src/main/kotlin/aws/sdk/kotlin/codegen/customization/machinelearning/MachineLearningEndpointCustomization.kt b/codegen/smithy-aws-kotlin-codegen/src/main/kotlin/aws/sdk/kotlin/codegen/customization/machinelearning/MachineLearningEndpointCustomization.kt index e99e90596a6..0baf998bb03 100644 --- a/codegen/smithy-aws-kotlin-codegen/src/main/kotlin/aws/sdk/kotlin/codegen/customization/machinelearning/MachineLearningEndpointCustomization.kt +++ b/codegen/smithy-aws-kotlin-codegen/src/main/kotlin/aws/sdk/kotlin/codegen/customization/machinelearning/MachineLearningEndpointCustomization.kt @@ -17,6 +17,8 @@ import software.amazon.smithy.model.Model import software.amazon.smithy.model.shapes.OperationShape import software.amazon.smithy.model.shapes.ServiceShape +// FIXME - this is wrong, it's removing the default aws endpoint resolver middleware and only adding back a resolver if the +// protocol middleware is enabled... class MachineLearningEndpointCustomization : KotlinIntegration { override fun customizeMiddleware( ctx: ProtocolGenerator.GenerationContext, @@ -26,14 +28,16 @@ class MachineLearningEndpointCustomization : KotlinIntegration { .customizeMiddleware(ctx, resolved) .replace(endpointResolverMiddleware) { it is ResolveAwsEndpointMiddleware } - private val endpointResolverMiddleware = object : HttpFeatureMiddleware() { + private val endpointResolverMiddleware = object : ProtocolMiddleware { override val name: String = "ResolvePredictEndpoint" override fun isEnabledFor(ctx: ProtocolGenerator.GenerationContext, op: OperationShape): Boolean = op.id.name == "Predict" - override fun renderConfigure(writer: KotlinWriter) { - writer.addImport(machineLearningSymbol("ResolvePredictEndpoint")) + override fun render(ctx: ProtocolGenerator.GenerationContext, op: OperationShape, writer: KotlinWriter) { + val symbol = machineLearningSymbol("ResolvePredictEndpoint") + writer.addImport(symbol) + writer.write("op.install(#T())", symbol) } private fun machineLearningSymbol(name: String) = buildSymbol { diff --git a/codegen/smithy-aws-kotlin-codegen/src/main/kotlin/aws/sdk/kotlin/codegen/customization/s3/S3SigningConfig.kt b/codegen/smithy-aws-kotlin-codegen/src/main/kotlin/aws/sdk/kotlin/codegen/customization/s3/S3SigningConfig.kt index aa32f45f891..c9844842d84 100644 --- a/codegen/smithy-aws-kotlin-codegen/src/main/kotlin/aws/sdk/kotlin/codegen/customization/s3/S3SigningConfig.kt +++ b/codegen/smithy-aws-kotlin-codegen/src/main/kotlin/aws/sdk/kotlin/codegen/customization/s3/S3SigningConfig.kt @@ -15,6 +15,7 @@ import software.amazon.smithy.kotlin.codegen.rendering.protocol.ProtocolGenerato import software.amazon.smithy.kotlin.codegen.rendering.protocol.ProtocolMiddleware import software.amazon.smithy.kotlin.codegen.rendering.protocol.replace import software.amazon.smithy.model.Model +import software.amazon.smithy.model.shapes.OperationShape import software.amazon.smithy.model.shapes.ServiceShape /** @@ -41,8 +42,8 @@ class S3SigningConfig : KotlinIntegration { } private class S3SigningMiddleware(signingServiceName: String) : AwsSignatureVersion4(signingServiceName) { - override fun renderConfigure(writer: KotlinWriter) { - super.renderConfigure(writer) + override fun renderSigningConfig(op: OperationShape, writer: KotlinWriter) { + super.renderSigningConfig(op, writer) val sbht = AwsRuntimeTypes.Signing.AwsSignedBodyHeaderType writer.addImport(sbht) writer.write("signedBodyHeaderType = #T.X_AMZ_CONTENT_SHA256", sbht) diff --git a/codegen/smithy-aws-kotlin-codegen/src/main/kotlin/aws/sdk/kotlin/codegen/protocols/json/AwsJsonProtocolMiddleware.kt b/codegen/smithy-aws-kotlin-codegen/src/main/kotlin/aws/sdk/kotlin/codegen/protocols/json/AwsJsonProtocolMiddleware.kt index 89057f08b4f..68b026aa860 100644 --- a/codegen/smithy-aws-kotlin-codegen/src/main/kotlin/aws/sdk/kotlin/codegen/protocols/json/AwsJsonProtocolMiddleware.kt +++ b/codegen/smithy-aws-kotlin-codegen/src/main/kotlin/aws/sdk/kotlin/codegen/protocols/json/AwsJsonProtocolMiddleware.kt @@ -9,7 +9,9 @@ import aws.sdk.kotlin.codegen.AwsKotlinDependency import software.amazon.smithy.kotlin.codegen.core.KotlinWriter import software.amazon.smithy.kotlin.codegen.model.buildSymbol import software.amazon.smithy.kotlin.codegen.model.namespace -import software.amazon.smithy.kotlin.codegen.rendering.protocol.HttpFeatureMiddleware +import software.amazon.smithy.kotlin.codegen.rendering.protocol.ProtocolGenerator +import software.amazon.smithy.kotlin.codegen.rendering.protocol.ProtocolMiddleware +import software.amazon.smithy.model.shapes.OperationShape import software.amazon.smithy.model.shapes.ShapeId /** @@ -19,18 +21,17 @@ import software.amazon.smithy.model.shapes.ShapeId class AwsJsonProtocolMiddleware( private val serviceShapeId: ShapeId, private val protocolVersion: String -) : HttpFeatureMiddleware() { +) : ProtocolMiddleware { override val name: String = "AwsJsonProtocol" override val order: Byte = 10 - override fun renderConfigure(writer: KotlinWriter) { + override fun render(ctx: ProtocolGenerator.GenerationContext, op: OperationShape, writer: KotlinWriter) { val awsJsonProtocolSymbol = buildSymbol { name = "AwsJsonProtocol" namespace(AwsKotlinDependency.AWS_JSON_PROTOCOLS) } writer.addImport(awsJsonProtocolSymbol) - writer.write("serviceShapeName = #S", serviceShapeId.name) - writer.write("version = #S", protocolVersion) + writer.write("op.install(#T(#S, #S))", awsJsonProtocolSymbol, serviceShapeId.name, protocolVersion) } } diff --git a/codegen/smithy-aws-kotlin-codegen/src/main/kotlin/aws/sdk/kotlin/codegen/protocols/middleware/AwsSignatureVersion4.kt b/codegen/smithy-aws-kotlin-codegen/src/main/kotlin/aws/sdk/kotlin/codegen/protocols/middleware/AwsSignatureVersion4.kt index 2c3e4f2815a..77ef6148dcb 100644 --- a/codegen/smithy-aws-kotlin-codegen/src/main/kotlin/aws/sdk/kotlin/codegen/protocols/middleware/AwsSignatureVersion4.kt +++ b/codegen/smithy-aws-kotlin-codegen/src/main/kotlin/aws/sdk/kotlin/codegen/protocols/middleware/AwsSignatureVersion4.kt @@ -8,11 +8,12 @@ package aws.sdk.kotlin.codegen.protocols.middleware import aws.sdk.kotlin.codegen.AwsRuntimeTypes import software.amazon.smithy.aws.traits.auth.SigV4Trait import software.amazon.smithy.kotlin.codegen.core.KotlinWriter +import software.amazon.smithy.kotlin.codegen.core.withBlock import software.amazon.smithy.kotlin.codegen.model.expectShape import software.amazon.smithy.kotlin.codegen.model.expectTrait import software.amazon.smithy.kotlin.codegen.model.hasTrait -import software.amazon.smithy.kotlin.codegen.rendering.protocol.HttpFeatureMiddleware import software.amazon.smithy.kotlin.codegen.rendering.protocol.ProtocolGenerator +import software.amazon.smithy.kotlin.codegen.rendering.protocol.ProtocolMiddleware import software.amazon.smithy.model.Model import software.amazon.smithy.model.knowledge.ServiceIndex import software.amazon.smithy.model.shapes.OperationShape @@ -24,7 +25,7 @@ import software.amazon.smithy.model.traits.OptionalAuthTrait * @param signingServiceName The credential scope service name to sign for * See the `name` property of: https://awslabs.github.io/smithy/1.0/spec/aws/aws-auth.html#aws-auth-sigv4-trait */ -open class AwsSignatureVersion4(private val signingServiceName: String) : HttpFeatureMiddleware() { +open class AwsSignatureVersion4(private val signingServiceName: String) : ProtocolMiddleware { override val name: String = AwsRuntimeTypes.Signing.AwsSigV4SigningMiddleware.name override val order: Byte = 126 // Must come before GlacierBodyChecksum @@ -37,9 +38,17 @@ open class AwsSignatureVersion4(private val signingServiceName: String) : HttpFe return hasSigV4AuthScheme(ctx.model, service, op) } - override fun renderConfigure(writer: KotlinWriter) { + final override fun render(ctx: ProtocolGenerator.GenerationContext, op: OperationShape, writer: KotlinWriter) { writer.addImport(AwsRuntimeTypes.Signing.AwsSigV4SigningMiddleware) + writer.withBlock("op.install(", ")") { + withBlock("#T{", "}", AwsRuntimeTypes.Signing.AwsSigV4SigningMiddleware) { + renderSigningConfig(op, writer) + } + } + } + + protected open fun renderSigningConfig(op: OperationShape, writer: KotlinWriter) { writer.write("this.credentialsProvider = config.credentialsProvider") writer.write("this.signingService = #S", signingServiceName) } diff --git a/codegen/smithy-aws-kotlin-codegen/src/main/kotlin/aws/sdk/kotlin/codegen/protocols/middleware/MutateHeadersMiddleware.kt b/codegen/smithy-aws-kotlin-codegen/src/main/kotlin/aws/sdk/kotlin/codegen/protocols/middleware/MutateHeadersMiddleware.kt index 74e35f2c825..70388591355 100644 --- a/codegen/smithy-aws-kotlin-codegen/src/main/kotlin/aws/sdk/kotlin/codegen/protocols/middleware/MutateHeadersMiddleware.kt +++ b/codegen/smithy-aws-kotlin-codegen/src/main/kotlin/aws/sdk/kotlin/codegen/protocols/middleware/MutateHeadersMiddleware.kt @@ -7,7 +7,10 @@ package aws.sdk.kotlin.codegen.protocols.middleware import software.amazon.smithy.kotlin.codegen.core.KotlinWriter import software.amazon.smithy.kotlin.codegen.core.RuntimeTypes -import software.amazon.smithy.kotlin.codegen.rendering.protocol.HttpFeatureMiddleware +import software.amazon.smithy.kotlin.codegen.core.withBlock +import software.amazon.smithy.kotlin.codegen.rendering.protocol.ProtocolGenerator +import software.amazon.smithy.kotlin.codegen.rendering.protocol.ProtocolMiddleware +import software.amazon.smithy.model.shapes.OperationShape /** * General purpose middleware that allows mutation of headers @@ -16,19 +19,23 @@ class MutateHeadersMiddleware( val extraHeaders: Map = emptyMap(), val overrideHeaders: Map = emptyMap(), val addMissingHeaders: Map = emptyMap(), -) : HttpFeatureMiddleware() { +) : ProtocolMiddleware { override val name: String = "MutateHeaders" override val order: Byte = 10 - override fun renderConfigure(writer: KotlinWriter) { + override fun render(ctx: ProtocolGenerator.GenerationContext, op: OperationShape, writer: KotlinWriter) { writer.addImport(RuntimeTypes.Http.Middlware.MutateHeadersMiddleware) - overrideHeaders.forEach { - writer.write("set(#S, #S)", it.key, it.value) - } - extraHeaders.forEach { - writer.write("append(#S, #S)", it.key, it.value) - } - addMissingHeaders.forEach { - writer.write("setIfMissing(#S, #S)", it.key, it.value) + writer.withBlock("op.install(", ")") { + withBlock("#T().apply {", "}", RuntimeTypes.Http.Middlware.MutateHeadersMiddleware) { + overrideHeaders.forEach { + writer.write("set(#S, #S)", it.key, it.value) + } + extraHeaders.forEach { + writer.write("append(#S, #S)", it.key, it.value) + } + addMissingHeaders.forEach { + writer.write("setIfMissing(#S, #S)", it.key, it.value) + } + } } } } diff --git a/codegen/smithy-aws-kotlin-codegen/src/main/kotlin/aws/sdk/kotlin/codegen/protocols/middleware/ResolveAwsEndpointMiddleware.kt b/codegen/smithy-aws-kotlin-codegen/src/main/kotlin/aws/sdk/kotlin/codegen/protocols/middleware/ResolveAwsEndpointMiddleware.kt index ec529b61eef..d469274f3d0 100644 --- a/codegen/smithy-aws-kotlin-codegen/src/main/kotlin/aws/sdk/kotlin/codegen/protocols/middleware/ResolveAwsEndpointMiddleware.kt +++ b/codegen/smithy-aws-kotlin-codegen/src/main/kotlin/aws/sdk/kotlin/codegen/protocols/middleware/ResolveAwsEndpointMiddleware.kt @@ -9,22 +9,21 @@ import aws.sdk.kotlin.codegen.AwsKotlinDependency import software.amazon.smithy.kotlin.codegen.core.KotlinWriter import software.amazon.smithy.kotlin.codegen.model.buildSymbol import software.amazon.smithy.kotlin.codegen.model.namespace -import software.amazon.smithy.kotlin.codegen.rendering.protocol.HttpFeatureMiddleware import software.amazon.smithy.kotlin.codegen.rendering.protocol.ProtocolGenerator +import software.amazon.smithy.kotlin.codegen.rendering.protocol.ProtocolMiddleware +import software.amazon.smithy.model.shapes.OperationShape /** * HTTP client interceptor that resolves service endpoints for a single service */ -class ResolveAwsEndpointMiddleware(private val ctx: ProtocolGenerator.GenerationContext) : HttpFeatureMiddleware() { +class ResolveAwsEndpointMiddleware(private val ctx: ProtocolGenerator.GenerationContext) : ProtocolMiddleware { override val name: String = "ResolveAwsEndpoint" - override fun renderConfigure(writer: KotlinWriter) { + override fun render(ctx: ProtocolGenerator.GenerationContext, op: OperationShape, writer: KotlinWriter) { val resolverFeatureSymbol = buildSymbol { name = "ResolveAwsEndpoint" namespace(AwsKotlinDependency.AWS_HTTP, subpackage = "middleware") } writer.addImport(resolverFeatureSymbol) - - writer.write("serviceId = ServiceId") - writer.write("resolver = config.endpointResolver") + writer.write("op.install(#T(ServiceId, config.endpointResolver))", resolverFeatureSymbol) } } diff --git a/codegen/smithy-aws-kotlin-codegen/src/main/kotlin/aws/sdk/kotlin/codegen/protocols/middleware/UserAgentMiddleware.kt b/codegen/smithy-aws-kotlin-codegen/src/main/kotlin/aws/sdk/kotlin/codegen/protocols/middleware/UserAgentMiddleware.kt index 7d96c2e7ff3..6cd1fdd9073 100644 --- a/codegen/smithy-aws-kotlin-codegen/src/main/kotlin/aws/sdk/kotlin/codegen/protocols/middleware/UserAgentMiddleware.kt +++ b/codegen/smithy-aws-kotlin-codegen/src/main/kotlin/aws/sdk/kotlin/codegen/protocols/middleware/UserAgentMiddleware.kt @@ -9,12 +9,14 @@ import aws.sdk.kotlin.codegen.AwsKotlinDependency import software.amazon.smithy.kotlin.codegen.core.KotlinWriter import software.amazon.smithy.kotlin.codegen.model.buildSymbol import software.amazon.smithy.kotlin.codegen.model.namespace -import software.amazon.smithy.kotlin.codegen.rendering.protocol.HttpFeatureMiddleware +import software.amazon.smithy.kotlin.codegen.rendering.protocol.ProtocolGenerator +import software.amazon.smithy.kotlin.codegen.rendering.protocol.ProtocolMiddleware +import software.amazon.smithy.model.shapes.OperationShape /** * Middleware that sets the User-Agent and x-amz-user-agent headers */ -class UserAgentMiddleware : HttpFeatureMiddleware() { +class UserAgentMiddleware : ProtocolMiddleware { override val name: String = "UserAgent" override val order: Byte = 20 @@ -27,7 +29,7 @@ class UserAgentMiddleware : HttpFeatureMiddleware() { name = "ApiMetadata" namespace(AwsKotlinDependency.AWS_HTTP) } - private val featSymbol = buildSymbol { + private val middlewareSymbol = buildSymbol { name = "UserAgent" namespace(AwsKotlinDependency.AWS_HTTP, subpackage = "middleware") } @@ -36,11 +38,11 @@ class UserAgentMiddleware : HttpFeatureMiddleware() { // static metadata that doesn't change per/request writer.addImport(uaSymbol) writer.addImport(apiMetaSymbol) - writer.addImport(featSymbol) + writer.addImport(middlewareSymbol) writer.write("private val awsUserAgentMetadata = #T.fromEnvironment(#T(ServiceId, SdkVersion))", uaSymbol, apiMetaSymbol) } - override fun renderConfigure(writer: KotlinWriter) { - writer.write("staticMetadata = awsUserAgentMetadata") + override fun render(ctx: ProtocolGenerator.GenerationContext, op: OperationShape, writer: KotlinWriter) { + writer.write("op.install(#T(awsUserAgentMetadata))", middlewareSymbol) } } diff --git a/services/glacier/common/src/aws/sdk/kotlin/services/glacier/internal/GlacierBodyChecksum.kt b/services/glacier/common/src/aws/sdk/kotlin/services/glacier/internal/GlacierBodyChecksum.kt index 1c797044932..1dee3177e0a 100644 --- a/services/glacier/common/src/aws/sdk/kotlin/services/glacier/internal/GlacierBodyChecksum.kt +++ b/services/glacier/common/src/aws/sdk/kotlin/services/glacier/internal/GlacierBodyChecksum.kt @@ -7,47 +7,35 @@ package aws.sdk.kotlin.services.glacier.internal import aws.sdk.kotlin.services.glacier.model.GlacierException import aws.smithy.kotlin.runtime.client.operationName -import aws.smithy.kotlin.runtime.http.Feature -import aws.smithy.kotlin.runtime.http.FeatureKey import aws.smithy.kotlin.runtime.http.HttpBody -import aws.smithy.kotlin.runtime.http.HttpClientFeatureFactory +import aws.smithy.kotlin.runtime.http.operation.ModifyRequestMiddleware import aws.smithy.kotlin.runtime.http.operation.SdkHttpOperation +import aws.smithy.kotlin.runtime.http.operation.SdkHttpRequest import aws.smithy.kotlin.runtime.http.request.headers import aws.smithy.kotlin.runtime.util.Sha256 import aws.smithy.kotlin.runtime.util.encodeToHex private const val defaultChunkSizeBytes = 1024 * 1024 // 1MB -internal class GlacierBodyChecksum(config: Config) : Feature { - private val treeHasher = config.treeHasher - - internal class Config { - internal var treeHasher: TreeHasher = TreeHasherImpl(defaultChunkSizeBytes) { Sha256() } +internal class GlacierBodyChecksum( + private val treeHasher: TreeHasher = TreeHasherImpl(defaultChunkSizeBytes) { Sha256() } +) : ModifyRequestMiddleware { + override fun install(op: SdkHttpOperation<*, *>) { + op.execution.finalize.register(this) } - internal companion object Feature : HttpClientFeatureFactory { - override val key: FeatureKey = FeatureKey("GlacierBodyChecksum") - - override fun create(block: Config.() -> Unit): GlacierBodyChecksum { - val config = Config().apply(block) - return GlacierBodyChecksum(config) + override suspend fun modifyRequest(req: SdkHttpRequest): SdkHttpRequest { + val body = req.subject.body + if (body is HttpBody.Streaming && !body.isReplayable) { + val opName = req.context.operationName ?: "This operation" + throw GlacierException("$opName requires a byte array or replayable stream") } - } - - override fun install(operation: SdkHttpOperation) { - operation.execution.finalize.intercept { req, next -> - val body = req.subject.body - if (body is HttpBody.Streaming && !body.isReplayable) { - val opName = req.context.operationName ?: "This operation" - throw GlacierException("$opName requires a byte array or replayable stream") - } - val hashes = treeHasher.calculateHashes(body) - req.subject.headers { - set("X-Amz-Content-Sha256", hashes.fullHash.encodeToHex()) - set("X-Amz-Sha256-Tree-Hash", hashes.treeHash.encodeToHex()) - } - - next.call(req) + val hashes = treeHasher.calculateHashes(body) + req.subject.headers { + set("X-Amz-Content-Sha256", hashes.fullHash.encodeToHex()) + set("X-Amz-Sha256-Tree-Hash", hashes.treeHash.encodeToHex()) } + + return req } } From 9024cef24e6c5c75f19b321c8dec712286f2f16b Mon Sep 17 00:00:00 2001 From: Aaron J Todd Date: Mon, 15 Nov 2021 17:38:14 -0500 Subject: [PATCH 3/7] remove autoinstall --- .../machinelearning/internal/ResolvePredictEndpoint.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/services/machinelearning/common/src/aws/sdk/kotlin/services/machinelearning/internal/ResolvePredictEndpoint.kt b/services/machinelearning/common/src/aws/sdk/kotlin/services/machinelearning/internal/ResolvePredictEndpoint.kt index 9b24c796a71..d51f05b5f0c 100644 --- a/services/machinelearning/common/src/aws/sdk/kotlin/services/machinelearning/internal/ResolvePredictEndpoint.kt +++ b/services/machinelearning/common/src/aws/sdk/kotlin/services/machinelearning/internal/ResolvePredictEndpoint.kt @@ -10,10 +10,10 @@ import aws.sdk.kotlin.services.machinelearning.model.MachineLearningException import aws.sdk.kotlin.services.machinelearning.model.PredictRequest import aws.sdk.kotlin.services.machinelearning.model.PredictResponse import aws.smithy.kotlin.runtime.http.middleware.setRequestEndpoint -import aws.smithy.kotlin.runtime.http.operation.AutoInstall +import aws.smithy.kotlin.runtime.http.operation.InlineMiddleware import aws.smithy.kotlin.runtime.http.operation.SdkHttpOperation -internal class ResolvePredictEndpoint : AutoInstall { +internal class ResolvePredictEndpoint : InlineMiddleware { override fun install(op: SdkHttpOperation) { op.execution.initialize.intercept { req, next -> val input = req.subject From 72ce71f8995dc38b4b11ddb754fb9cce6bbcbdbb Mon Sep 17 00:00:00 2001 From: Aaron J Todd Date: Tue, 16 Nov 2021 11:17:14 -0500 Subject: [PATCH 4/7] remove unnecessary overrides --- .../sdk/kotlin/runtime/http/middleware/ResolveAwsEndpoint.kt | 5 ----- .../aws/sdk/kotlin/runtime/protocol/json/AwsJsonProtocol.kt | 5 ----- 2 files changed, 10 deletions(-) diff --git a/aws-runtime/aws-http/common/src/aws/sdk/kotlin/runtime/http/middleware/ResolveAwsEndpoint.kt b/aws-runtime/aws-http/common/src/aws/sdk/kotlin/runtime/http/middleware/ResolveAwsEndpoint.kt index 16372a6cdf1..22bf2577b4c 100644 --- a/aws-runtime/aws-http/common/src/aws/sdk/kotlin/runtime/http/middleware/ResolveAwsEndpoint.kt +++ b/aws-runtime/aws-http/common/src/aws/sdk/kotlin/runtime/http/middleware/ResolveAwsEndpoint.kt @@ -12,7 +12,6 @@ import aws.sdk.kotlin.runtime.execution.AuthAttributes import aws.smithy.kotlin.runtime.http.* import aws.smithy.kotlin.runtime.http.middleware.setRequestEndpoint import aws.smithy.kotlin.runtime.http.operation.ModifyRequestMiddleware -import aws.smithy.kotlin.runtime.http.operation.SdkHttpOperation import aws.smithy.kotlin.runtime.http.operation.SdkHttpRequest import aws.smithy.kotlin.runtime.http.operation.getLogger import aws.smithy.kotlin.runtime.util.get @@ -34,10 +33,6 @@ public class ResolveAwsEndpoint( ) : ModifyRequestMiddleware { - override fun install(op: SdkHttpOperation<*, *>) { - op.execution.mutate.register(this) - } - override suspend fun modifyRequest(req: SdkHttpRequest): SdkHttpRequest { val region = req.context[AwsClientOption.Region] val endpoint = resolver.resolve(serviceId, region) diff --git a/aws-runtime/protocols/aws-json-protocols/common/src/aws/sdk/kotlin/runtime/protocol/json/AwsJsonProtocol.kt b/aws-runtime/protocols/aws-json-protocols/common/src/aws/sdk/kotlin/runtime/protocol/json/AwsJsonProtocol.kt index 23d4247a749..280f5826d0d 100644 --- a/aws-runtime/protocols/aws-json-protocols/common/src/aws/sdk/kotlin/runtime/protocol/json/AwsJsonProtocol.kt +++ b/aws-runtime/protocols/aws-json-protocols/common/src/aws/sdk/kotlin/runtime/protocol/json/AwsJsonProtocol.kt @@ -9,7 +9,6 @@ import aws.smithy.kotlin.runtime.client.SdkClientOption import aws.smithy.kotlin.runtime.http.* import aws.smithy.kotlin.runtime.http.content.ByteArrayContent import aws.smithy.kotlin.runtime.http.operation.ModifyRequestMiddleware -import aws.smithy.kotlin.runtime.http.operation.SdkHttpOperation import aws.smithy.kotlin.runtime.http.operation.SdkHttpRequest import aws.smithy.kotlin.runtime.util.get @@ -35,10 +34,6 @@ public class AwsJsonProtocol( private val version: String ) : ModifyRequestMiddleware { - override fun install(op: SdkHttpOperation<*, *>) { - op.execution.mutate.register(this) - } - override suspend fun modifyRequest(req: SdkHttpRequest): SdkHttpRequest { val context = req.context // required context elements From af9c101ef103ce6e4228a2a1da5995650294e028 Mon Sep 17 00:00:00 2001 From: Aaron J Todd Date: Tue, 16 Nov 2021 13:09:36 -0500 Subject: [PATCH 5/7] fix imds middleware --- .../common/src/aws/sdk/kotlin/runtime/config/imds/ImdsClient.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/aws-runtime/aws-config/common/src/aws/sdk/kotlin/runtime/config/imds/ImdsClient.kt b/aws-runtime/aws-config/common/src/aws/sdk/kotlin/runtime/config/imds/ImdsClient.kt index 355a258bb4b..43fe2d7ee64 100644 --- a/aws-runtime/aws-config/common/src/aws/sdk/kotlin/runtime/config/imds/ImdsClient.kt +++ b/aws-runtime/aws-config/common/src/aws/sdk/kotlin/runtime/config/imds/ImdsClient.kt @@ -90,7 +90,7 @@ public class ImdsClient private constructor(builder: Builder) : InstanceMetadata val delayProvider = ExponentialBackoffWithJitter(ExponentialBackoffWithJitterOptions.Default) val strategy = StandardRetryStrategy(StandardRetryStrategyOptions.Default, tokenBucket, delayProvider) val policy = ImdsRetryPolicy() - RetryFeature(strategy, policy) + RetryFeature(strategy, policy) } private val tokenMiddleware = TokenMiddleware(httpClient, tokenTtl, clock) From 6bb3bc239f3569e3f150b09e3da499f3897a0ba5 Mon Sep 17 00:00:00 2001 From: Aaron J Todd Date: Tue, 30 Nov 2021 15:01:16 -0500 Subject: [PATCH 6/7] fix override retry middleware --- .../aws/sdk/kotlin/codegen/AwsDefaultRetryIntegration.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/codegen/smithy-aws-kotlin-codegen/src/main/kotlin/aws/sdk/kotlin/codegen/AwsDefaultRetryIntegration.kt b/codegen/smithy-aws-kotlin-codegen/src/main/kotlin/aws/sdk/kotlin/codegen/AwsDefaultRetryIntegration.kt index 58e8169063f..93b02a2ca37 100644 --- a/codegen/smithy-aws-kotlin-codegen/src/main/kotlin/aws/sdk/kotlin/codegen/AwsDefaultRetryIntegration.kt +++ b/codegen/smithy-aws-kotlin-codegen/src/main/kotlin/aws/sdk/kotlin/codegen/AwsDefaultRetryIntegration.kt @@ -23,10 +23,10 @@ class AwsDefaultRetryIntegration : KotlinIntegration { override fun customizeMiddleware( ctx: ProtocolGenerator.GenerationContext, resolved: List - ): List = resolved.replace(middleware) { it.name == StandardRetryMiddleware.name } + ): List = resolved.replace(middleware) { it is StandardRetryMiddleware } private val middleware = object : ProtocolMiddleware { - override val name: String = "Retry" + override val name: String = RuntimeTypes.Http.Middlware.Retry.name override fun render(ctx: ProtocolGenerator.GenerationContext, op: OperationShape, writer: KotlinWriter) { writer.addImport(RuntimeTypes.Http.Middlware.Retry) From 5697b93532e9010895489a370e071705ea411a7a Mon Sep 17 00:00:00 2001 From: Aaron J Todd Date: Tue, 7 Dec 2021 09:29:51 -0500 Subject: [PATCH 7/7] use chained writer --- .../middleware/MutateHeadersMiddleware.kt | 22 +++++++++---------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/codegen/smithy-aws-kotlin-codegen/src/main/kotlin/aws/sdk/kotlin/codegen/protocols/middleware/MutateHeadersMiddleware.kt b/codegen/smithy-aws-kotlin-codegen/src/main/kotlin/aws/sdk/kotlin/codegen/protocols/middleware/MutateHeadersMiddleware.kt index 70388591355..178e06b0012 100644 --- a/codegen/smithy-aws-kotlin-codegen/src/main/kotlin/aws/sdk/kotlin/codegen/protocols/middleware/MutateHeadersMiddleware.kt +++ b/codegen/smithy-aws-kotlin-codegen/src/main/kotlin/aws/sdk/kotlin/codegen/protocols/middleware/MutateHeadersMiddleware.kt @@ -24,18 +24,18 @@ class MutateHeadersMiddleware( override val order: Byte = 10 override fun render(ctx: ProtocolGenerator.GenerationContext, op: OperationShape, writer: KotlinWriter) { writer.addImport(RuntimeTypes.Http.Middlware.MutateHeadersMiddleware) - writer.withBlock("op.install(", ")") { - withBlock("#T().apply {", "}", RuntimeTypes.Http.Middlware.MutateHeadersMiddleware) { - overrideHeaders.forEach { - writer.write("set(#S, #S)", it.key, it.value) - } - extraHeaders.forEach { - writer.write("append(#S, #S)", it.key, it.value) - } - addMissingHeaders.forEach { - writer.write("setIfMissing(#S, #S)", it.key, it.value) + .withBlock("op.install(", ")") { + withBlock("#T().apply {", "}", RuntimeTypes.Http.Middlware.MutateHeadersMiddleware) { + overrideHeaders.forEach { + writer.write("set(#S, #S)", it.key, it.value) + } + extraHeaders.forEach { + writer.write("append(#S, #S)", it.key, it.value) + } + addMissingHeaders.forEach { + writer.write("setIfMissing(#S, #S)", it.key, it.value) + } } } - } } }