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