Skip to content

Commit 7c32f4e

Browse files
authored
KTOR-4511 Ignore ByteReadChannel for server response and client request (#3067)
1 parent 8214a32 commit 7c32f4e

File tree

18 files changed

+254
-13
lines changed

18 files changed

+254
-13
lines changed

ktor-client/ktor-client-core/common/src/io/ktor/client/plugins/DefaultTransform.kt

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -43,9 +43,8 @@ public fun HttpClient.defaultTransformers() {
4343
override fun readFrom(): ByteReadChannel = body
4444
}
4545
is OutgoingContent -> body
46-
else -> null
46+
else -> platformRequestDefaultTransform(contentType, context, body)
4747
}
48-
4948
if (content != null) {
5049
context.headers.remove(HttpHeaders.ContentType)
5150
proceedWith(content)
@@ -111,7 +110,13 @@ public fun HttpClient.defaultTransformers() {
111110
}
112111
}
113112

114-
platformDefaultTransformers()
113+
platformResponseDefaultTransformers()
115114
}
116115

117-
internal expect fun HttpClient.platformDefaultTransformers()
116+
internal expect fun platformRequestDefaultTransform(
117+
contentType: ContentType?,
118+
context: HttpRequestBuilder,
119+
body: Any
120+
): OutgoingContent?
121+
122+
internal expect fun HttpClient.platformResponseDefaultTransformers()

ktor-client/ktor-client-core/js/src/io/ktor/client/plugins/DefaultTransformJs.kt

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,5 +5,14 @@
55
package io.ktor.client.plugins
66

77
import io.ktor.client.*
8+
import io.ktor.client.request.*
9+
import io.ktor.http.*
10+
import io.ktor.http.content.*
811

9-
internal actual fun HttpClient.platformDefaultTransformers() {}
12+
internal actual fun platformRequestDefaultTransform(
13+
contentType: ContentType?,
14+
context: HttpRequestBuilder,
15+
body: Any
16+
): OutgoingContent? = null
17+
18+
internal actual fun HttpClient.platformResponseDefaultTransformers() {}

ktor-client/ktor-client-core/jvm/src/io/ktor/client/plugins/DefaultTransformersJvm.kt

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,15 +5,18 @@
55
package io.ktor.client.plugins
66

77
import io.ktor.client.*
8+
import io.ktor.client.request.*
89
import io.ktor.client.statement.*
10+
import io.ktor.http.*
11+
import io.ktor.http.content.*
912
import io.ktor.util.*
1013
import io.ktor.utils.io.*
1114
import io.ktor.utils.io.jvm.javaio.*
1215
import kotlinx.coroutines.*
1316
import java.io.*
1417

