带有 AsyncRestTemplate Netty 客户端的 Spring Boot 失败
Posted
技术标签:
【中文标题】带有 AsyncRestTemplate Netty 客户端的 Spring Boot 失败【英文标题】:Spring Boot with AsyncRestTemplate Netty Client Fails 【发布时间】:2016-11-14 03:32:43 【问题描述】:我有一个 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 Module 以确保正确的构造函数反序列化。
客户
然后我继续创建一个调用此端点的客户端。
如果我在我的客户端中执行以下操作,它会完美运行并且我会获得成功的回显响应。
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 Bad Request 报告我没有通过正文:
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 一起工作的任何建议?
【问题讨论】:
你知道为什么 MappingJackson2HttpMessageConverter 不解析 json 吗?我在java中看到同样的错误。如果您查看 asyncresttemplate 的文档“AsyncRestTemplate 通过 getRestOperations() 方法公开一个同步 RestTemplate,并与该 RestTemplate 共享其错误处理程序和消息转换器。” 【参考方案1】:看起来客户端的序列化存在问题。因为这段代码完美运行:
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 本身显然存在问题。
【讨论】:
有我的游乐场github.com/IRus/kotlin-meetup/tree/***/async-netty 非常有趣!我一步一步地按照代码进行操作,我可以看到它是如何使用 Mapping Jackson 转换器正确转换的,然后向下游刷新。我想这种刷新可能没有按预期工作,因为当我将有效负载作为字符串发送并且它使用 StringMessageConverter 时,一切正常。【参考方案2】:我的 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);
【讨论】:
以上是关于带有 AsyncRestTemplate Netty 客户端的 Spring Boot 失败的主要内容,如果未能解决你的问题,请参考以下文章
在测试AsyncRestTemplate时,防止已经声明异常的期望
将 OAuth2RestTemplate 公开为 AsyncRestTemplate