SpringBoot 2.0.2中WebClient发送SOAP POST请求缺失Content-Length头
我完全理解你碰到的麻烦——在Spring Boot 2.0.2.RELEASE里用WebClient发送SOAP POST请求时,遗留API要求的Content-Length头总是不见,试过BodyInserters.fromObject和syncBody两种方式都没用,但换成RestTemplate就能正常设置这个头,API也能顺利响应。下面给你几个实用的解决方案:
方案1:手动序列化请求并设置Content-Length
既然WebClient默认不会帮JAXB序列化的请求计算Content-Length,我们可以自己先把请求对象转成字节数组,算好长度后再手动加到请求头里:
// 1. 初始化JAXB序列化工具 JAXBContext jaxbContext = JAXBContext.newInstance(YourSoapRequestClass.class); Marshaller marshaller = jaxbContext.createMarshaller(); // 2. 将请求对象序列化为字节数组 ByteArrayOutputStream baos = new ByteArrayOutputStream(); marshaller.marshal(yourSoapRequestObject, baos); byte[] requestBytes = baos.toByteArray(); // 3. 构建WebClient请求,手动指定Content-Length webClient.post() .uri("your-legacy-api-endpoint") .header(HttpHeaders.CONTENT_LENGTH, String.valueOf(requestBytes.length)) .header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_XML_VALUE) .body(BodyInserters.fromByteArray(requestBytes)) .exchange();
这种方式直接跳过WebClient默认的序列化逻辑,完全由我们掌控请求体的长度计算,能确保Content-Length头被正确设置。
方案2:自定义HttpMessageWriter自动计算Content-Length
如果你不想每次请求都手动序列化,可以通过自定义EncoderHttpMessageWriter来增强JAXB的处理逻辑,让它自动计算并添加Content-Length头:
@Bean public WebClient webClient() { ExchangeStrategies strategies = ExchangeStrategies.builder() .messageWriters(writers -> { // 遍历默认消息写入器,找到JAXB对应的那个 for (HttpMessageWriter<?> writer : writers) { if (writer instanceof EncoderHttpMessageWriter) { Encoder<?> encoder = ((EncoderHttpMessageWriter<?>) writer).getEncoder(); if (encoder instanceof Jaxb2XmlEncoder) { // 替换为自定义的写入器 EncoderHttpMessageWriter<?> customWriter = new EncoderHttpMessageWriter<>( encoder, MediaType.APPLICATION_XML, MediaType.TEXT_XML) { @Override public Mono<Void> write(Publisher<?> inputStream, ResolvableType elementType, MediaType mediaType, ReactiveHttpOutputMessage message, Map<String, Object> hints) { // 先收集请求对象并序列化,计算长度后设置请求头 return Mono.from(inputStream) .map(obj -> { try { ByteArrayOutputStream baos = new ByteArrayOutputStream(); JAXBContext context = JAXBContext.newInstance(obj.getClass()); Marshaller marshaller = context.createMarshaller(); marshaller.marshal(obj, baos); return baos.toByteArray(); } catch (JAXBException e) { throw new RuntimeException("JAXB序列化失败", e); } }) .doOnNext(bytes -> message.getHeaders().setContentLength(bytes.length)) .flatMap(bytes -> super.write(Mono.just(bytes), ResolvableType.forClass(byte[].class), mediaType, message, hints)); } }; writers.set(writers.indexOf(writer), customWriter); break; } } } }) .build(); return WebClient.builder() .exchangeStrategies(strategies) .build(); }
配置完成后,后续用WebClient发送JAXB序列化的SOAP请求时,就会自动计算并添加Content-Length头了。
为什么RestTemplate可以正常工作?
RestTemplate是同步阻塞式的客户端,它会在发送请求前就把对象完整序列化成字节数组,然后直接根据数组长度设置Content-Length头。而WebClient是响应式非阻塞的,默认会采用分块传输(Transfer-Encoding: chunked),这种模式下不需要提前知道内容长度,所以不会自动添加Content-Length——这也是你遇到问题的核心原因。
至于SPR-16542的修复,它确实优化了EncoderHttpMessageWriter的逻辑,但Jaxb2XmlEncoder因为序列化的特性,没办法提前预估内容长度,所以这个修复并没有覆盖到JAXB的场景。
内容的提问来源于stack exchange,提问作者Wojciech Marusarz

