diff --git a/spring-core/src/main/java/org/springframework/core/codec/ResourceDecoder.java b/spring-core/src/main/java/org/springframework/core/codec/ResourceDecoder.java index d8e859280f..e8b9384daa 100644 --- a/spring-core/src/main/java/org/springframework/core/codec/ResourceDecoder.java +++ b/spring-core/src/main/java/org/springframework/core/codec/ResourceDecoder.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2018 the original author or authors. + * Copyright 2002-2019 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -41,6 +41,10 @@ import org.springframework.util.MimeTypeUtils; */ public class ResourceDecoder extends AbstractDataBufferDecoder { + /** Name of hint with a filename for the resource(e.g. from "Content-Disposition" HTTP header). */ + public static String FILENAME_HINT = ResourceDecoder.class.getName() + ".filename"; + + public ResourceDecoder() { super(MimeTypeUtils.ALL); } @@ -72,11 +76,22 @@ public class ResourceDecoder extends AbstractDataBufferDecoder { } Class clazz = elementType.toClass(); + String filename = hints != null ? (String) hints.get(FILENAME_HINT) : null; if (clazz == InputStreamResource.class) { - return new InputStreamResource(new ByteArrayInputStream(bytes)); + return new InputStreamResource(new ByteArrayInputStream(bytes)) { + @Override + public String getFilename() { + return filename; + } + }; } else if (Resource.class.isAssignableFrom(clazz)) { - return new ByteArrayResource(bytes); + return new ByteArrayResource(bytes) { + @Override + public String getFilename() { + return filename; + } + }; } else { throw new IllegalStateException("Unsupported resource class: " + clazz); diff --git a/spring-core/src/test/java/org/springframework/core/codec/ResourceDecoderTests.java b/spring-core/src/test/java/org/springframework/core/codec/ResourceDecoderTests.java index adb31e5fa5..b51736ccd2 100644 --- a/spring-core/src/test/java/org/springframework/core/codec/ResourceDecoderTests.java +++ b/spring-core/src/test/java/org/springframework/core/codec/ResourceDecoderTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2018 the original author or authors. + * Copyright 2002-2019 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -18,6 +18,7 @@ package org.springframework.core.codec; import java.io.IOException; import java.nio.charset.StandardCharsets; +import java.util.Collections; import java.util.Map; import org.junit.Test; @@ -100,23 +101,28 @@ public class ResourceDecoderTests extends AbstractDecoderTestCase input = Flux.concat( dataBuffer(this.fooBytes), dataBuffer(this.barBytes)); - testDecodeToMonoAll(input, Resource.class, step -> step - .consumeNextWith(resource -> { - try { - byte[] bytes = StreamUtils.copyToByteArray(resource.getInputStream()); - assertEquals("foobar", new String(bytes)); - } - catch (IOException e) { - fail(e.getMessage()); - } - }) - .expectComplete() - .verify()); + testDecodeToMonoAll(input, ResolvableType.forClass(Resource.class), + step -> step + .consumeNextWith(value -> { + Resource resource = (Resource) value; + try { + byte[] bytes = StreamUtils.copyToByteArray(resource.getInputStream()); + assertEquals("foobar", new String(bytes)); + assertEquals("testFile", resource.getFilename()); + } + catch (IOException e) { + fail(e.getMessage()); + } + }) + .expectComplete() + .verify(), + null, + Collections.singletonMap(ResourceDecoder.FILENAME_HINT, "testFile")); } } diff --git a/spring-web/src/main/java/org/springframework/http/codec/ResourceHttpMessageReader.java b/spring-web/src/main/java/org/springframework/http/codec/ResourceHttpMessageReader.java new file mode 100644 index 0000000000..b206af621b --- /dev/null +++ b/spring-web/src/main/java/org/springframework/http/codec/ResourceHttpMessageReader.java @@ -0,0 +1,56 @@ +/* + * Copyright 2002-2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.http.codec; + +import java.util.Map; + +import org.springframework.core.ResolvableType; +import org.springframework.core.codec.Hints; +import org.springframework.core.codec.ResourceDecoder; +import org.springframework.core.io.Resource; +import org.springframework.http.server.reactive.ServerHttpRequest; +import org.springframework.http.server.reactive.ServerHttpResponse; +import org.springframework.util.StringUtils; + +/** + * Simple around around {@link ResourceDecoder} that extracts the filename from + * the "Content-Disposition" header, if available, and passes it as the hint + * {@link ResourceDecoder#FILENAME_HINT}. + * + * @author Rossen Stoyanchev + * @since 5.2 + */ +public class ResourceHttpMessageReader extends DecoderHttpMessageReader { + + + public ResourceHttpMessageReader() { + super(new ResourceDecoder()); + } + + public ResourceHttpMessageReader(ResourceDecoder resourceDecoder) { + super(resourceDecoder); + } + + + @Override + protected Map getReadHints(ResolvableType actualType, ResolvableType elementType, + ServerHttpRequest request, ServerHttpResponse response) { + + String name = request.getHeaders().getContentDisposition().getFilename(); + return StringUtils.hasText(name) ? Hints.from(ResourceDecoder.FILENAME_HINT, name) : Hints.none(); + } + +} diff --git a/spring-web/src/main/java/org/springframework/http/codec/support/BaseDefaultCodecs.java b/spring-web/src/main/java/org/springframework/http/codec/support/BaseDefaultCodecs.java index 19529c004e..d0e36432d7 100644 --- a/spring-web/src/main/java/org/springframework/http/codec/support/BaseDefaultCodecs.java +++ b/spring-web/src/main/java/org/springframework/http/codec/support/BaseDefaultCodecs.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2018 the original author or authors. + * Copyright 2002-2019 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -29,7 +29,6 @@ import org.springframework.core.codec.DataBufferDecoder; import org.springframework.core.codec.DataBufferEncoder; import org.springframework.core.codec.Decoder; import org.springframework.core.codec.Encoder; -import org.springframework.core.codec.ResourceDecoder; import org.springframework.core.codec.StringDecoder; import org.springframework.http.codec.CodecConfigurer; import org.springframework.http.codec.DecoderHttpMessageReader; @@ -37,6 +36,7 @@ import org.springframework.http.codec.EncoderHttpMessageWriter; import org.springframework.http.codec.FormHttpMessageReader; import org.springframework.http.codec.HttpMessageReader; import org.springframework.http.codec.HttpMessageWriter; +import org.springframework.http.codec.ResourceHttpMessageReader; import org.springframework.http.codec.ResourceHttpMessageWriter; import org.springframework.http.codec.json.Jackson2JsonDecoder; import org.springframework.http.codec.json.Jackson2JsonEncoder; @@ -158,7 +158,7 @@ class BaseDefaultCodecs implements CodecConfigurer.DefaultCodecs { readers.add(new DecoderHttpMessageReader<>(new ByteArrayDecoder())); readers.add(new DecoderHttpMessageReader<>(new ByteBufferDecoder())); readers.add(new DecoderHttpMessageReader<>(new DataBufferDecoder())); - readers.add(new DecoderHttpMessageReader<>(new ResourceDecoder())); + readers.add(new ResourceHttpMessageReader()); readers.add(new DecoderHttpMessageReader<>(StringDecoder.textPlainOnly())); if (protobufPresent) { Decoder decoder = this.protobufDecoder != null ? this.protobufDecoder : new ProtobufDecoder(); diff --git a/spring-web/src/test/java/org/springframework/http/codec/support/ClientCodecConfigurerTests.java b/spring-web/src/test/java/org/springframework/http/codec/support/ClientCodecConfigurerTests.java index 617082b25a..c794c21605 100644 --- a/spring-web/src/test/java/org/springframework/http/codec/support/ClientCodecConfigurerTests.java +++ b/spring-web/src/test/java/org/springframework/http/codec/support/ClientCodecConfigurerTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2018 the original author or authors. + * Copyright 2002-2019 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -36,7 +36,6 @@ import org.springframework.core.codec.DataBufferDecoder; import org.springframework.core.codec.DataBufferEncoder; import org.springframework.core.codec.Decoder; import org.springframework.core.codec.Encoder; -import org.springframework.core.codec.ResourceDecoder; import org.springframework.core.codec.StringDecoder; import org.springframework.core.io.buffer.DefaultDataBufferFactory; import org.springframework.http.MediaType; @@ -46,6 +45,7 @@ import org.springframework.http.codec.EncoderHttpMessageWriter; import org.springframework.http.codec.FormHttpMessageReader; import org.springframework.http.codec.HttpMessageReader; import org.springframework.http.codec.HttpMessageWriter; +import org.springframework.http.codec.ResourceHttpMessageReader; import org.springframework.http.codec.ResourceHttpMessageWriter; import org.springframework.http.codec.ServerSentEventHttpMessageReader; import org.springframework.http.codec.json.Jackson2JsonDecoder; @@ -60,7 +60,7 @@ import org.springframework.http.codec.xml.Jaxb2XmlEncoder; import org.springframework.util.MimeTypeUtils; import static org.junit.Assert.*; -import static org.springframework.core.ResolvableType.forClass; +import static org.springframework.core.ResolvableType.*; /** * Unit tests for {@link ClientCodecConfigurer}. @@ -81,7 +81,7 @@ public class ClientCodecConfigurerTests { assertEquals(ByteArrayDecoder.class, getNextDecoder(readers).getClass()); assertEquals(ByteBufferDecoder.class, getNextDecoder(readers).getClass()); assertEquals(DataBufferDecoder.class, getNextDecoder(readers).getClass()); - assertEquals(ResourceDecoder.class, getNextDecoder(readers).getClass()); + assertEquals(ResourceHttpMessageReader.class, readers.get(this.index.getAndIncrement()).getClass()); assertStringDecoder(getNextDecoder(readers), true); assertEquals(ProtobufDecoder.class, getNextDecoder(readers).getClass()); assertEquals(FormHttpMessageReader.class, readers.get(this.index.getAndIncrement()).getClass()); // SPR-16804 diff --git a/spring-web/src/test/java/org/springframework/http/codec/support/CodecConfigurerTests.java b/spring-web/src/test/java/org/springframework/http/codec/support/CodecConfigurerTests.java index 3f7057eea9..29df90465d 100644 --- a/spring-web/src/test/java/org/springframework/http/codec/support/CodecConfigurerTests.java +++ b/spring-web/src/test/java/org/springframework/http/codec/support/CodecConfigurerTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2018 the original author or authors. + * Copyright 2002-2019 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -32,7 +32,6 @@ import org.springframework.core.codec.DataBufferDecoder; import org.springframework.core.codec.DataBufferEncoder; import org.springframework.core.codec.Decoder; import org.springframework.core.codec.Encoder; -import org.springframework.core.codec.ResourceDecoder; import org.springframework.core.codec.StringDecoder; import org.springframework.http.MediaType; import org.springframework.http.codec.CodecConfigurer; @@ -41,6 +40,7 @@ import org.springframework.http.codec.EncoderHttpMessageWriter; import org.springframework.http.codec.FormHttpMessageReader; import org.springframework.http.codec.HttpMessageReader; import org.springframework.http.codec.HttpMessageWriter; +import org.springframework.http.codec.ResourceHttpMessageReader; import org.springframework.http.codec.ResourceHttpMessageWriter; import org.springframework.http.codec.json.Jackson2JsonDecoder; import org.springframework.http.codec.json.Jackson2JsonEncoder; @@ -76,7 +76,7 @@ public class CodecConfigurerTests { assertEquals(ByteArrayDecoder.class, getNextDecoder(readers).getClass()); assertEquals(ByteBufferDecoder.class, getNextDecoder(readers).getClass()); assertEquals(DataBufferDecoder.class, getNextDecoder(readers).getClass()); - assertEquals(ResourceDecoder.class, getNextDecoder(readers).getClass()); + assertEquals(ResourceHttpMessageReader.class, readers.get(this.index.getAndIncrement()).getClass()); assertStringDecoder(getNextDecoder(readers), true); assertEquals(ProtobufDecoder.class, getNextDecoder(readers).getClass()); assertEquals(FormHttpMessageReader.class, readers.get(this.index.getAndIncrement()).getClass()); @@ -128,7 +128,7 @@ public class CodecConfigurerTests { assertEquals(ByteArrayDecoder.class, getNextDecoder(readers).getClass()); assertEquals(ByteBufferDecoder.class, getNextDecoder(readers).getClass()); assertEquals(DataBufferDecoder.class, getNextDecoder(readers).getClass()); - assertEquals(ResourceDecoder.class, getNextDecoder(readers).getClass()); + assertEquals(ResourceHttpMessageReader.class, readers.get(this.index.getAndIncrement()).getClass()); assertEquals(StringDecoder.class, getNextDecoder(readers).getClass()); assertEquals(ProtobufDecoder.class, getNextDecoder(readers).getClass()); assertEquals(FormHttpMessageReader.class, readers.get(this.index.getAndIncrement()).getClass()); diff --git a/spring-web/src/test/java/org/springframework/http/codec/support/ServerCodecConfigurerTests.java b/spring-web/src/test/java/org/springframework/http/codec/support/ServerCodecConfigurerTests.java index 15408dc906..6c3023f945 100644 --- a/spring-web/src/test/java/org/springframework/http/codec/support/ServerCodecConfigurerTests.java +++ b/spring-web/src/test/java/org/springframework/http/codec/support/ServerCodecConfigurerTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2018 the original author or authors. + * Copyright 2002-2019 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -36,7 +36,6 @@ import org.springframework.core.codec.DataBufferDecoder; import org.springframework.core.codec.DataBufferEncoder; import org.springframework.core.codec.Decoder; import org.springframework.core.codec.Encoder; -import org.springframework.core.codec.ResourceDecoder; import org.springframework.core.codec.StringDecoder; import org.springframework.core.io.buffer.DefaultDataBufferFactory; import org.springframework.http.MediaType; @@ -45,6 +44,7 @@ import org.springframework.http.codec.EncoderHttpMessageWriter; import org.springframework.http.codec.FormHttpMessageReader; import org.springframework.http.codec.HttpMessageReader; import org.springframework.http.codec.HttpMessageWriter; +import org.springframework.http.codec.ResourceHttpMessageReader; import org.springframework.http.codec.ResourceHttpMessageWriter; import org.springframework.http.codec.ServerCodecConfigurer; import org.springframework.http.codec.ServerSentEventHttpMessageWriter; @@ -61,7 +61,7 @@ import org.springframework.http.codec.xml.Jaxb2XmlEncoder; import org.springframework.util.MimeTypeUtils; import static org.junit.Assert.*; -import static org.springframework.core.ResolvableType.forClass; +import static org.springframework.core.ResolvableType.*; /** * Unit tests for {@link ServerCodecConfigurer}. @@ -82,7 +82,7 @@ public class ServerCodecConfigurerTests { assertEquals(ByteArrayDecoder.class, getNextDecoder(readers).getClass()); assertEquals(ByteBufferDecoder.class, getNextDecoder(readers).getClass()); assertEquals(DataBufferDecoder.class, getNextDecoder(readers).getClass()); - assertEquals(ResourceDecoder.class, getNextDecoder(readers).getClass()); + assertEquals(ResourceHttpMessageReader.class, readers.get(this.index.getAndIncrement()).getClass()); assertStringDecoder(getNextDecoder(readers), true); assertEquals(ProtobufDecoder.class, getNextDecoder(readers).getClass()); assertEquals(FormHttpMessageReader.class, readers.get(this.index.getAndIncrement()).getClass());