Allow repeatable writes in HttpMessageConverter

This commit ensures that the StreamingHttpOutputMessage.Body.repeatable
flag is set in message converters for bodies that can be written
repeatedly.

Closes gh-31516
See gh-31449
This commit is contained in:
Arjen Poutsma
2023-11-02 15:51:10 +01:00
parent ab316d9bc8
commit 6dd93d4d85
22 changed files with 206 additions and 25 deletions

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2021 the original author or authors.
* Copyright 2002-2023 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.
@@ -88,16 +88,27 @@ public abstract class AbstractGenericHttpMessageConverter<T> extends AbstractHtt
addDefaultHeaders(headers, t, contentType);
if (outputMessage instanceof StreamingHttpOutputMessage streamingOutputMessage) {
streamingOutputMessage.setBody(outputStream -> writeInternal(t, type, new HttpOutputMessage() {
streamingOutputMessage.setBody(new StreamingHttpOutputMessage.Body() {
@Override
public OutputStream getBody() {
return outputStream;
public void writeTo(OutputStream outputStream) throws IOException {
writeInternal(t, type, new HttpOutputMessage() {
@Override
public OutputStream getBody() {
return outputStream;
}
@Override
public HttpHeaders getHeaders() {
return headers;
}
});
}
@Override
public HttpHeaders getHeaders() {
return headers;
public boolean repeatable() {
return supportsRepeatableWrites(t);
}
}));
});
}
else {
writeInternal(t, type, outputMessage);

View File

@@ -210,16 +210,27 @@ public abstract class AbstractHttpMessageConverter<T> implements HttpMessageConv
addDefaultHeaders(headers, t, contentType);
if (outputMessage instanceof StreamingHttpOutputMessage streamingOutputMessage) {
streamingOutputMessage.setBody(outputStream -> writeInternal(t, new HttpOutputMessage() {
streamingOutputMessage.setBody(new StreamingHttpOutputMessage.Body() {
@Override
public OutputStream getBody() {
return outputStream;
public void writeTo(OutputStream outputStream) throws IOException {
writeInternal(t, new HttpOutputMessage() {
@Override
public OutputStream getBody() {
return outputStream;
}
@Override
public HttpHeaders getHeaders() {
return headers;
}
});
}
@Override
public HttpHeaders getHeaders() {
return headers;
public boolean repeatable() {
return supportsRepeatableWrites(t);
}
}));
});
}
else {
writeInternal(t, outputMessage);
@@ -289,6 +300,21 @@ public abstract class AbstractHttpMessageConverter<T> implements HttpMessageConv
return null;
}
/**
* Indicates whether this message converter can
* {@linkplain #write(Object, MediaType, HttpOutputMessage) write} the
* given object multiple times.
*
* <p>Default implementation returns {@code false}.
* @param t the object t
* @return {@code true} if {@code t} can be written repeatedly;
* {@code false} otherwise
* @since 6.1
*/
protected boolean supportsRepeatableWrites(T t) {
return false;
}
/**
* Indicates whether the given class is supported by this converter.

View File

@@ -178,4 +178,9 @@ public abstract class AbstractKotlinSerializationHttpMessageConverter<T extends
}
return false;
}
@Override
protected boolean supportsRepeatableWrites(Object object) {
return true;
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2022 the original author or authors.
* Copyright 2002-2023 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.
@@ -226,7 +226,17 @@ public class BufferedImageHttpMessageConverter implements HttpMessageConverter<B
outputMessage.getHeaders().setContentType(selectedContentType);
if (outputMessage instanceof StreamingHttpOutputMessage streamingOutputMessage) {
streamingOutputMessage.setBody(outputStream -> writeInternal(image, selectedContentType, outputStream));
streamingOutputMessage.setBody(new StreamingHttpOutputMessage.Body() {
@Override
public void writeTo(OutputStream outputStream) throws IOException {
BufferedImageHttpMessageConverter.this.writeInternal(image, selectedContentType, outputStream);
}
@Override
public boolean repeatable() {
return true;
}
});
}
else {
writeInternal(image, selectedContentType, outputMessage.getBody());

View File

@@ -67,4 +67,8 @@ public class ByteArrayHttpMessageConverter extends AbstractHttpMessageConverter<
StreamUtils.copy(bytes, outputMessage.getBody());
}
@Override
protected boolean supportsRepeatableWrites(byte[] bytes) {
return true;
}
}

View File

@@ -400,7 +400,17 @@ public class FormHttpMessageConverter implements HttpMessageConverter<MultiValue
outputMessage.getHeaders().setContentLength(bytes.length);
if (outputMessage instanceof StreamingHttpOutputMessage streamingOutputMessage) {
streamingOutputMessage.setBody(outputStream -> StreamUtils.copy(bytes, outputStream));
streamingOutputMessage.setBody(new StreamingHttpOutputMessage.Body() {
@Override
public void writeTo(OutputStream outputStream) throws IOException {
StreamUtils.copy(bytes, outputStream);
}
@Override
public boolean repeatable() {
return true;
}
});
}
else {
StreamUtils.copy(bytes, outputMessage.getBody());

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2018 the original author or authors.
* Copyright 2002-2023 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.
@@ -135,4 +135,8 @@ public class ObjectToStringHttpMessageConverter extends AbstractHttpMessageConve
return this.stringHttpMessageConverter.getContentLength(value, contentType);
}
@Override
protected boolean supportsRepeatableWrites(Object o) {
return true;
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2022 the original author or authors.
* Copyright 2002-2023 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.
@@ -166,4 +166,8 @@ public class ResourceHttpMessageConverter extends AbstractHttpMessageConverter<R
}
}
@Override
protected boolean supportsRepeatableWrites(Resource resource) {
return !(resource instanceof InputStreamResource);
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2022 the original author or authors.
* Copyright 2002-2023 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.
@@ -24,6 +24,7 @@ import java.lang.reflect.Type;
import java.nio.charset.StandardCharsets;
import java.util.Collection;
import org.springframework.core.io.InputStreamResource;
import org.springframework.core.io.Resource;
import org.springframework.core.io.support.ResourceRegion;
import org.springframework.http.HttpHeaders;
@@ -238,4 +239,24 @@ public class ResourceRegionHttpMessageConverter extends AbstractGenericHttpMessa
os.write(buf.getBytes(StandardCharsets.US_ASCII));
}
@Override
@SuppressWarnings("unchecked")
protected boolean supportsRepeatableWrites(Object object) {
if (object instanceof ResourceRegion resourceRegion) {
return supportsRepeatableWrites(resourceRegion);
}
else {
Collection<ResourceRegion> regions = (Collection<ResourceRegion>) object;
for (ResourceRegion region : regions) {
if (!supportsRepeatableWrites(region)) {
return false;
}
}
return true;
}
}
private boolean supportsRepeatableWrites(ResourceRegion region) {
return !(region.getResource() instanceof InputStreamResource);
}
}

View File

@@ -163,4 +163,8 @@ public class StringHttpMessageConverter extends AbstractHttpMessageConverter<Str
return charset;
}
@Override
protected boolean supportsRepeatableWrites(String s) {
return true;
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2022 the original author or authors.
* Copyright 2002-2023 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.
@@ -107,4 +107,8 @@ public abstract class AbstractWireFeedHttpMessageConverter<T extends WireFeed>
}
}
@Override
protected boolean supportsRepeatableWrites(T t) {
return true;
}
}

View File

@@ -568,4 +568,8 @@ public abstract class AbstractJackson2HttpMessageConverter extends AbstractGener
return super.getContentLength(object, contentType);
}
@Override
protected boolean supportsRepeatableWrites(Object o) {
return true;
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2020 the original author or authors.
* Copyright 2002-2023 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.
@@ -107,4 +107,8 @@ public class GsonHttpMessageConverter extends AbstractJsonHttpMessageConverter {
}
}
@Override
protected boolean supportsRepeatableWrites(Object o) {
return true;
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2022 the original author or authors.
* Copyright 2002-2023 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.
@@ -110,4 +110,8 @@ public class JsonbHttpMessageConverter extends AbstractJsonHttpMessageConverter
}
}
@Override
protected boolean supportsRepeatableWrites(Object o) {
return true;
}
}

View File

@@ -247,6 +247,10 @@ public class ProtobufHttpMessageConverter extends AbstractHttpMessageConverter<M
response.getHeaders().set(X_PROTOBUF_MESSAGE_HEADER, message.getDescriptorForType().getFullName());
}
@Override
protected boolean supportsRepeatableWrites(Message message) {
return true;
}
/**
* Protobuf format support.

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2021 the original author or authors.
* Copyright 2002-2023 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.
@@ -200,6 +200,11 @@ public class Jaxb2RootElementHttpMessageConverter extends AbstractJaxb2HttpMessa
}
}
@Override
protected boolean supportsRepeatableWrites(Object o) {
return true;
}
private static final EntityResolver NO_OP_ENTITY_RESOLVER =
(publicId, systemId) -> new InputSource(new StringReader(""));

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2022 the original author or authors.
* Copyright 2002-2023 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.
@@ -137,4 +137,8 @@ public class MarshallingHttpMessageConverter extends AbstractXmlHttpMessageConve
this.marshaller.marshal(o, result);
}
@Override
protected boolean supportsRepeatableWrites(Object o) {
return true;
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2022 the original author or authors.
* Copyright 2002-2023 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.
@@ -269,6 +269,11 @@ public class SourceHttpMessageConverter<T extends Source> extends AbstractHttpMe
this.transformerFactory.newTransformer().transform(source, result);
}
@Override
protected boolean supportsRepeatableWrites(T t) {
return t instanceof DOMSource;
}
private static class CountingOutputStream extends OutputStream {