Retrofit-Vertx con RxJava2 en Kotlin IllegalStateException message == null

Estoy construyendo una aplicación muy simple en Kotlin con Vertx y RxJava 2 (RxKotlin), usando el marco Kovert REST y Retrofit. Tengo el adaptador retrofit-vertx y el adaptador Retrofit RxJava2. Puedo devolver una list arbitraria de mi método listUndergroundStations() , pero cada vez que bash cargar desde la API remota, listUndergroundStations() el siguiente error:

 Jun 23, 2017 2:16:29 PM uk.amb85.rxweb.api.UndergroundRestController SEVERE: HTTP CODE 500 - /api/underground/stations - java.io.IOException: java.lang.IllegalStateException: message == null java.lang.RuntimeException: java.io.IOException: java.lang.IllegalStateException: message == null at io.reactivex.internal.util.ExceptionHelper.wrapOrThrow(ExceptionHelper.java:45) at io.reactivex.internal.observers.BlockingMultiObserver.blockingGet(BlockingMultiObserver.java:91) at io.reactivex.Single.blockingGet(Single.java:2148) at uk.amb85.rxweb.api.UndergroundRestController$listUndergroundStations$1.invoke(UndergroundRestController.kt:35) at uk.amb85.rxweb.api.UndergroundRestController$listUndergroundStations$1.invoke(UndergroundRestController.kt:13) at nl.komponents.kovenant.TaskPromise$wrapper$1.invoke(promises-jvm.kt:138) at nl.komponents.kovenant.TaskPromise$wrapper$1.invoke(promises-jvm.kt:130) at nl.komponents.kovenant.NonBlockingDispatcher$ThreadContext.run(dispatcher-jvm.kt:327) at java.lang.Thread.run(Thread.java:748) Caused by: java.io.IOException: java.lang.IllegalStateException: message == null at com.julienviet.retrofit.vertx.VertxCallFactory$VertxCall.lambda$enqueue$0(VertxCallFactory.java:90) at io.vertx.core.impl.FutureImpl.tryFail(FutureImpl.java:170) at io.vertx.core.http.impl.HttpClientResponseImpl.handleException(HttpClientResponseImpl.java:270) at io.vertx.core.http.impl.HttpClientResponseImpl.handleEnd(HttpClientResponseImpl.java:259) at io.vertx.core.http.impl.ClientConnection.handleResponseEnd(ClientConnection.java:361) at io.vertx.core.http.impl.ClientHandler.doMessageReceived(ClientHandler.java:80) at io.vertx.core.http.impl.ClientHandler.doMessageReceived(ClientHandler.java:38) at io.vertx.core.http.impl.VertxHttpHandler.lambda$channelRead$0(VertxHttpHandler.java:71) at io.vertx.core.impl.ContextImpl.lambda$wrapTask$2(ContextImpl.java:335) at io.vertx.core.impl.ContextImpl.executeFromIO(ContextImpl.java:193) at io.vertx.core.http.impl.VertxHttpHandler.channelRead(VertxHttpHandler.java:71) at io.vertx.core.net.impl.VertxHandler.channelRead(VertxHandler.java:122) at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:363) at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:349) at io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:341) at io.netty.channel.CombinedChannelDuplexHandler$DelegatingChannelHandlerContext.fireChannelRead(CombinedChannelDuplexHandler.java:435) at io.netty.handler.codec.ByteToMessageDecoder.fireChannelRead(ByteToMessageDecoder.java:293) at io.netty.handler.codec.ByteToMessageDecoder.channelRead(ByteToMessageDecoder.java:267) at io.netty.channel.CombinedChannelDuplexHandler.channelRead(CombinedChannelDuplexHandler.java:250) at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:363) at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:349) at io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:341) at io.netty.handler.ssl.SslHandler.unwrap(SslHandler.java:1228) at io.netty.handler.ssl.SslHandler.decode(SslHandler.java:1039) at io.netty.handler.codec.ByteToMessageDecoder.callDecode(ByteToMessageDecoder.java:411) at io.netty.handler.codec.ByteToMessageDecoder.channelRead(ByteToMessageDecoder.java:248) at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:363) at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:349) at io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:341) at io.netty.channel.DefaultChannelPipeline$HeadContext.channelRead(DefaultChannelPipeline.java:1334) at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:363) at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:349) at io.netty.channel.DefaultChannelPipeline.fireChannelRead(DefaultChannelPipeline.java:926) at io.netty.channel.nio.AbstractNioByteChannel$NioByteUnsafe.read(AbstractNioByteChannel.java:129) at io.netty.channel.nio.NioEventLoop.processSelectedKey(NioEventLoop.java:642) at io.netty.channel.nio.NioEventLoop.processSelectedKeysOptimized(NioEventLoop.java:565) at io.netty.channel.nio.NioEventLoop.processSelectedKeys(NioEventLoop.java:479) at io.netty.channel.nio.NioEventLoop.run(NioEventLoop.java:441) at io.netty.util.concurrent.SingleThreadEventExecutor$5.run(SingleThreadEventExecutor.java:858) ... 1 more Caused by: java.lang.IllegalStateException: message == null at okhttp3.Response$Builder.build(Response.java:431) at com.julienviet.retrofit.vertx.VertxCallFactory$VertxCall.lambda$null$1(VertxCallFactory.java:109) at io.vertx.core.http.impl.HttpClientResponseImpl$BodyHandler.notifyHandler(HttpClientResponseImpl.java:301) at io.vertx.core.http.impl.HttpClientResponseImpl.lambda$bodyHandler$0(HttpClientResponseImpl.java:193) at io.vertx.core.http.impl.HttpClientResponseImpl.handleEnd(HttpClientResponseImpl.java:257) ... 36 more 

