Skip to content

Commit db1d4d4

Browse files
authored
fix: http trace interceptor not keeping original HttpBody properties and LogMode not behaving as expected (#1035)
1 parent 6f3f642 commit db1d4d4

File tree

6 files changed

+54
-19
lines changed

6 files changed

+54
-19
lines changed
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
{
2+
"id": "00960dac-40d8-4b6d-a3a7-12eb944d5753",
3+
"type": "bugfix",
4+
"description": "Fix `PutObject` request when `LogMode` is set to `LogRequestWithBody`",
5+
"issues": [
6+
"awslabs/aws-sdk-kotlin#1198"
7+
]
8+
}
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
{
2+
"id": "4ecf93e9-b11e-43b8-9c7c-ed7753916c64",
3+
"type": "bugfix",
4+
"description": "Fix `LogRequestWithBody` and `LogResponseWithBody` imply `LogRequest` and `LogResponse` respectively"
5+
}

runtime/protocol/http/common/src/aws/smithy/kotlin/runtime/http/request/HttpRequestBuilder.kt

Lines changed: 17 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@ package aws.smithy.kotlin.runtime.http.request
66

77
import aws.smithy.kotlin.runtime.InternalApi
88
import aws.smithy.kotlin.runtime.http.*
9-
import aws.smithy.kotlin.runtime.http.content.ByteArrayContent
109
import aws.smithy.kotlin.runtime.io.*
1110
import aws.smithy.kotlin.runtime.net.url.Url
1211
import aws.smithy.kotlin.runtime.util.CanDeepCopy
@@ -113,26 +112,35 @@ public suspend fun dumpRequest(request: HttpRequestBuilder, dumpBody: Boolean):
113112
val skip = setOf("Host", "Content-Length")
114113
request.headers.entries()
115114
.filterNot { it.key in skip }
116-
.forEach {
117-
buffer.writeUtf8(it.value.joinToString(separator = ";", prefix = "${it.key}: ", postfix = "\r\n"))
115+
.forEach { (key, values) ->
116+
buffer.writeUtf8(values.joinToString(separator = ";", prefix = "$key: ", postfix = "\r\n"))
118117
}
119118

120119
buffer.writeUtf8("\r\n")
121-
122120
if (dumpBody) {
123121
when (val body = request.body) {
124122
is HttpBody.Bytes -> buffer.write(body.bytes())
125123
is HttpBody.ChannelContent, is HttpBody.SourceContent -> {
126124
// consume the stream and replace the body
127-
val content = body.readAll()
128-
if (content != null) {
129-
buffer.write(content)
130-
request.body = ByteArrayContent(content)
131-
}
125+
request.body = copyHttpBody(request.body, buffer)
132126
}
133127
is HttpBody.Empty -> { } // nothing to dump
134128
}
135129
}
136130

137131
return buffer.readUtf8()
138132
}
133+
134+
private suspend fun copyHttpBody(original: HttpBody, buffer: SdkBuffer): HttpBody {
135+
val content = original.readAll() ?: return HttpBody.Empty
136+
buffer.write(content)
137+
return object : HttpBody.SourceContent() {
138+
override fun readFrom(): SdkSource =
139+
content.source()
140+
141+
// even though we know the content length we preserve the original in case it was chunked encoding
142+
override val contentLength: Long? = original.contentLength
143+
override val isOneShot: Boolean = original.isOneShot
144+
override val isDuplex: Boolean = original.isDuplex
145+
}
146+
}

runtime/protocol/http/common/test/aws/smithy/kotlin/runtime/http/HttpRequestBuilderTest.kt

Lines changed: 11 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -18,9 +18,7 @@ import aws.smithy.kotlin.runtime.net.Host
1818
import aws.smithy.kotlin.runtime.net.Scheme
1919
import aws.smithy.kotlin.runtime.net.url.Url
2020
import kotlinx.coroutines.test.runTest
21-
import kotlin.test.Test
22-
import kotlin.test.assertEquals
23-
import kotlin.test.assertTrue
21+
import kotlin.test.*
2422

2523
class HttpRequestBuilderTest {
2624
@Test
@@ -67,13 +65,18 @@ class HttpRequestBuilderTest {
6765
}
6866

6967
assertTrue(builder.body is HttpBody.ChannelContent)
70-
dumpRequest(builder, false)
68+
val actualNoContent = dumpRequest(builder, false)
69+
val expectedNoContent = "GET /debug/test?foo=bar\r\nHost: test.amazon.com\r\nContent-Length: ${content.length}\r\nx-baz: quux;qux\r\n\r\n"
7170
assertTrue(builder.body is HttpBody.ChannelContent)
71+
assertEquals(expectedNoContent, actualNoContent)
7272

73-
val actual = dumpRequest(builder, true)
74-
assertTrue(builder.body is HttpBody.Bytes)
75-
val expected = "GET /debug/test?foo=bar\r\nHost: test.amazon.com\r\nContent-Length: ${content.length}\r\nx-baz: quux;qux\r\n\r\n$content"
76-
assertEquals(expected, actual)
73+
val actualWithContent = dumpRequest(builder, true)
74+
assertTrue(builder.body is HttpBody.SourceContent)
75+
val expectedWithContent = "$expectedNoContent$content"
76+
assertEquals(expectedWithContent, actualWithContent)
77+
78+
val actualReplacedContent = assertNotNull(builder.body.readAll()?.decodeToString())
79+
assertEquals(content, actualReplacedContent)
7780
}
7881

7982
@Test

runtime/smithy-client/common/src/aws/smithy/kotlin/runtime/client/LogMode.kt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ public sealed class LogMode(private val mask: Int) {
3434
/**
3535
* Log the request details as well as the body if possible
3636
*/
37-
public object LogRequestWithBody : LogMode(0x02) {
37+
public object LogRequestWithBody : LogMode(0x03) {
3838
override fun toString(): String = "LogRequestWithBody"
3939
}
4040

@@ -48,7 +48,7 @@ public sealed class LogMode(private val mask: Int) {
4848
/**
4949
* Log the response details as well as the body if possible
5050
*/
51-
public object LogResponseWithBody : LogMode(0x08) {
51+
public object LogResponseWithBody : LogMode(0x0C) {
5252
override fun toString(): String = "LogResponseWithBody"
5353
}
5454

runtime/smithy-client/common/test/aws/smithy/kotlin/runtime/client/LogModeTest.kt

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,4 +55,15 @@ class LogModeTest {
5555
fun testUnsupportedCompositeLogMode() {
5656
assertFailsWith<ClientException> { LogMode.fromString("LogRequest|UnsupportedLogMode") }
5757
}
58+
59+
@Test
60+
fun testWithBodyImpliesWithout() {
61+
// LogRequestWithBody implies LogRequest
62+
assertTrue(LogMode.LogRequestWithBody.isEnabled(LogMode.LogRequest))
63+
assertFalse(LogMode.LogRequestWithBody.isEnabled(LogMode.LogResponse))
64+
65+
// LogResponseWithBody implies LogResponse
66+
assertTrue(LogMode.LogResponseWithBody.isEnabled(LogMode.LogResponse))
67+
assertFalse(LogMode.LogResponseWithBody.isEnabled(LogMode.LogRequest))
68+
}
5869
}

0 commit comments

Comments
 (0)