Skip to content

Commit a7f71f4

Browse files
committed
Support filename hint for client side too
Closes gh-25516
1 parent 3655326 commit a7f71f4

File tree

3 files changed

+96
-6
lines changed

3 files changed

+96
-6
lines changed

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

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2019 the original author or authors.
2+
* Copyright 2002-2020 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.
@@ -96,13 +96,15 @@ public boolean canRead(ResolvableType elementType, @Nullable MediaType mediaType
9696
@Override
9797
public Flux<T> read(ResolvableType elementType, ReactiveHttpInputMessage message, Map<String, Object> hints) {
9898
MediaType contentType = getContentType(message);
99-
return this.decoder.decode(message.getBody(), elementType, contentType, hints);
99+
Map<String, Object> allHints = Hints.merge(hints, getReadHints(elementType, message));
100+
return this.decoder.decode(message.getBody(), elementType, contentType, allHints);
100101
}
101102

102103
@Override
103104
public Mono<T> readMono(ResolvableType elementType, ReactiveHttpInputMessage message, Map<String, Object> hints) {
104105
MediaType contentType = getContentType(message);
105-
return this.decoder.decodeToMono(message.getBody(), elementType, contentType, hints);
106+
Map<String, Object> allHints = Hints.merge(hints, getReadHints(elementType, message));
107+
return this.decoder.decodeToMono(message.getBody(), elementType, contentType, allHints);
106108
}
107109

108110
/**
@@ -118,6 +120,14 @@ protected MediaType getContentType(HttpMessage inputMessage) {
118120
return (contentType != null ? contentType : MediaType.APPLICATION_OCTET_STREAM);
119121
}
120122

123+
/**
124+
* Get additional hints for decoding based on the input HTTP message.
125+
* @since 5.3
126+
*/
127+
protected Map<String, Object> getReadHints(ResolvableType elementType, ReactiveHttpInputMessage message) {
128+
return Hints.none();
129+
}
130+
121131

122132
// Server-side only...
123133

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

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2019 the original author or authors.
2+
* Copyright 2002-2020 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.
@@ -22,6 +22,7 @@
2222
import org.springframework.core.codec.Hints;
2323
import org.springframework.core.codec.ResourceDecoder;
2424
import org.springframework.core.io.Resource;
25+
import org.springframework.http.ReactiveHttpInputMessage;
2526
import org.springframework.http.server.reactive.ServerHttpRequest;
2627
import org.springframework.http.server.reactive.ServerHttpResponse;
2728
import org.springframework.util.StringUtils;
@@ -45,12 +46,18 @@ public ResourceHttpMessageReader(ResourceDecoder resourceDecoder) {
4546
}
4647

4748

49+
@Override
50+
protected Map<String, Object> getReadHints(ResolvableType elementType, ReactiveHttpInputMessage message) {
51+
String filename = message.getHeaders().getContentDisposition().getFilename();
52+
return (StringUtils.hasText(filename) ?
53+
Hints.from(ResourceDecoder.FILENAME_HINT, filename) : Hints.none());
54+
}
55+
4856
@Override
4957
protected Map<String, Object> getReadHints(ResolvableType actualType, ResolvableType elementType,
5058
ServerHttpRequest request, ServerHttpResponse response) {
5159

52-
String name = request.getHeaders().getContentDisposition().getFilename();
53-
return StringUtils.hasText(name) ? Hints.from(ResourceDecoder.FILENAME_HINT, name) : Hints.none();
60+
return getReadHints(elementType, request);
5461
}
5562

5663
}
Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
/*
2+
* Copyright 2002-2020 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package org.springframework.http.codec;
17+
18+
import java.io.IOException;
19+
import java.nio.charset.StandardCharsets;
20+
import java.util.Collections;
21+
22+
import org.junit.jupiter.api.Test;
23+
import reactor.core.publisher.Mono;
24+
25+
import org.springframework.core.ResolvableType;
26+
import org.springframework.core.io.ByteArrayResource;
27+
import org.springframework.core.io.Resource;
28+
import org.springframework.core.io.buffer.DataBuffer;
29+
import org.springframework.core.testfixture.io.buffer.AbstractLeakCheckingTests;
30+
import org.springframework.http.ContentDisposition;
31+
import org.springframework.http.HttpStatus;
32+
import org.springframework.http.MediaType;
33+
import org.springframework.web.testfixture.http.client.reactive.MockClientHttpResponse;
34+
35+
import static org.assertj.core.api.Assertions.assertThat;
36+
37+
/**
38+
* Unit tests for {@link ResourceHttpMessageReader}.
39+
* @author Rossen Stoyanchev
40+
*/
41+
public class ResourceHttpMessageReaderTests extends AbstractLeakCheckingTests {
42+
43+
private final ResourceHttpMessageReader reader = new ResourceHttpMessageReader();
44+
45+
@Test
46+
void readResourceAsMono() throws IOException {
47+
String filename = "test.txt";
48+
String body = "Test resource content";
49+
50+
ContentDisposition contentDisposition =
51+
ContentDisposition.builder("attachment").name("file").filename(filename).build();
52+
53+
MockClientHttpResponse response = new MockClientHttpResponse(HttpStatus.OK);
54+
response.getHeaders().setContentType(MediaType.TEXT_PLAIN);
55+
response.getHeaders().setContentDisposition(contentDisposition);
56+
response.setBody(Mono.just(stringBuffer(body)));
57+
58+
Resource resource = reader.readMono(
59+
ResolvableType.forClass(ByteArrayResource.class), response, Collections.emptyMap()).block();
60+
61+
assertThat(resource).isNotNull();
62+
assertThat(resource.getFilename()).isEqualTo(filename);
63+
assertThat(resource.getInputStream()).hasContent(body);
64+
}
65+
66+
private DataBuffer stringBuffer(String value) {
67+
byte[] bytes = value.getBytes(StandardCharsets.UTF_8);
68+
DataBuffer buffer = this.bufferFactory.allocateBuffer(bytes.length);
69+
buffer.write(bytes);
70+
return buffer;
71+
}
72+
73+
}

0 commit comments

Comments
 (0)