Skip to content

Commit 4a26f93

Browse files
committed
WebClient writes Content-Length for Mono bodies
In SPR-16892, the `EncoderHttpMessageWriter` has been improved to write `"Content-Length"` HTTP response headers if the response body is of type `Mono` (i.e. the actual content length is easily accessible without buffering a possibly large response body). That change was relying on the fact that the server side is using a `ChannelSendOperator` to delay the writing of the body until the first signal is received. This strategy is not effective on the client side, since no such channel operator is used for `WebClient`. This commit improves `EncoderHttpMessageWriter` and delays, for `Mono` HTTP message bodies only, the writing of the body so that we can write the `"Content-Length"` header information once we've got the body resolved. Issue: SPR-16949
1 parent a774305 commit 4a26f93

File tree

4 files changed

+16
-11
lines changed

4 files changed

+16
-11
lines changed

spring-web/src/main/java/org/springframework/http/client/reactive/AbstractClientHttpRequest.java

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2017 the original author or authors.
2+
* Copyright 2002-2018 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -118,12 +118,12 @@ protected Mono<Void> doCommit(@Nullable Supplier<? extends Publisher<Void>> writ
118118
return Mono.empty();
119119
}
120120

121-
this.commitActions.add(() -> {
122-
applyHeaders();
123-
applyCookies();
124-
this.state.set(State.COMMITTED);
125-
return Mono.empty();
126-
});
121+
this.commitActions.add(() ->
122+
Mono.fromRunnable(() -> {
123+
applyHeaders();
124+
applyCookies();
125+
this.state.set(State.COMMITTED);
126+
}));
127127

128128
if (writeAction != null) {
129129
this.commitActions.add(writeAction);
@@ -132,7 +132,7 @@ protected Mono<Void> doCommit(@Nullable Supplier<? extends Publisher<Void>> writ
132132
List<? extends Publisher<Void>> actions = this.commitActions.stream()
133133
.map(Supplier::get).collect(Collectors.toList());
134134

135-
return Mono.fromDirect(Flux.concat(actions));
135+
return Flux.concat(actions).then();
136136
}
137137

138138

spring-web/src/main/java/org/springframework/http/codec/EncoderHttpMessageWriter.java

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -103,11 +103,14 @@ public Mono<Void> write(Publisher<? extends T> inputStream, ResolvableType eleme
103103
Flux<DataBuffer> body = this.encoder.encode(
104104
inputStream, message.bufferFactory(), elementType, contentType, hints);
105105

106-
// Response is not committed until the first signal...
107106
if (inputStream instanceof Mono) {
108107
HttpHeaders headers = message.getHeaders();
109108
if (headers.getContentLength() < 0 && !headers.containsKey(HttpHeaders.TRANSFER_ENCODING)) {
110-
body = body.doOnNext(data -> headers.setContentLength(data.readableByteCount()));
109+
return Mono.from(body)
110+
.flatMap(dataBuffer -> {
111+
headers.setContentLength(dataBuffer.readableByteCount());
112+
return message.writeWith(Mono.just(dataBuffer));
113+
});
111114
}
112115
}
113116

spring-webflux/src/test/java/org/springframework/web/reactive/function/BodyInsertersTests.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -340,10 +340,12 @@ public void fromMultipartDataWithMultipleValues() {
340340
String content = new String(resultBytes, StandardCharsets.UTF_8);
341341
assertThat(content, containsString("Content-Disposition: form-data; name=\"name\"\r\n" +
342342
"Content-Type: text/plain;charset=UTF-8\r\n" +
343+
"Content-Length: 6\r\n" +
343344
"\r\n" +
344345
"value1"));
345346
assertThat(content, containsString("Content-Disposition: form-data; name=\"name\"\r\n" +
346347
"Content-Type: text/plain;charset=UTF-8\r\n" +
348+
"Content-Length: 6\r\n" +
347349
"\r\n" +
348350
"value2"));
349351
})

spring-webflux/src/test/java/org/springframework/web/reactive/function/client/WebClientIntegrationTests.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -318,7 +318,7 @@ public void shouldSendPojoAsJson() throws Exception {
318318
expectRequest(request -> {
319319
assertEquals("/pojo/capitalize", request.getPath());
320320
assertEquals("{\"foo\":\"foofoo\",\"bar\":\"barbar\"}", request.getBody().readUtf8());
321-
assertEquals("chunked", request.getHeader(HttpHeaders.TRANSFER_ENCODING));
321+
assertEquals("31", request.getHeader(HttpHeaders.CONTENT_LENGTH));
322322
assertEquals("application/json", request.getHeader(HttpHeaders.ACCEPT));
323323
assertEquals("application/json", request.getHeader(HttpHeaders.CONTENT_TYPE));
324324
});

0 commit comments

Comments
 (0)