【问题标题】:Missing Content-Length header sending POST request with WebClient (SpringBoot 2.0.2.RELEASE)缺少使用 WebClient 发送 POST 请求的 Content-Length 标头(SpringBoot 2.0.2.RELEASE)
【发布时间】:2018-11-02 17:02:27
【问题描述】:

我正在使用 WebClient (SpringBoot 2.0.2.RELEASE) 发送带有 SOAP 请求的 POST,但它缺少所需的“Content-Length”标头旧版 API。

是否可以将 WebClient 配置为包含“Content-Length”标头? 在 SpringBoot 2.0.1 中为 EncoderHttpMessageWriter 解析并引入了 Spring Framework Issue,但它似乎不适用于 JAXB。

我尝试使用BodyInserters

webClient.post().body(BodyInserters.fromObject(request)).exchange();

syncBody:

webClient.post().syncBody(request).exchange();

他们都没有为WebClient工作。但是,当使用RestTemplate 时,Content-Length 已设置并且 API 响应成功

【问题讨论】:

    标签: spring-boot spring-webflux


    【解决方案1】:

    我正在努力解决同样的问题,作为一个丑陋的解决方法,我手动序列化请求(在我的情况下为 JSON)并设置长度(Kotlin 代码):

    open class PostRetrieverWith411ErrorFix(
        private val objectMapper: ObjectMapper
    ) {
    
    protected fun <T : Any> post(webClient: WebClient, body: Any, responseClass: Class<T>): Mono<T> {
        val bodyJson = objectMapper.writeValueAsString(body)
    
        return webClient.post()
            .contentType(MediaType.APPLICATION_JSON_UTF8)
            .contentLength(bodyJson.toByteArray(Charset.forName("UTF-8")).size.toLong())
            .syncBody(bodyJson)
            .retrieve()
            .bodyToMono(responseClass)
        }
    }
    

    【讨论】:

    • 目前看来只能这样了。经过一番研究,似乎没有办法使用当前的 webflux 实现来添加这个标头。
    【解决方案2】:

    WebClient 是一个流媒体客户端,在流结束之前设置内容长度有点困难。到那时,标题早已不复存在。如果您使用旧版,您可以重新使用您的单声道(Mono/Flux 可以重复使用,Java 流不能)并检查长度。

        public void post() {
    
        Mono<String> mono = Mono.just("HELLO WORLDZ");
    
        final String response = WebClient.create("http://httpbin.org")
                .post()
                .uri("/post")
                .header(HttpHeaders.CONTENT_LENGTH,
                        mono.map(s -> String.valueOf(s.getBytes(StandardCharsets.UTF_8).length)).block())
                .body(BodyInserters.fromPublisher(mono, String.class))
                .retrieve()
                .bodyToMono(String.class)
                .block();
    
        System.out.println(response);
    
    }
    

    我的一位同事(干得好,Max!)提出了更简洁的解决方案,我添加了一些包装代码以便对其进行测试:

        Mono<String> my = Mono.just("HELLO WORLDZZ")
                .flatMap(body -> WebClient.create("http://httpbin.org")
                        .post()
                        .uri("/post")
                        .header(HttpHeaders.CONTENT_LENGTH,
                                String.valueOf(body.getBytes(StandardCharsets.UTF_8).length))
                        .syncBody(body)
                        .retrieve()
                        .bodyToMono(String.class));
    
        System.out.println(my.block());
    

    【讨论】:

    • 这仅适用于纯字符串主体,不适用于 POJO 到 JSON 之类的映射对象,还是这样?
    • 自 2018 年以来有一个回复可以回答您的问题...
    • 当您使用 WebClient .syncBody(myPojo) 的自动映射功能将 Pojo 作为 syncBody 传递时,即使 brebDev 的答案也无法解决这种情况,因为在这种情况下,我不知道序列化有多少个字符会,或者我可以吗?还是显式映射是唯一的出路?很抱歉我的第一个问题表述得很草率。
    • 简单解决方案:使用 2.2 或更高版本。问题已修复:github.com/spring-projects/spring-framework/issues/21085 in 2019
    【解决方案3】:

    如果您像我们一样应用 Sven 的 colleague(Max) 解决方案,您也可以将其调整为适用于您的身体是自定义 obj 但您必须对其进行序列化一次的情况:

    String req = objectMapper.writeValueAsString(requestObject)
    

    并将其传递给

    webClient.syncBody(req)
    

    请记住,在 SpringBoot 2.0.3.RELEASE 中,如果您将字符串作为请求传递给 webClient,它将作为 ContentType 标头 MediaType.TEXT_PLAIN 放置,这会使我们与其他服务的集成失败。我们通过像这样专门设置内容类型标题来解决这个问题:

    httpHeaders.setContentType(MediaType.APPLICATION_JSON);
    

    【讨论】:

      猜你喜欢
      • 2021-04-17
      • 2018-04-06
      • 2020-09-21
      • 2020-02-05
      • 1970-01-01
      • 1970-01-01
      • 2013-02-15
      • 2019-11-21
      • 1970-01-01
      相关资源
      最近更新 更多