Skip to content

Commit 5133dd0

Browse files
authored
remove middleware Feature concept (#425)
1 parent 6194e0a commit 5133dd0

File tree

22 files changed

+302
-399
lines changed

22 files changed

+302
-399
lines changed

aws-runtime/aws-config/common/src/aws/sdk/kotlin/runtime/config/imds/ImdsClient.kt

Lines changed: 15 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -78,30 +78,18 @@ public class ImdsClient private constructor(builder: Builder) : InstanceMetadata
7878
}
7979

8080
// cached middleware instances
81-
private val middleware: List<Feature> = listOf(
82-
ResolveEndpoint.create {
83-
resolver = ImdsEndpointResolver(platformProvider, endpointConfiguration)
84-
},
85-
UserAgent.create {
86-
staticMetadata = AwsUserAgentMetadata.fromEnvironment(ApiMetadata(SERVICE, "unknown"))
87-
},
88-
Retry.create {
89-
val tokenBucket = StandardRetryTokenBucket(StandardRetryTokenBucketOptions.Default)
90-
val delayProvider = ExponentialBackoffWithJitter(ExponentialBackoffWithJitterOptions.Default)
91-
strategy = StandardRetryStrategy(
92-
StandardRetryStrategyOptions.Default.copy(maxAttempts = maxRetries),
93-
tokenBucket,
94-
delayProvider
95-
)
96-
policy = ImdsRetryPolicy()
97-
},
98-
// must come after retries
99-
TokenMiddleware.create {
100-
httpClient = this@ImdsClient.httpClient
101-
ttl = tokenTtl
102-
clock = this@ImdsClient.clock
103-
},
81+
private val resolveEndpointMiddleware = ResolveEndpoint(ImdsEndpointResolver(platformProvider, endpointConfiguration))
82+
private val userAgentMiddleware = UserAgent(
83+
staticMetadata = AwsUserAgentMetadata.fromEnvironment(ApiMetadata(SERVICE, "unknown"))
10484
)
85+
private val retryMiddleware = run {
86+
val tokenBucket = StandardRetryTokenBucket(StandardRetryTokenBucketOptions.Default)
87+
val delayProvider = ExponentialBackoffWithJitter(ExponentialBackoffWithJitterOptions.Default)
88+
val strategy = StandardRetryStrategy(StandardRetryStrategyOptions.Default, tokenBucket, delayProvider)
89+
val policy = ImdsRetryPolicy()
90+
Retry<String>(strategy, policy)
91+
}
92+
private val tokenMiddleware = TokenMiddleware(httpClient, tokenTtl, clock)
10593

10694
public companion object {
10795
public operator fun invoke(block: Builder.() -> Unit): ImdsClient = ImdsClient(Builder().apply(block))
@@ -142,7 +130,10 @@ public class ImdsClient private constructor(builder: Builder) : InstanceMetadata
142130
set(SdkClientOption.LogMode, sdkLogMode)
143131
}
144132
}
145-
middleware.forEach { it.install(op) }
133+
op.install(resolveEndpointMiddleware)
134+
op.install(userAgentMiddleware)
135+
op.install(retryMiddleware)
136+
op.install(tokenMiddleware)
146137
op.execution.mutate.intercept(Phase.Order.Before) { req, next ->
147138
req.subject.url.path = path
148139
next.call(req)

aws-runtime/aws-config/common/src/aws/sdk/kotlin/runtime/config/imds/TokenMiddleware.kt

Lines changed: 12 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ package aws.sdk.kotlin.runtime.config.imds
88
import aws.sdk.kotlin.runtime.config.CachedValue
99
import aws.sdk.kotlin.runtime.config.ExpiringValue
1010
import aws.smithy.kotlin.runtime.http.*
11+
import aws.smithy.kotlin.runtime.http.operation.ModifyRequestMiddleware
1112
import aws.smithy.kotlin.runtime.http.operation.SdkHttpOperation
1213
import aws.smithy.kotlin.runtime.http.operation.SdkHttpRequest
1314
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
2930
internal const val X_AWS_EC2_METADATA_TOKEN = "x-aws-ec2-metadata-token"
3031

3132
@OptIn(ExperimentalTime::class)
32-
internal class TokenMiddleware(config: Config) : Feature {
33-
private val ttl: Duration = config.ttl
34-
private val httpClient = requireNotNull(config.httpClient) { "SdkHttpClient is required for token middleware to make requests" }
35-
private val clock: Clock = config.clock
33+
internal class TokenMiddleware(
34+
private val httpClient: SdkHttpClient,
35+
private val ttl: Duration = Duration.seconds(DEFAULT_TOKEN_TTL_SECONDS),
36+
private val clock: Clock = Clock.System
37+
) : ModifyRequestMiddleware {
3638
private var cachedToken = CachedValue<Token>(null, bufferTime = Duration.seconds(TOKEN_REFRESH_BUFFER_SECONDS), clock = clock)
3739

38-
public class Config {
39-
var ttl: Duration = Duration.seconds(DEFAULT_TOKEN_TTL_SECONDS)
40-
var httpClient: SdkHttpClient? = null
41-
var clock: Clock = Clock.System
40+
override fun install(op: SdkHttpOperation<*, *>) {
41+
op.execution.finalize.register(this)
4242
}
4343

44-
public companion object Feature :
45-
HttpClientFeatureFactory<Config, TokenMiddleware> {
46-
override val key: FeatureKey<TokenMiddleware> = FeatureKey("EC2Metadata_Token_Middleware")
47-
override fun create(block: Config.() -> Unit): TokenMiddleware {
48-
val config = Config().apply(block)
49-
return TokenMiddleware(config)
50-
}
51-
}
52-
53-
override fun <I, O> install(operation: SdkHttpOperation<I, O>) {
54-
operation.execution.finalize.intercept { req, next ->
55-
val token = cachedToken.getOrLoad { getToken(clock, req).let { ExpiringValue(it, it.expires) } }
56-
req.subject.headers.append(X_AWS_EC2_METADATA_TOKEN, token.value.decodeToString())
57-
next.call(req)
58-
}
44+
override suspend fun modifyRequest(req: SdkHttpRequest): SdkHttpRequest {
45+
val token = cachedToken.getOrLoad { getToken(clock, req).let { ExpiringValue(it, it.expires) } }
46+
req.subject.headers.append(X_AWS_EC2_METADATA_TOKEN, token.value.decodeToString())
47+
return req
5948
}
6049

6150
private suspend fun getToken(clock: Clock, req: SdkHttpRequest): Token {
@@ -76,7 +65,6 @@ internal class TokenMiddleware(config: Config) : Feature {
7665
}
7766
}
7867

79-
// TODO - retries with custom policy around 400 and 403
8068
val call = httpClient.call(tokenReq)
8169
return try {
8270
when (call.response.status) {

aws-runtime/aws-http/common/src/aws/sdk/kotlin/runtime/http/middleware/ResolveAwsEndpoint.kt

Lines changed: 30 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,8 @@ import aws.sdk.kotlin.runtime.endpoint.AwsEndpointResolver
1111
import aws.sdk.kotlin.runtime.execution.AuthAttributes
1212
import aws.smithy.kotlin.runtime.http.*
1313
import aws.smithy.kotlin.runtime.http.middleware.setRequestEndpoint
14-
import aws.smithy.kotlin.runtime.http.operation.SdkHttpOperation
14+
import aws.smithy.kotlin.runtime.http.operation.ModifyRequestMiddleware
15+
import aws.smithy.kotlin.runtime.http.operation.SdkHttpRequest
1516
import aws.smithy.kotlin.runtime.http.operation.getLogger
1617
import aws.smithy.kotlin.runtime.util.get
1718

@@ -20,54 +21,35 @@ import aws.smithy.kotlin.runtime.util.get
2021
*/
2122
@InternalSdkApi
2223
public class ResolveAwsEndpoint(
23-
config: Config
24-
) : Feature {
25-
26-
private val serviceId: String = requireNotNull(config.serviceId) { "ServiceId must not be null" }
27-
private val resolver: AwsEndpointResolver = requireNotNull(config.resolver) { "EndpointResolver must not be null" }
28-
29-
public class Config {
30-
/**
31-
* The AWS service ID to resolve endpoints for
32-
*/
33-
public var serviceId: String? = null
34-
35-
/**
36-
* The resolver to use
37-
*/
38-
public var resolver: AwsEndpointResolver? = null
39-
}
40-
41-
public companion object Feature : HttpClientFeatureFactory<Config, ResolveAwsEndpoint> {
42-
override val key: FeatureKey<ResolveAwsEndpoint> = FeatureKey("ServiceEndpointResolver")
43-
44-
override fun create(block: Config.() -> Unit): ResolveAwsEndpoint {
45-
val config = Config().apply(block)
46-
return ResolveAwsEndpoint(config)
47-
}
48-
}
49-
50-
override fun <I, O> install(operation: SdkHttpOperation<I, O>) {
51-
operation.execution.mutate.intercept { req, next ->
52-
53-
val region = req.context[AwsClientOption.Region]
54-
val endpoint = resolver.resolve(serviceId, region)
55-
setRequestEndpoint(req, endpoint.endpoint)
56-
57-
endpoint.credentialScope?.let { scope ->
58-
// resolved endpoint has credential scope override(s), update the context for downstream consumers
59-
scope.service?.let {
60-
if (it.isNotBlank()) req.context[AuthAttributes.SigningService] = it
61-
}
62-
scope.region?.let {
63-
if (it.isNotBlank()) req.context[AuthAttributes.SigningRegion] = it
64-
}
24+
/**
25+
* The AWS service ID to resolve endpoints for
26+
*/
27+
private val serviceId: String,
28+
29+
/**
30+
* The resolver to use
31+
*/
32+
private val resolver: AwsEndpointResolver
33+
34+
) : ModifyRequestMiddleware {
35+
36+
override suspend fun modifyRequest(req: SdkHttpRequest): SdkHttpRequest {
37+
val region = req.context[AwsClientOption.Region]
38+
val endpoint = resolver.resolve(serviceId, region)
39+
setRequestEndpoint(req, endpoint.endpoint)
40+
41+
endpoint.credentialScope?.let { scope ->
42+
// resolved endpoint has credential scope override(s), update the context for downstream consumers
43+
scope.service?.let {
44+
if (it.isNotBlank()) req.context[AuthAttributes.SigningService] = it
45+
}
46+
scope.region?.let {
47+
if (it.isNotBlank()) req.context[AuthAttributes.SigningRegion] = it
6548
}
66-
67-
val logger = req.context.getLogger("ResolveAwsEndpoint")
68-
logger.debug { "resolved endpoint: $endpoint" }
69-
70-
next.call(req)
7149
}
50+
51+
val logger = req.context.getLogger("ResolveAwsEndpoint")
52+
logger.debug { "resolved endpoint: $endpoint" }
53+
return req
7254
}
7355
}

aws-runtime/aws-http/common/src/aws/sdk/kotlin/runtime/http/middleware/UserAgent.kt

Lines changed: 20 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,9 @@ package aws.sdk.kotlin.runtime.http.middleware
88
import aws.sdk.kotlin.runtime.InternalSdkApi
99
import aws.sdk.kotlin.runtime.http.AwsUserAgentMetadata
1010
import aws.sdk.kotlin.runtime.http.operation.CustomUserAgentMetadata
11-
import aws.smithy.kotlin.runtime.http.Feature
12-
import aws.smithy.kotlin.runtime.http.FeatureKey
13-
import aws.smithy.kotlin.runtime.http.HttpClientFeatureFactory
11+
import aws.smithy.kotlin.runtime.http.operation.ModifyRequestMiddleware
1412
import aws.smithy.kotlin.runtime.http.operation.SdkHttpOperation
13+
import aws.smithy.kotlin.runtime.http.operation.SdkHttpRequest
1514
import aws.smithy.kotlin.runtime.io.middleware.Phase
1615

1716
internal const val X_AMZ_USER_AGENT: String = "x-amz-user-agent"
@@ -22,43 +21,30 @@ internal const val USER_AGENT: String = "User-Agent"
2221
*/
2322
@InternalSdkApi
2423
public class UserAgent(
24+
/**
25+
* Metadata that doesn't change per/request (e.g. sdk and environment related metadata)
26+
*/
2527
private val staticMetadata: AwsUserAgentMetadata
26-
) : Feature {
28+
) : ModifyRequestMiddleware {
2729

28-
public class Config {
29-
/**
30-
* Metadata that doesn't change per/request (e.g. sdk and environment related metadata)
31-
*/
32-
public var staticMetadata: AwsUserAgentMetadata? = null
30+
override fun install(op: SdkHttpOperation<*, *>) {
31+
op.execution.mutate.register(this, Phase.Order.After)
3332
}
3433

35-
public companion object Feature :
36-
HttpClientFeatureFactory<Config, UserAgent> {
37-
override val key: FeatureKey<UserAgent> = FeatureKey("UserAgent")
34+
override suspend fun modifyRequest(req: SdkHttpRequest): SdkHttpRequest {
35+
// pull dynamic values out of the context
36+
val customMetadata = req.context.getOrNull(CustomUserAgentMetadata.ContextKey)
3837

39-
override fun create(block: Config.() -> Unit): UserAgent {
40-
val config = Config().apply(block)
41-
val metadata = requireNotNull(config.staticMetadata) { "staticMetadata is required" }
42-
return UserAgent(metadata)
43-
}
44-
}
45-
46-
override fun <I, O> install(operation: SdkHttpOperation<I, O>) {
47-
operation.execution.mutate.intercept(Phase.Order.After) { req, next ->
48-
49-
// pull dynamic values out of the context
50-
val customMetadata = req.context.getOrNull(CustomUserAgentMetadata.ContextKey)
38+
// resolve the metadata for the request which is a combination of the static and per/operation metadata
39+
val requestMetadata = staticMetadata.copy(customMetadata = customMetadata)
5140

52-
// resolve the metadata for the request which is a combination of the static and per/operation metadata
53-
val requestMetadata = staticMetadata.copy(customMetadata = customMetadata)
41+
// NOTE: Due to legacy issues with processing the user agent, the original content for
42+
// x-amz-user-agent and User-Agent is swapped here. See top note in the
43+
// sdk-user-agent-header SEP and https://github.com/awslabs/smithy-kotlin/issues/373
44+
// for further details.
45+
req.subject.headers[USER_AGENT] = requestMetadata.xAmzUserAgent
46+
req.subject.headers[X_AMZ_USER_AGENT] = requestMetadata.userAgent
5447

55-
// NOTE: Due to legacy issues with processing the user agent, the original content for
56-
// x-amz-user-agent and User-Agent is swapped here. See top note in the
57-
// sdk-user-agent-header SEP and https://github.com/awslabs/smithy-kotlin/issues/373
58-
// for further details.
59-
req.subject.headers[USER_AGENT] = requestMetadata.xAmzUserAgent
60-
req.subject.headers[X_AMZ_USER_AGENT] = requestMetadata.userAgent
61-
next.call(req)
62-
}
48+
return req
6349
}
6450
}

aws-runtime/aws-http/common/test/aws/sdk/kotlin/runtime/http/middleware/ResolveAwsEndpointTest.kt

Lines changed: 4 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -48,10 +48,8 @@ class ResolveAwsEndpointTest {
4848
}
4949

5050
val endpoint = AwsEndpoint("https://api.test.com")
51-
op.install(ResolveAwsEndpoint) {
52-
resolver = AwsEndpointResolver { _, _ -> endpoint }
53-
serviceId = "TestService"
54-
}
51+
val resolver = AwsEndpointResolver { _, _ -> endpoint }
52+
op.install(ResolveAwsEndpoint("TestService", resolver))
5553

5654
op.roundTrip(client, Unit)
5755
val actual = op.context[HttpOperationContext.HttpCallList].first().request
@@ -78,10 +76,8 @@ class ResolveAwsEndpointTest {
7876
}
7977

8078
val endpoint = AwsEndpoint("https://api.test.com", CredentialScope("us-west-2", "foo"))
81-
op.install(ResolveAwsEndpoint) {
82-
resolver = AwsEndpointResolver { _, _ -> endpoint }
83-
serviceId = "TestService"
84-
}
79+
val resolver = AwsEndpointResolver { _, _ -> endpoint }
80+
op.install(ResolveAwsEndpoint("TestService", resolver))
8581

8682
op.roundTrip(client, Unit)
8783
val actual = op.context[HttpOperationContext.HttpCallList].first().request

aws-runtime/aws-http/common/test/aws/sdk/kotlin/runtime/http/middleware/UserAgentTest.kt

Lines changed: 4 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -50,9 +50,8 @@ class UserAgentTest {
5050
}
5151

5252
val provider = TestPlatformProvider()
53-
op.install(UserAgent) {
54-
staticMetadata = loadAwsUserAgentMetadataFromEnvironment(provider, ApiMetadata("Test Service", "1.2.3"))
55-
}
53+
val metadata = loadAwsUserAgentMetadataFromEnvironment(provider, ApiMetadata("Test Service", "1.2.3"))
54+
op.install(UserAgent(metadata))
5655

5756
op.roundTrip(client, Unit)
5857
val request = op.context[HttpOperationContext.HttpCallList].last().request
@@ -75,9 +74,7 @@ class UserAgentTest {
7574

7675
val provider = TestPlatformProvider()
7776
val staticMeta = loadAwsUserAgentMetadataFromEnvironment(provider, ApiMetadata("Test Service", "1.2.3"))
78-
op.install(UserAgent) {
79-
staticMetadata = staticMeta
80-
}
77+
op.install(UserAgent(staticMeta))
8178

8279
op.context.customUserAgentMetadata.add("foo", "bar")
8380

@@ -96,9 +93,7 @@ class UserAgentTest {
9693
}
9794
}
9895

99-
op2.install(UserAgent) {
100-
staticMetadata = staticMeta
101-
}
96+
op2.install(UserAgent(staticMeta))
10297

10398
op2.context.customUserAgentMetadata.add("baz", "quux")
10499

0 commit comments

Comments
 (0)