Skip to content

Commit 4b925bb

Browse files
authored
fix: allow Content-Length=0 headers in the CRT HTTP engine (#819)
1 parent e8fe25b commit 4b925bb

File tree

5 files changed

+76
-8
lines changed

5 files changed

+76
-8
lines changed
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
{
2+
"id": "85c91e25-fbc9-4416-a8bc-54991580a802",
3+
"type": "bugfix",
4+
"description": "Allow requests with zero content length in the CRT HTTP engine",
5+
"issues": [
6+
"https://github.com/awslabs/smithy-kotlin/issues/818"
7+
]
8+
}

runtime/protocol/http-client-engines/http-client-engine-crt/common/src/aws/smithy/kotlin/runtime/http/engine/crt/RequestUtil.kt

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -60,11 +60,7 @@ internal fun HttpRequest.toCrtRequest(callContext: CoroutineContext): aws.sdk.ko
6060
headers.forEach { key, values -> appendAll(key, values) }
6161
}
6262

63-
val bodyLen = body.contentLength
64-
val contentLength = when {
65-
bodyLen != null -> if (bodyLen > 0) bodyLen.toString() else null
66-
else -> headers[CONTENT_LENGTH_HEADER]
67-
}
63+
val contentLength = body.contentLength?.toString() ?: headers[CONTENT_LENGTH_HEADER]
6864
contentLength?.let { crtHeaders.append(CONTENT_LENGTH_HEADER, it) }
6965

7066
return aws.sdk.kotlin.crt.http.HttpRequest(method.name, url.encodedPath, crtHeaders.build(), bodyStream)

runtime/protocol/http-client-engines/http-client-engine-crt/common/test/aws/smithy/kotlin/runtime/http/engine/crt/RequestConversionTest.kt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,7 @@ class RequestConversionTest {
9292
}
9393

9494
@Test
95-
fun testEngineDoesNotAddContentLengthHeaderForEmptyBody() {
95+
fun testEngineAddsContentLengthHeaderForEmptyBody() {
9696
val request = HttpRequest(
9797
HttpMethod.POST,
9898
Url.parse("https://test.aws.com?foo=bar"),
@@ -102,7 +102,7 @@ class RequestConversionTest {
102102

103103
val testContext = EmptyCoroutineContext + Job()
104104
val crtRequest = request.toCrtRequest(testContext)
105-
assertFalse(crtRequest.headers.contains("Content-Length"))
105+
assertEquals("0", crtRequest.headers["Content-Length"])
106106
}
107107

108108
@Test

runtime/protocol/http-client-engines/http-client-engine-okhttp/jvm/src/aws/smithy/kotlin/runtime/http/engine/okhttp/OkHttpUtils.kt

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,18 @@ internal fun HttpRequest.toOkHttpRequest(
5757
when (val body = body) {
5858
is HttpBody.Empty -> ByteArray(0).toRequestBody(null, 0, 0)
5959
is HttpBody.Bytes -> body.bytes().let { it.toRequestBody(null, 0, it.size) }
60-
is HttpBody.SourceContent, is HttpBody.ChannelContent -> StreamingRequestBody(body, callContext)
60+
is HttpBody.SourceContent, is HttpBody.ChannelContent -> {
61+
val updatedBody: HttpBody = headers["Content-Length"]?.let {
62+
if (body.contentLength == null || body.contentLength == -1L) {
63+
when (body) {
64+
is HttpBody.SourceContent -> body.readFrom().toHttpBody(it.toLong())
65+
is HttpBody.ChannelContent -> body.readFrom().toHttpBody(it.toLong())
66+
else -> null
67+
}
68+
} else { null }
69+
} ?: body
70+
StreamingRequestBody(updatedBody, callContext)
71+
}
6172
}
6273
} else {
6374
// assert we don't silently ignore a body even though one is unexpected here

runtime/protocol/http-client-engines/http-client-engine-okhttp/jvm/test/aws/smithy/kotlin/runtime/http/engine/okhttp/OkHttpRequestTest.kt

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -135,4 +135,57 @@ class OkHttpRequestTest {
135135
actualBody.writeTo(buffer)
136136
assertEquals(content, buffer.readUtf8())
137137
}
138+
139+
@Test
140+
fun itUsesContentLengthHeaderWhenContentLengthIsUnknown() {
141+
val url = Url.parse("https://aws.amazon.com")
142+
val content = "Hello OkHttp from HttpBody.Streaming".repeat(1024)
143+
val contentBytes = content.encodeToByteArray()
144+
val chan = SdkByteReadChannel(contentBytes)
145+
val body = object : HttpBody.ChannelContent() {
146+
override val contentLength: Long = -1
147+
override fun readFrom(): SdkByteReadChannel = chan
148+
}
149+
val expectedContentLength = "5"
150+
val headers = HeadersBuilder().apply {
151+
append("Content-Length", expectedContentLength)
152+
}.build()
153+
154+
val request = HttpRequest(HttpMethod.POST, url, headers, body)
155+
val execContext = ExecutionContext()
156+
val actual = request.toOkHttpRequest(execContext, EmptyCoroutineContext)
157+
158+
val actualBody = assertNotNull(actual.body)
159+
assertEquals(expectedContentLength.toLong(), actualBody.contentLength())
160+
161+
val buffer = Buffer()
162+
actualBody.writeTo(buffer)
163+
assertEquals(content, buffer.readUtf8())
164+
}
165+
166+
@Test
167+
fun itDoesNotUseContentLengthHeaderWhenContentLengthIsDefined() {
168+
val url = Url.parse("https://aws.amazon.com")
169+
val content = "Hello OkHttp from HttpBody.Streaming".repeat(1024)
170+
val contentBytes = content.encodeToByteArray()
171+
val chan = SdkByteReadChannel(contentBytes)
172+
val body = object : HttpBody.ChannelContent() {
173+
override val contentLength: Long = contentBytes.size.toLong()
174+
override fun readFrom(): SdkByteReadChannel = chan
175+
}
176+
val headers = HeadersBuilder().apply {
177+
append("Content-Length", "9")
178+
}.build()
179+
180+
val request = HttpRequest(HttpMethod.POST, url, headers, body)
181+
val execContext = ExecutionContext()
182+
val actual = request.toOkHttpRequest(execContext, EmptyCoroutineContext)
183+
184+
val actualBody = assertNotNull(actual.body)
185+
assertEquals(request.body.contentLength, actualBody.contentLength())
186+
187+
val buffer = Buffer()
188+
actualBody.writeTo(buffer)
189+
assertEquals(content, buffer.readUtf8())
190+
}
138191
}

0 commit comments

Comments
 (0)