Ktor 1.3.2, TestApplicationEngine
When running a test that sends post with multipart it runs forever and never stops.
No problem when server really runs. It's just the test.
To Reproduce
Server:
fun main(args: Array<String>): Unit = io.ktor.server.netty.EngineMain.main(args)
@Suppress("unused") // Referenced in application.conf
@kotlin.jvm.JvmOverloads
fun Application.module(testing: Boolean = false) {
install(ContentNegotiation) {
}
routing {
post("/") {
val mp = try {
call.receiveMultipart()
} catch (e: Exception) {
call.application.log.error("Getting multipart error"")
null
}
if (mp == null) call.respond(HttpStatusCode.BadRequest, "Null value")
else call.respond(HttpStatusCode.OK,"OK")
}
}
}
Test:
internal class ApplicationKtTest {
private val boundary = "***bbb***"
private val sysTempDir = System.getProperty("java.io.tmpdir") ?: "/tmp"
private val multipart = listOf(PartData.FileItem({ byteArrayOf(1, 2, 3).inputStream().asInput() }, {}, headersOf(
HttpHeaders.ContentDisposition,
ContentDisposition.File
.withParameter(ContentDisposition.Parameters.Name, "file")
.withParameter(ContentDisposition.Parameters.FileName, "test.jpg")
.toString()
)))
@Test
fun testUploadApplication() = testApp {
handlePost("/", boundary, multipart).apply {
println("============= RESPONSE ====================")
println(response.content)
println("=================================")
}
}
}
private fun TestApplicationEngine.handlePost(uri: String,
boundary: String,
multipart: List<PartData.FileItem>,
setup: TestApplicationRequest.() -> Unit = {}
): TestApplicationCall {
return handleRequest(method = HttpMethod.Post, uri = uri) {
addHeader(HttpHeaders.ContentType,
ContentType.MultiPart.FormData.withParameter("boundary", boundary).toString()
)
setBody(boundary, multipart)
setup()
}
}
private fun testApp(callback: TestApplicationEngine.() -> Unit): Unit {
withTestApplication({ module(true) }, callback)
}
The test runs forever and gives no result. if addHeader and setBody are commented there is a response.
Expected behavior
The test should print OK
Repository
If you just want to clone a test repository.
If the multipart is used it works
post("/") {
var fileName = ""
val mp = try {
val mp = call.receiveMultipart()
mp.forEachPart { part ->
when (part) {
is PartData.FileItem -> fileName = part.originalFileName ?: "(no file name)"
else -> call.application.log.info("Not a file item: ${part.name}")
}
part.dispose
}
} catch (e: Exception) {
call.application.log.error("Getting multipart error", e)
null
}
if (mp == null) call.respond(HttpStatusCode.BadRequest, "Null value")
else call.respond(HttpStatusCode.OK,"File name: $fileName")
}
I have the same issue with my HTTP PATCH request.
@razvn do you have a workaround? I didn't understand your second comment.
@neelkamath just doing
call.receiveMultipart().forEachPart { it.dispose }
is enough to make it work.
There are currently two workarounds. Note that these snippets are pseudocode.
@razvn's workaround is to use call.receiveMultipart().forEachPart().
So, this code won't work:
call.receiveMultipart().readPart()!!.dispose()
call.respond(HttpStatusCode.NoContent)
But this code will work:
call.receiveMultipart().forEachPart { it.dispose() }
call.respond(HttpStatusCode.NoContent)
I was wondering if this was because the call waits for some sort of EOF to know when to stop executing, but couldn't find a close method on call.receiveMultipart(). However, I was able to get rid of the call to call.receiveMultipart().forEachPart(). For example:
call.receiveMultipart().readPart()!!.dispose()
multipart.readPart() // Read it an extra time even though there's only one part.
call.respond(HttpStatusCode.NoContent)
Please check the following ticket on YouTrack for follow-ups to this issue. GitHub issues will be closed in the coming weeks.
Fixed in 1.5.2