首页 文章

使用AsyncRestTemplate Netty客户端的Spring启动失败

提问于
浏览
7

我有一个Spring Boot 1.3.6应用程序,开箱即用并使用嵌入式Tomcat服务器 . 应用程序有一个 endpoints 执行非常简单的echo请求 .

后来我定义了一个使用 AsyncRestTemplate 调用该简单 endpoints 的相应客户端,但是如果我的客户端使用 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())))
    }
}

我的 endpoints 看起来像这样:

@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以确保正确的构造函数反序列化 .

客户

然后我继续创建一个调用此 endpoints 的客户端 .

如果我在我的客户端执行类似下面的操作,它可以完美地工作,并且我得到了成功的回应响应 .

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 ,上面写着"Required request body is missing"的消息 .

我调试了Spring Boot代码,我看到当读取 ServletInputStream 时,它总是返回-1,好像它是空的 .

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

为什么 SimpleClientHttpRequestFactory 工作正常,但 Netty4ClientHttpRequestFactory 失败了?

我认为它可能与嵌入式Tomcat服务器有关,但是,如果我将此应用程序打包为war并将其部署在现有Tomcat服务器中(即不使用嵌入式服务器),则问题仍然存在 . 所以,我猜测是与Spring / Spring Boot相关的东西 .

我错过了Spring Boot应用程序中的任何配置吗?有关如何使Netty客户端与Spring Boot一起工作的任何建议?

2 回答

  • 2

    看起来客户端的序列化存在问题 . 因为这段代码非常完美:

    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本身时显然存在问题 .

  • 1

    我的2美分 . 这肯定不是解决方案 .

    我将 asyncRestTemplate 配置为 AsyncHttpClientRequestInterceptor 并且神奇地工作了 . 没有解释,期间!

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

相关问题