No puedo entender por qué causa la IllegalStateException y la he buscado en Google. No creo que esté relacionado con Rx porque obtengo el mismo error si hago que el método devuelva Observable<List<UndergroundLine>> o incluso que me deshago de Rx por completo y devuelvo Call<List<UndergroundLine>> (ajustando el controller en consecuencia). Sin embargo, más allá de eso, ¡me estoy golpeando la cabeza contra la panetworking! ¿Alguien puede señalar el error de mis paths (además de poner un cojín debajo de mi cabeza)?

Vertículo principal:

 class ApiVerticle : AbstractVerticle() { override fun start(startFuture: Future<Void>?) { // Initialise injection. configureKodein() val apiRouter = configureRouter(vertx) vertx.createHttpServer() .requestHandler { apiRouter.accept(it) } .listen(8080) } private fun configureKodein() { Kodein.global.addImport(Kodein.Module { import(TflUndergroundService.module) }) } private fun configureRouter(vertx: Vertx): Router { val apiMountPoint = "api" val routerInit = fun Router.() { bindController(UndergroundRestController(), apiMountPoint) } val router = Router.router(vertx) initializedBy { router -> router.routerInit() } return router } } 

TflService:

 interface TflService { @GET("/Line/Mode/tube") fun getAllUndergroundLines(): Observable<UndergroundLine> @GET("/Line/{lineName}/StopPoints") fun getStationsForUndergroundLine( @Path("lineName") lineName: String ): Observable<UndergroundStation> @GET("/Line/{lineName}/Arrivals?stopPointId={stationNaptanId") fun getArrivalsFor( @Path("lineName") lineName: String, @Path("stationNaptanId") stationNaptanId: String ) : Observable<Arrival> } data class UndergroundLine(val id: String, val name: String) data class UndergroundStation(val naptanId: String, val commonName: String) data class Arrival( val platformName: String, val towards: String, val currentLocation: String, val expectedArrival: LocalDateTime) object TflUndergroundService { val module = Kodein.Module { val vertx: Vertx = Vertx.currentContext().owner() val client: HttpClient = vertx.createHttpClient() val jacksonMapper: ObjectMapper = ObjectMapper() jacksonMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false) val retrofit: Retrofit = Retrofit.Builder() .baseUrl("https://api.tfl.gov.uk/") .callFactory(VertxCallFactory(client)) .addCallAdapterFactory(RxJava2CallAdapterFactory.createAsync()) .addConverterFactory(JacksonConverterFactory.create(jacksonMapper)) .build() val tflService: TflService = retrofit.create(TflService::class.java) bind<TflService>() with instance(tflService) } } 

ApiKeySecunetworking (solo requiere que "appid" sea un parámetro):

 class ApiKeySecunetworking(private val routingContext: RoutingContext) : KodeinGlobalAware { val user: String = routingContext.request().getParam("appid") ?: throw HttpErrorUnauthorized() } 

El controller REST ofensivo (en Kovert, Promise se ejecuta en el hilo de trabajo Vertx):

 class UndergroundRestController(val undergroundService: TflService = Kodein.global.instance()) { fun ApiKeySecunetworking.listUndergroundStations(): Promise<List<UndergroundLine>, Exception> { //TODO: This is blocking, fix it!?? return task { undergroundService .getAllUndergroundLines() .doOnError { println(it) } .toList() .blockingGet() } } } 

build.gradle:

 mainClassName = "io.vertx.core.Launcher" def mainVerticleName = "uk.amb85.rxweb.verticles.ApiVerticle" def configurationFile = "conf/development.json" run { args = ["run", mainVerticleName, "--launcher-class=$mainClassName", "-conf $configurationFile" ] } 

Hay un problema con retrofit-vertx que está usando. El ResponseBuilder OkHttp3 requiere que el message no sea nulo, pero VertxCallFactory no lo establece.

Se corrigió en la última versión, pero como todavía está en desarrollo, debes usar la instantánea:

 repositories { mavenCentral() maven { url "https://oss.sonatype.org/content/repositories/snapshots" } } dependencies { compile 'com.julienviet:retrofit-vertx:1.0.2-SNAPSHOT' } 

El cambio a la dependencia de instantáneas corrige el problema que mencionas en tu pregunta, pero hay un problema con el mapeo json, que se puede solucionar fácilmente cambiando el código de:

 @GET("/Line/Mode/tube") fun getAllUndergroundLines(): Observable<UndergroundLine> 

a:

 @GET("/Line/Mode/tube") fun getAllUndergroundLines(): Observable<List<UndergroundLine>> 

Y actualizando tus classs de datos para tener un constructor vacío por defecto para que Jackson pueda crear instancias usando la reflexión:

 data class UndergroundLine(var id: String = "", var name: String = "") 

Más sobre emtpy constructor por defecto para classs de datos.

Pero es otra cuestión relacionada con cómo analizar la respuesta de la API que está utilizando a Observable y se le debe preguntar si no encuentra una solución alternativa.

  • Controlador de respuesta de coincidencia con request en VertX
  • ¿Cómo comstackr y usar el código de Kotlin en time de ejecución?
  • El código de Vertx Sync se ejecuta varias veces
  • Proxies de service Vert.x de Kotlin con vertx-codegen
  • Kotlin Vertx Type Mismatch encontrado Future <Unit> expected Handler <AsyncResult <Void >>
  • ¿Cómo puedo get una request web actual usando Vert.x Web?
  • VertX Web no elimina cookies