1518
@OptIn(InternalAPI::class)
16-
internal actual fun HttpClient.platformDefaultTransformers() {
19+
internal actual fun HttpClient.platformResponseDefaultTransformers() {
1720
responsePipeline.intercept(HttpResponsePipeline.Parse) { (info, body) ->
1821
if (body !is ByteReadChannel) return@intercept
1922
when (info.type) {
@@ -35,3 +38,16 @@ internal actual fun HttpClient.platformDefaultTransformers() {
3538
}
3639
}
3740
}
41+
42+
internal actual fun platformRequestDefaultTransform(
43+
contentType: ContentType?,
44+
context: HttpRequestBuilder,
45+
body: Any
46+
): OutgoingContent? = when (body) {
47+
is InputStream -> object : OutgoingContent.ReadChannelContent() {
48+
override val contentLength = context.headers[HttpHeaders.ContentLength]?.toLong()
49+
override val contentType: ContentType = contentType ?: ContentType.Application.OctetStream
50+
override fun readFrom(): ByteReadChannel = body.toByteReadChannel()
51+
}
52+
else -> null
53+
}

ktor-client/ktor-client-core/posix/src/io/ktor/client/plugins/DefaultTransformIos.kt

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,15 @@
55
package io.ktor.client.plugins
66

77
import io.ktor.client.*
8+
import io.ktor.client.request.*
9+
import io.ktor.http.*
10+
import io.ktor.http.content.*
811

9-
internal actual fun HttpClient.platformDefaultTransformers() {
12+
internal actual fun platformRequestDefaultTransform(
13+
contentType: ContentType?,
14+
context: HttpRequestBuilder,
15+
body: Any
16+
): OutgoingContent? = null
17+
18+
internal actual fun HttpClient.platformResponseDefaultTransformers() {
1019
}

ktor-client/ktor-client-plugins/ktor-client-content-negotiation/common/src/io/ktor/client/plugins/contentnegotiation/ContentNegotiation.kt

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,10 +10,14 @@ import io.ktor.client.request.*
1010
import io.ktor.client.statement.*
1111
import io.ktor.client.utils.*
1212
import io.ktor.http.*
13+
import io.ktor.http.content.*
1314
import io.ktor.serialization.*
1415
import io.ktor.util.*
1516
import io.ktor.utils.io.*
1617
import io.ktor.utils.io.charsets.*
18+
import kotlin.reflect.*
19+
20+
internal expect val DefaultIgnoredTypes: Set<KClass<*>>
1721

1822
/**
1923
* A plugin that serves two primary purposes:
@@ -95,9 +99,12 @@ public class ContentNegotiation internal constructor(
9599
val registrations = plugin.registrations
96100
registrations.forEach { context.accept(it.contentTypeToSend) }
97101

102+
if (subject is OutgoingContent || DefaultIgnoredTypes.any { it.isInstance(payload) }) {
103+
return@intercept
104+
}
98105
val contentType = context.contentType() ?: return@intercept
99106

100-
if (payload is Unit || payload is EmptyContent) {
107+
if (payload is Unit) {
101108
context.headers.remove(HttpHeaders.ContentType)
102109
proceedWith(EmptyContent)
103110
return@intercept

ktor-client/ktor-client-plugins/ktor-client-content-negotiation/common/test/io/ktor/client/plugins/ContentNegotiationTests.kt

Lines changed: 30 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2014-2021 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license.
2+
* Copyright 2014-2022 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license.
33
*/
44

55
package io.ktor.client.plugins
@@ -14,6 +14,7 @@ import io.ktor.http.*
1414
import io.ktor.http.content.*
1515
import io.ktor.util.*
1616
import io.ktor.utils.io.*
17+
import io.ktor.utils.io.core.*
1718
import kotlin.test.*
1819

1920
class ContentNegotiationTests {
@@ -84,7 +85,7 @@ class ContentNegotiationTests {
8485
}
8586

8687
@Test
87-
fun testIgnoresByteReadChannel() {
88+
fun testReceiveByteReadChannel() {
8889
val contentType = ContentType("testing", "a")
8990
testWithEngine(MockEngine) {
9091
setupWithContentNegotiation {
@@ -103,6 +104,33 @@ class ContentNegotiationTests {
103104
}
104105
}
105106

107+
@Test
108+
fun testSendByteReadChannel() = testWithEngine(MockEngine) {
109+
config {
110+
install(ContentNegotiation) {
111+
register(ContentType.Application.Json, TestContentConverter()) {
112+
deserializeFn = { _, _, _ -> fail() }
113+
serializeFn = { _, _, _, _ -> fail() }
114+
}
115+
}
116+
engine {
117+
addHandler {
118+
val text = (it.body as OutgoingContent.ReadChannelContent).readFrom().readRemaining().readText()
119+
respond(text)
120+
}
121+
}
122+
}
123+
124+
test { client ->
125+
val response = client.post("/post") {
126+
val channel = ByteReadChannel("""{"x": 123}""".toByteArray())
127+
contentType(ContentType.Application.Json)
128+
setBody(channel)
129+
}.bodyAsText()
130+
assertEquals("""{"x": 123}""", response)
131+
}
132+
}
133+
106134
@Test
107135
fun replaceContentTypeInRequestPipeline(): Unit = testWithEngine(MockEngine) {
108136
val bodyContentType = ContentType("testing", "a")
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
/*
2+
* Copyright 2014-2022 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license.
3+
*/
4+
5+
package io.ktor.client.plugins.contentnegotiation
6+
7+
import io.ktor.http.content.*
8+
import io.ktor.utils.io.*
9+
import kotlin.reflect.*
10+
11+
internal actual val DefaultIgnoredTypes: Set<KClass<*>> =
12+
mutableSetOf(OutgoingContent::class, ByteReadChannel::class, ByteArray::class)
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
/*
2+
* Copyright 2014-2022 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license.
3+
*/
4+
5+
package io.ktor.client.plugins.contentnegotiation
6+
7+
import io.ktor.http.content.*
8+
import io.ktor.utils.io.*
9+
import java.io.*
10+
import kotlin.reflect.*
11+
12+
internal actual val DefaultIgnoredTypes: Set<KClass<*>> =
13+
mutableSetOf(OutgoingContent::class, ByteReadChannel::class, InputStream::class, ByteArray::class)
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
/*
2+
* Copyright 2014-2022 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license.
3+
*/
4+
5+
package io.ktor.client.plugins.contentnegotiation
6+
7+
import io.ktor.http.content.*
8+
import io.ktor.utils.io.*
9+
import kotlin.reflect.*
10+
11+
internal actual val DefaultIgnoredTypes: Set<KClass<*>> =
12+
mutableSetOf(OutgoingContent::class, ByteReadChannel::class, ByteArray::class)
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
/*
2+
* Copyright 2014-2022 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license.
3+
*/
4+
5+
package io.ktor.client.tests
6+
7+
import io.ktor.client.call.*
8+
import io.ktor.client.engine.mock.*
9+
import io.ktor.client.request.*
10+
import io.ktor.client.statement.*
11+
import io.ktor.client.tests.utils.*
12+
import io.ktor.http.*
13+
import io.ktor.http.content.*
14+
import io.ktor.utils.io.*
15+
import java.io.*
16+
import kotlin.test.*
17+
18+
/*
19+
* Copyright 2014-2022 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license.
20+
*/
21+
22+
class DefaultTransformTest {
23+
24+
@Test
25+
fun testSendInputStream() = testWithEngine(MockEngine) {
26+
config {
27+
engine {
28+
addHandler {
29+
val text = (it.body as OutgoingContent.ReadChannelContent).readFrom().readRemaining().readText()
30+
respond(text)
31+
}
32+
}
33+
}
34+
35+
test { client ->
36+
val response = client.post("/post") {
37+
val stream = ByteArrayInputStream("""{"x": 123}""".toByteArray())
38+
contentType(ContentType.Application.Json)
39+
setBody(stream)
40+
}.bodyAsText()
41+
assertEquals("""{"x": 123}""", response)
42+
}
43+
}
44+
45+
@Test
46+
fun testReceiveInputStream() = testWithEngine(MockEngine) {
47+
config {
48+
engine {
49+
addHandler {
50+
respond("""{"x": 123}""")
51+
}
52+
}
53+
}
54+
55+
test { client ->
56+
val response = client.get("/").body<InputStream>()
57+
assertEquals("""{"x": 123}""", response.bufferedReader().readText())
58+
}
59+
}
60+
}

0 commit comments

Comments
 (0)