Spring Boot with AsyncRestTemplate Netty Client falla

Tengo una aplicación Spring Boot 1.3.6, construida fuera de la caja y usando el server Tomcat integrado. La aplicación tiene un punto final único haciendo una request de eco muy simple.

Más tarde AsyncRestTemplate un cliente correspondiente que invoca ese punto final simple usando AsyncRestTemplate ; sin embargo, si mi cliente usa Netty4ClientHttpRequestFactory la request falla; de lo contrario, tiene éxito.

Mi ejemplo a continuación está en Kotlin, pero falla de la misma manera en Java, por lo que no tiene que ver con el lenguaje que uso para implementarlo.

El server

 @SpringBootApplication open class EchoApplication { companion object { @JvmStatic fun main(args: Array<String>) { SpringApplication.run(EchoApplication::class.java, *args) } } @Bean open fun objectMapper(): ObjectMapper { return ObjectMapper().apply { dateFormat = SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssXXX") registerModule(KotlinModule()) } } @Bean open fun customConverters(): HttpMessageConverters { return HttpMessageConverters(listOf(MappingJackson2HttpMessageConverter(objectMapper()))) } } 

Mi punto final se ve así:

 @RestController class EchoController { @RequestMapping(value="/foo", method = arrayOf(RequestMethod.PUT)) fun echo(@RequestBody order: Order): Order { return order } } 

Y la class de datos de la order es

 data class Order(val orderId: String) 

Nota: Desde que uso Kotlin, también agregué el Módulo Kotlin Jackson para asegurar la deserialization del constructor adecuada.

El cliente

Luego procedí a crear un cliente que invoca este punto final.

Si hago algo como lo siguiente en mi cliente, funciona perfectamente y obtengo una respuesta de eco exitosa.

  val executor = TaskExecutorAdapter(Executors.newFixedThreadPool(2)) val restTemplate = AsyncRestTemplate(executor) restTemplate.messageConverters = listOf(MappingJackson2HttpMessageConverter(mapper)) val promise = restTemplate.exchange(URI.create("http://localhost:8080/foo"), HttpMethod.PUT, HttpEntity(Order("b1254"), headers), Order::class.java) promise.addCallback(object : ListenableFutureCallback<ResponseEntity<Order>> { override fun onSuccess(result: ResponseEntity<Order>) { println(result.body) } override fun onFailure(ex: Throwable) { ex.printStackTrace() if(ex is HttpStatusCodeException){ println(ex.responseBodyAsString) } } }) 

Como se mencionó anteriormente, el código anterior funciona perfectamente e imprime una respuesta de eco exitosa.

El problema

Pero si decido usar el cliente de Netty, recibo 400 informes de request incorrecta que no pasé el cuerpo:

 val nettyFactory = Netty4ClientHttpRequestFactory() val restTemplate = AsyncRestTemplate(nettyFactory) 

Cuando hago esto, obtengo una HttpMessageNotReadableException con un post que dice "Falta el cuerpo de request requerido".

Depuré el código Spring Boot y veo que cuando se lee ServletInputStream , siempre devuelve -1 como si estuviera vacío.

En mi gradle agregué el runtime('io.netty:netty-all:4.1.2.Final') , así que estoy usando lo que, a partir de hoy, es la última versión de Netty. Esta versión de Netty ha funcionado bien al interactuar con puntos finales en otros proyectos que tengo que usan Spring regular (es decir, no Spring Boot).

¿Cómo es que SimpleClientHttpRequestFactory funciona bien, pero falla Netty4ClientHttpRequestFactory ?

Pensé que podría tener que ver con el server Tomcat integrado, sin embargo, si empaqueto esta aplicación como guerra y la despliego en un server Tomcat existente (es decir, sin utilizar la integrada), el problema persiste. Entonces, supongo que es algo relacionado con Spring / Spring Boot.

¿Me falta alguna configuration en mi aplicación Spring Boot? ¿Alguna sugerencia sobre cómo hacer que el cliente Netty trabaje con Spring Boot?

Parece que hay un problema en la serialization por parte del Cliente. Porque este código funciona perfecto:

 restTemplate.exchange( URI.create("http://localhost:8080/foo"), HttpMethod.PUT, HttpEntity("""{"orderId":"1234"}""", HttpHeaders().apply { setContentType(MediaType.APPLICATION_JSON); }), Order::class.java ).addCallback(object : ListenableFutureCallback<ResponseEntity<Order>> { override fun onSuccess(result: ResponseEntity<Order>) { println("Result: ${result.body}") } override fun onFailure(ex: Throwable) { ex.printStackTrace() if (ex is HttpStatusCodeException) { println(ex.responseBodyAsString) } } }) 

Necesito una vista más precisa de restTemplate en sus conversores, pero por ahora puede escribir esta parte de esta manera:

 val mapper = ObjectMapper() restTemplate.exchange( URI.create("http://localhost:8080/foo"), HttpMethod.PUT, HttpEntity(mapper.writeValueAsString(Order("HELLO")), HttpHeaders().apply { setContentType(MediaType.APPLICATION_JSON); }), Order::class.java ).addCallback(object : ListenableFutureCallback<ResponseEntity<Order>> { override fun onSuccess(result: ResponseEntity<Order>) { println("Result: ${result.body}") } override fun onFailure(ex: Throwable) { ex.printStackTrace() if (ex is HttpStatusCodeException) { println(ex.responseBodyAsString) } } }) 

Como ve, no uso KotlinModule y este código funciona perfectamente, por lo que obviamente es un problema en la cofiguración AsyncRestTemplate.

Mis 2 centavos. Esta seguramente no es la solución.

Configuré el asyncRestTemplate con un AsyncHttpClientRequestInterceptor y funcionó mágicamente. Sin explicación, punto!

 public class AsyncClientLoggingInterceptor implements AsyncClientHttpRequestInterceptor { @Override public ListenableFuture<ClientHttpResponse> intercept(HttpRequest request, byte[] body, AsyncClientHttpRequestExecution execution) throws IOException { return execution.executeAsync(request, body); } } 
  • El file spring-configuration-metadata.json no se genera en IntelliJ Idea for Kotlin @ConfigurationProperties class
  • Usando una constante de Java en un parámetro de anotación de Kotlin
  • Spring Boot e Hibernate. Manejar la fábrica de sesiones
  • Estado de respuesta HTTP SpringBoot Kotlin Api
  • SpringBoot ArrayIndexOutOfBoundsException MethodParameter.getGenericParameterType
  • Spring Boot: Agregar @Transactional produce java.lang.ClassNotFoundException: org.aspectj.util.PartialOrder $ PartialComparable
  • Encabezados HTTP no devueltos en EC2
  • Instant no se puede serializar al formatting apropiado incluso con jackson-datatype-jsr310
  • Enfoque correcto para la class de Kotlin inyectada de constructor inmutable
  • Analizando un object json con un campo dynamic en Kotlin
  • Kotlin + Spring Boot solicita sorting