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:
@@ -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);
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -178,4 +178,9 @@ public abstract class AbstractKotlinSerializationHttpMessageConverter<T extends
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean supportsRepeatableWrites(Object object) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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());
|
||||
|
||||
@@ -67,4 +67,8 @@ public class ByteArrayHttpMessageConverter extends AbstractHttpMessageConverter<
|
||||
StreamUtils.copy(bytes, outputMessage.getBody());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean supportsRepeatableWrites(byte[] bytes) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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());
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -163,4 +163,8 @@ public class StringHttpMessageConverter extends AbstractHttpMessageConverter<Str
|
||||
return charset;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean supportsRepeatableWrites(String s) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -568,4 +568,8 @@ public abstract class AbstractJackson2HttpMessageConverter extends AbstractGener
|
||||
return super.getContentLength(object, contentType);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean supportsRepeatableWrites(Object o) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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(""));
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
|
||||
Reference in New Issue
Block a user