我正在使用Kotlin协程重写一些Java Vertx异步代码以用于学习目的。但是,当我尝试测试一个简单的HTTP调用时,基于协程的测试永远挂起,我真的不明白问题出在哪里。这是一个复制者:
@RunWith(VertxUnitRunner::class)
class HelloWorldTest {
private val vertx: Vertx = Vertx.vertx()
@Before
fun setUp(context: TestContext) {
// HelloWorldVerticle is a simple http server that replies "Hello, World!" to whatever call
vertx.deployVerticle(HelloWorldVerticle::class.java!!.getName(), context.asyncAssertSuccess())
}
// ORIGINAL ASYNC TEST HERE. IT WORKS AS EXPECTED
@Test
fun testAsync(context: TestContext) {
val atc = context.async()
vertx.createHttpClient().getNow(8080, "localhost", "/") { response ->
response.handler { body ->
context.assertTrue(body.toString().equals("Hello, World!"))
atc.complete()
}
}
}
// First attempt, it hangs forever, the response is never called
@Test
fun testSync1(context: TestContext) = runBlocking<Unit> {
val atc = context.async()
val body = await<HttpClientResponse> {
vertx.createHttpClient().getNow(8080, "localhost", "/", { response -> response.handler {it}} )
}
context.assertTrue(body.toString().equals("Hello, World!"))
atc.complete()
}
// Second attempt, it hangs forever, the response is never called
@Test
fun testSync2(context: TestContext) = runBlocking<Unit> {
val atc = context.async()
val response = await<HttpClientResponse> {
vertx.createHttpClient().getNow(8080, "localhost", "/", it )
}
response.handler { body ->
context.assertTrue(body.toString().equals("Hello, World!"))
atc.complete()
}
}
suspend fun <T> await(callback: (Handler<T>) -> Unit) =
suspendCoroutine<T> { cont ->
callback(Handler { result: T ->
cont.resume(result)
})
}
}
每个人都能找出问题吗?
答案 0 :(得分:1)
在我看来,您的代码有几个问题:
runBlocking
内执行代码,所以阻止事件循环完成请求。 HttpClienctResponse::bodyHandler
方法而不是HttpClientResponse::handler
,因为处理程序可能会收到部分数据。 这是一个可行的替代解决方案:
import io.vertx.core.AbstractVerticle
import io.vertx.core.Future
import io.vertx.core.Handler
import io.vertx.core.Vertx
import io.vertx.core.buffer.Buffer
import io.vertx.core.http.HttpClientResponse
import kotlin.coroutines.experimental.Continuation
import kotlin.coroutines.experimental.EmptyCoroutineContext
import kotlin.coroutines.experimental.startCoroutine
import kotlin.coroutines.experimental.suspendCoroutine
inline suspend fun <T> await(crossinline callback: (Handler<T>) -> Unit) =
suspendCoroutine<T> { cont ->
callback(Handler { result: T ->
cont.resume(result)
})
}
fun <T : Any> async(code: suspend () -> T) = Future.future<T>().apply {
code.startCoroutine(object : Continuation<T> {
override val context = EmptyCoroutineContext
override fun resume(value: T) = complete()
override fun resumeWithException(exception: Throwable) = fail(exception)
})
}
fun main(args: Array<String>) {
async {
val vertx: Vertx = Vertx.vertx()
//0. take the current context
val ctx = vertx.getOrCreateContext()
//1. deploy the http server
await<Unit> { cont ->
vertx.deployVerticle(object : AbstractVerticle() {
override fun start() {
vertx.createHttpServer()
.requestHandler { it.response().end("Hello World") }
.listen(7777) { ctx.runOnContext { cont.handle(Unit) } }
//note that it is important tp complete the handler in the correct context
}
})
}
//2. send request
val response: HttpClientResponse = await { vertx.createHttpClient().getNow(7777, "localhost", "/", it) }
//3. await response
val body = await<Buffer> { response.bodyHandler(it) }
println("received $body")
}
}