Skip to content

Commit b17d66d

Browse files
authored
refactor: remove middleware Feature concept (#536)
1 parent 1574cec commit b17d66d

File tree

23 files changed

+393
-311
lines changed

23 files changed

+393
-311
lines changed
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
/*
2+
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
3+
* SPDX-License-Identifier: Apache-2.0.
4+
*/
5+
6+
package aws.smithy.kotlin.runtime.io.middleware
7+
8+
import aws.smithy.kotlin.runtime.io.Handler
9+
10+
/**
11+
* A transform that only modifies the input type
12+
*/
13+
interface ModifyRequest<Request> {
14+
suspend fun modifyRequest(req: Request): Request
15+
}
16+
17+
/**
18+
* Adapter for [ModifyRequest] to implement middleware
19+
*/
20+
internal class ModifyRequestMiddleware<Request, Response>(
21+
private val transform: ModifyRequest<Request>
22+
) : Middleware<Request, Response> {
23+
override suspend fun <H : Handler<Request, Response>> handle(request: Request, next: H): Response =
24+
next.call(transform.modifyRequest(request))
25+
}
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
/*
2+
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
3+
* SPDX-License-Identifier: Apache-2.0.
4+
*/
5+
6+
package aws.smithy.kotlin.runtime.io.middleware
7+
8+
import aws.smithy.kotlin.runtime.io.Handler
9+
10+
/**
11+
* A transform that only modifies the output type
12+
*/
13+
interface ModifyResponse<Response> {
14+
suspend fun modifyResponse(resp: Response): Response
15+
}
16+
17+
/**
18+
* Adapter for [ModifyResponse] to implement middleware
19+
*/
20+
internal class ModifyResponseMiddleware<Request, Response>(
21+
private val transform: ModifyResponse<Response>
22+
) : Middleware<Request, Response> {
23+
override suspend fun <H : Handler<Request, Response>> handle(request: Request, next: H): Response {
24+
val resp = next.call(request)
25+
return transform.modifyResponse(resp)
26+
}
27+
}

runtime/io/common/src/aws/smithy/kotlin/runtime/io/middleware/Phase.kt

Lines changed: 19 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -20,23 +20,37 @@ class Phase<Request, Response> : Middleware<Request, Response> {
2020
Before, After
2121
}
2222

23-
private val middlewares = mutableListOf<Middleware<Request, Response>>()
23+
private val middlewares = ArrayDeque<Middleware<Request, Response>>()
2424

2525
/**
2626
* Insert [interceptor] in a specific order into the set of interceptors for this phase
2727
*/
2828
fun intercept(order: Order = Order.After, interceptor: suspend (req: Request, next: Handler<Request, Response>) -> Response) {
2929
val wrapped = MiddlewareLambda(interceptor)
30-
register(order, wrapped)
30+
register(wrapped, order)
31+
}
32+
33+
/**
34+
* Insert a [transform] that only modifies the request of this phase
35+
*/
36+
fun register(transform: ModifyRequest<Request>, order: Order = Order.After) {
37+
register(ModifyRequestMiddleware(transform), order)
38+
}
39+
40+
/**
41+
* Insert a [transform] that only modifies the response of this phase
42+
*/
43+
fun register(transform: ModifyResponse<Response>, order: Order = Order.After) {
44+
register(ModifyResponseMiddleware(transform), order)
3145
}
3246

3347
/**
3448
* Register a middleware in a specific order
3549
*/
36-
fun register(order: Order, middleware: Middleware<Request, Response>) {
50+
fun register(middleware: Middleware<Request, Response>, order: Order = Order.After) {
3751
when (order) {
38-
Order.Before -> middlewares.add(0, middleware)
39-
Order.After -> middlewares.add(middleware)
52+
Order.Before -> middlewares.addFirst(middleware)
53+
Order.After -> middlewares.addLast(middleware)
4054
}
4155
}
4256

runtime/protocol/http/common/src/aws/smithy/kotlin/runtime/http/Feature.kt

Lines changed: 0 additions & 40 deletions
This file was deleted.

runtime/protocol/http/common/src/aws/smithy/kotlin/runtime/http/middleware/DefaultValidateResponse.kt

Lines changed: 18 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,11 @@ package aws.smithy.kotlin.runtime.http.middleware
66

77
import aws.smithy.kotlin.runtime.SdkBaseException
88
import aws.smithy.kotlin.runtime.http.*
9-
import aws.smithy.kotlin.runtime.http.operation.SdkHttpOperation
9+
import aws.smithy.kotlin.runtime.http.operation.ReceiveMiddleware
10+
import aws.smithy.kotlin.runtime.http.operation.SdkHttpRequest
1011
import aws.smithy.kotlin.runtime.http.request.HttpRequest
12+
import aws.smithy.kotlin.runtime.http.response.HttpCall
13+
import aws.smithy.kotlin.runtime.io.Handler
1114

1215
/**
1316
* Generic HTTP service exception
@@ -51,29 +54,22 @@ class HttpResponseException : SdkBaseException {
5154
* so all we can do is throw a generic exception with the code and let the user figure out what modeled error it was
5255
* using whatever matching mechanism they want.
5356
*/
54-
class DefaultValidateResponse : Feature {
55-
companion object Feature : HttpClientFeatureFactory<DefaultValidateResponse, DefaultValidateResponse> {
56-
override val key: FeatureKey<DefaultValidateResponse> = FeatureKey("DefaultValidateResponse")
57-
override fun create(block: DefaultValidateResponse.() -> Unit): DefaultValidateResponse =
58-
DefaultValidateResponse().apply(block)
59-
}
60-
61-
override fun <I, O> install(operation: SdkHttpOperation<I, O>) {
62-
operation.execution.receive.intercept { req, next ->
63-
val call = next.call(req)
64-
if (call.response.status.isSuccess()) {
65-
return@intercept call
66-
}
57+
class DefaultValidateResponse : ReceiveMiddleware {
6758

68-
val message = "received unsuccessful HTTP call.response: ${call.response.status}"
69-
val httpException = HttpResponseException(message).apply {
70-
statusCode = call.response.status
71-
headers = call.response.headers
72-
body = call.response.body.readAll()
73-
request = call.request
74-
}
59+
override suspend fun <H : Handler<SdkHttpRequest, HttpCall>> handle(request: SdkHttpRequest, next: H): HttpCall {
60+
val call = next.call(request)
61+
if (call.response.status.isSuccess()) {
62+
return call
63+
}
7564

76-
throw httpException
65+
val message = "received unsuccessful HTTP call.response: ${call.response.status}"
66+
val httpException = HttpResponseException(message).apply {
67+
statusCode = call.response.status
68+
headers = call.response.headers
69+
body = call.response.body.readAll()
70+
this.request = call.request
7771
}
72+
73+
throw httpException
7874
}
7975
}

runtime/protocol/http/common/src/aws/smithy/kotlin/runtime/http/middleware/Md5Checksum.kt

Lines changed: 9 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,8 @@
55

66
package aws.smithy.kotlin.runtime.http.middleware
77

8-
import aws.smithy.kotlin.runtime.http.Feature
9-
import aws.smithy.kotlin.runtime.http.FeatureKey
108
import aws.smithy.kotlin.runtime.http.HttpBody
11-
import aws.smithy.kotlin.runtime.http.HttpClientFeatureFactory
12-
import aws.smithy.kotlin.runtime.http.operation.SdkHttpOperation
9+
import aws.smithy.kotlin.runtime.http.operation.*
1310
import aws.smithy.kotlin.runtime.http.request.header
1411
import aws.smithy.kotlin.runtime.util.InternalApi
1512
import aws.smithy.kotlin.runtime.util.encodeBase64String
@@ -22,23 +19,15 @@ import aws.smithy.kotlin.runtime.util.md5
2219
* - https://datatracker.ietf.org/doc/html/rfc1864.html
2320
*/
2421
@InternalApi
25-
class Md5Checksum : Feature {
22+
class Md5Checksum : ModifyRequestMiddleware {
2623

27-
companion object Feature : HttpClientFeatureFactory<Md5Checksum, Md5Checksum> {
28-
override val key: FeatureKey<Md5Checksum> = FeatureKey("Md5Checksum")
29-
override fun create(block: Md5Checksum.() -> Unit): Md5Checksum =
30-
Md5Checksum().apply(block)
31-
}
32-
33-
override fun <I, O> install(operation: SdkHttpOperation<I, O>) {
34-
operation.execution.mutate.intercept { req, next ->
35-
val checksum = when (val body = req.subject.body) {
36-
is HttpBody.Bytes -> body.bytes().md5().encodeBase64String()
37-
else -> null
38-
}
39-
40-
checksum?.let { req.subject.header("Content-MD5", it) }
41-
next.call(req)
24+
override suspend fun modifyRequest(req: SdkHttpRequest): SdkHttpRequest {
25+
val checksum = when (val body = req.subject.body) {
26+
is HttpBody.Bytes -> body.bytes().md5().encodeBase64String()
27+
else -> null
4228
}
29+
30+
checksum?.let { req.subject.header("Content-MD5", it) }
31+
return req
4332
}
4433
}

runtime/protocol/http/common/src/aws/smithy/kotlin/runtime/http/middleware/MutateHeaders.kt

Lines changed: 25 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -6,16 +6,28 @@
66
package aws.smithy.kotlin.runtime.http.middleware
77

88
import aws.smithy.kotlin.runtime.http.*
9-
import aws.smithy.kotlin.runtime.http.operation.SdkHttpOperation
9+
import aws.smithy.kotlin.runtime.http.operation.*
10+
import aws.smithy.kotlin.runtime.util.InternalApi
1011

1112
/**
1213
* HTTP middleware feature that allows mutation of in-flight request headers
1314
*/
14-
class MutateHeaders : Feature {
15+
@InternalApi
16+
class MutateHeaders(
17+
override: Map<String, String> = emptyMap(),
18+
append: Map<String, String> = emptyMap(),
19+
setMissing: Map<String, String> = emptyMap(),
20+
) : ModifyRequestMiddleware {
1521
private val overrides = HeadersBuilder()
1622
private val additional = HeadersBuilder()
1723
private val conditionallySet = HeadersBuilder()
1824

25+
init {
26+
override.forEach { (key, value) -> set(key, value) }
27+
append.forEach { (key, value) -> append(key, value) }
28+
setMissing.forEach { (key, value) -> setIfMissing(key, value) }
29+
}
30+
1931
/**
2032
* Set a header in the request, overriding any existing key of the same name
2133
*/
@@ -33,29 +45,21 @@ class MutateHeaders : Feature {
3345
*/
3446
fun setIfMissing(name: String, value: String) = conditionallySet.append(name, value)
3547

36-
companion object Feature : HttpClientFeatureFactory<MutateHeaders, MutateHeaders> {
37-
override val key: FeatureKey<MutateHeaders> = FeatureKey("AddHeaders")
38-
override fun create(block: MutateHeaders.() -> Unit): MutateHeaders =
39-
MutateHeaders().apply(block)
40-
}
48+
override suspend fun modifyRequest(req: SdkHttpRequest): SdkHttpRequest {
49+
additional.entries().forEach { (key, values) ->
50+
req.subject.headers.appendAll(key, values)
51+
}
4152

42-
override fun <I, O> install(operation: SdkHttpOperation<I, O>) {
43-
operation.execution.mutate.intercept { req, next ->
44-
additional.entries().forEach { (key, values) ->
45-
req.subject.headers.appendAll(key, values)
46-
}
53+
overrides.entries().forEach { (key, values) ->
54+
req.subject.headers[key] = values.last()
55+
}
4756

48-
overrides.entries().forEach { (key, values) ->
57+
conditionallySet.entries().forEach { (key, values) ->
58+
if (!req.subject.headers.contains(key)) {
4959
req.subject.headers[key] = values.last()
5060
}
51-
52-
conditionallySet.entries().forEach { (key, values) ->
53-
if (!req.subject.headers.contains(key)) {
54-
req.subject.headers[key] = values.last()
55-
}
56-
}
57-
58-
next.call(req)
5961
}
62+
63+
return req
6064
}
6165
}

runtime/protocol/http/common/src/aws/smithy/kotlin/runtime/http/middleware/ResolveEndpoint.kt

Lines changed: 11 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -5,44 +5,23 @@
55

66
package aws.smithy.kotlin.runtime.http.middleware
77

8-
import aws.smithy.kotlin.runtime.http.*
98
import aws.smithy.kotlin.runtime.http.operation.*
109
import aws.smithy.kotlin.runtime.util.InternalApi
1110

1211
/**
13-
* Http feature for resolving the service endpoint.
12+
* Http middleware for resolving the service endpoint.
1413
*/
1514
@InternalApi
16-
public class ResolveEndpoint(
17-
config: Config
18-
) : Feature {
19-
20-
private val resolver: EndpointResolver = requireNotNull(config.resolver) { "EndpointResolver must not be null" }
21-
22-
public class Config {
23-
/**
24-
* The resolver to use
25-
*/
26-
public var resolver: EndpointResolver? = null
27-
}
28-
29-
public companion object Feature : HttpClientFeatureFactory<Config, ResolveEndpoint> {
30-
override val key: FeatureKey<ResolveEndpoint> = FeatureKey("ResolveEndpoint")
31-
32-
override fun create(block: Config.() -> Unit): ResolveEndpoint {
33-
val config = Config().apply(block)
34-
return ResolveEndpoint(config)
35-
}
36-
}
37-
38-
override fun <I, O> install(operation: SdkHttpOperation<I, O>) {
39-
operation.execution.mutate.intercept { req, next ->
40-
val endpoint = resolver.resolve()
41-
setRequestEndpoint(req, endpoint)
42-
val logger = req.context.getLogger("ResolveEndpoint")
43-
logger.debug { "resolved endpoint: $endpoint" }
44-
next.call(req)
45-
}
15+
class ResolveEndpoint(
16+
private val resolver: EndpointResolver
17+
) : ModifyRequestMiddleware {
18+
19+
override suspend fun modifyRequest(req: SdkHttpRequest): SdkHttpRequest {
20+
val endpoint = resolver.resolve()
21+
setRequestEndpoint(req, endpoint)
22+
val logger = req.context.getLogger("ResolveEndpoint")
23+
logger.debug { "resolved endpoint: $endpoint" }
24+
return req
4625
}
4726
}
4827

0 commit comments

Comments
 (0)