与AsyncRestTemplate Netty客户端的Spring启动失败

我有一个Spring Boot 1.3.6应用程序,它使用嵌入式的Tomcat服务器。 该应用程序具有一个简单的回应请求的单个端点。

后来我定义了一个相应的客户端使用AsyncRestTemplate调用这个简单的端点,但是如果我的客户端使用Netty4ClientHttpRequestFactory请求失败,否则它会成功。

我的例子在Kotlin中,但是它在Java中失败了,所以它不需要使用我用来实现它的语言。

服务器

 @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()))) } } 

我的端点如下所示:

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

订单数据类是

 data class Order(val orderId: String) 

注意:自从我使用Kotlin之后,我还添加了Kotlin Jackson模块以确保正确的构造函数反序列化。

客户端

然后我开始创建一个调用这个端点的客户端。

如果我在客户端执行类似以下的操作,它可以很好地工作,并获得成功的回应响应。

  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) } } }) 

如上所述,上面的代码运行完美,打印成功的回应响应。

问题

但是,如果我决定使用Netty客户端,那么我得到了400个“错误请求”报告,我没有传递正文:

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

当我这样做时,我得到一个HttpMessageNotReadableException与消息说“所需的请求正文丢失”。

我调试了Spring Boot代码,发现ServletInputStream被读取时,它总是返回-1,就像它是空的一样。

在我的gradle中,我添加了runtime('io.netty:netty-all:4.1.2.Final') ,所以我使用了Netty的最新版本。 这个Netty版本在与我使用普通Spring的其他项目(即不是Spring Boot)的终端交互时工作得很好。

SimpleClientHttpRequestFactory如何正常工作,但Netty4ClientHttpRequestFactory失败?

我认为这可能与嵌入式Tomcat服务器有关,但是,如果我将这个应用程序打包为war,并将其部署到现有Tomcat服务器(即不使用嵌入式服务器),问题依然存在。 所以,我猜猜是与Spring / Spring Boot有关。

我是否在我的Spring Boot应用程序中缺少任何配置? 有关如何使Netty客户端使用Spring Boot的建议?

看起来客户端序列化存在问题。 因为这个代码工作完美:

 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) } } }) 

我需要更精确地看看他的转换器中的restTemplate,但现在你可以这样写这个部分:

 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) } } }) 

正如你所看到的,我不使用KotlinModule和这个代码完美的作品,所以显然在配置AsyncRestTemplate本身的问题。

我的2美分。 这当然不是解决方案。

我用AsyncHttpClientRequestInterceptor配置了asyncRestTemplate ,它神奇地工作。 没有解释,期限!

 public class AsyncClientLoggingInterceptor implements AsyncClientHttpRequestInterceptor { @Override public ListenableFuture<ClientHttpResponse> intercept(HttpRequest request, byte[] body, AsyncClientHttpRequestExecution execution) throws IOException { return execution.executeAsync(request, body); } }