Skip to content

Commit

Permalink
Allow repeatable writes in HttpMessageConverter
Browse files Browse the repository at this point in the history
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
  • Loading branch information
poutsma committed Nov 2, 2023
1 parent ab316d9 commit 6dd93d4
Show file tree
Hide file tree
Showing 22 changed files with 206 additions and 25 deletions.
Original file line number Diff line number Diff line change
@@ -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.
Expand Down Expand Up @@ -88,16 +88,27 @@ public final void write(final T t, @Nullable final Type type, @Nullable MediaTyp
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);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -210,16 +210,27 @@ public final void write(final T t, @Nullable MediaType contentType, HttpOutputMe
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);
Expand Down Expand Up @@ -289,6 +300,21 @@ protected Long getContentLength(T t, @Nullable MediaType contentType) throws IOE
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.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -178,4 +178,9 @@ private boolean hasPolymorphism(SerialDescriptor descriptor, Set<String> already
}
return false;
}

@Override
protected boolean supportsRepeatableWrites(Object object) {
return true;
}
}
Original file line number Diff line number Diff line change
@@ -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.
Expand Down Expand Up @@ -226,7 +226,17 @@ public void write(final BufferedImage image, @Nullable final MediaType contentTy
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());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -67,4 +67,8 @@ protected void writeInternal(byte[] bytes, HttpOutputMessage outputMessage) thro
StreamUtils.copy(bytes, outputMessage.getBody());
}

@Override
protected boolean supportsRepeatableWrites(byte[] bytes) {
return true;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -400,7 +400,17 @@ private void writeForm(MultiValueMap<String, Object> formData, @Nullable MediaTy
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());
Expand Down
Original file line number Diff line number Diff line change
@@ -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.
Expand Down Expand Up @@ -135,4 +135,8 @@ protected Long getContentLength(Object obj, @Nullable MediaType contentType) {
return this.stringHttpMessageConverter.getContentLength(value, contentType);
}

@Override
protected boolean supportsRepeatableWrites(Object o) {
return true;
}
}
Original file line number Diff line number Diff line change
@@ -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.
Expand Down Expand Up @@ -166,4 +166,8 @@ protected void writeContent(Resource resource, HttpOutputMessage outputMessage)
}
}

@Override
protected boolean supportsRepeatableWrites(Resource resource) {
return !(resource instanceof InputStreamResource);
}
}
Original file line number Diff line number Diff line change
@@ -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.
Expand All @@ -24,6 +24,7 @@
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;
Expand Down Expand Up @@ -238,4 +239,24 @@ private static void print(OutputStream os, String buf) throws IOException {
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);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -163,4 +163,8 @@ else if (contentType.isCompatibleWith(MediaType.APPLICATION_JSON) ||
return charset;
}

@Override
protected boolean supportsRepeatableWrites(String s) {
return true;
}
}
Original file line number Diff line number Diff line change
@@ -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.
Expand Down Expand Up @@ -107,4 +107,8 @@ protected void writeInternal(T wireFeed, HttpOutputMessage outputMessage)
}
}

@Override
protected boolean supportsRepeatableWrites(T t) {
return true;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -568,4 +568,8 @@ protected Long getContentLength(Object object, @Nullable MediaType contentType)
return super.getContentLength(object, contentType);
}

@Override
protected boolean supportsRepeatableWrites(Object o) {
return true;
}
}
Original file line number Diff line number Diff line change
@@ -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.
Expand Down Expand Up @@ -107,4 +107,8 @@ protected void writeInternal(Object object, @Nullable Type type, Writer writer)
}
}

@Override
protected boolean supportsRepeatableWrites(Object o) {
return true;
}
}
Original file line number Diff line number Diff line change
@@ -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.
Expand Down Expand Up @@ -110,4 +110,8 @@ protected void writeInternal(Object object, @Nullable Type type, Writer writer)
}
}

@Override
protected boolean supportsRepeatableWrites(Object o) {
return true;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -247,6 +247,10 @@ private void setProtoHeader(HttpOutputMessage response, Message message) {
response.getHeaders().set(X_PROTOBUF_MESSAGE_HEADER, message.getDescriptorForType().getFullName());
}

@Override
protected boolean supportsRepeatableWrites(Message message) {
return true;
}

/**
* Protobuf format support.
Expand Down
Original file line number Diff line number Diff line change
@@ -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.
Expand Down Expand Up @@ -200,6 +200,11 @@ private void setCharset(@Nullable MediaType contentType, Marshaller marshaller)
}
}

@Override
protected boolean supportsRepeatableWrites(Object o) {
return true;
}


private static final EntityResolver NO_OP_ENTITY_RESOLVER =
(publicId, systemId) -> new InputSource(new StringReader(""));
Expand Down
Original file line number Diff line number Diff line change
@@ -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.
Expand Down Expand Up @@ -137,4 +137,8 @@ protected void writeToResult(Object o, HttpHeaders headers, Result result) throw
this.marshaller.marshal(o, result);
}

@Override
protected boolean supportsRepeatableWrites(Object o) {
return true;
}
}
Original file line number Diff line number Diff line change
@@ -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.
Expand Down Expand Up @@ -269,6 +269,11 @@ private void transform(Source source, Result result) throws TransformerException
this.transformerFactory.newTransformer().transform(source, result);
}

@Override
protected boolean supportsRepeatableWrites(T t) {
return t instanceof DOMSource;
}


private static class CountingOutputStream extends OutputStream {

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -79,4 +79,18 @@ public void write() throws IOException {
assertThat(outputMessage.getHeaders().getContentLength()).isEqualTo(2);
}

@Test
public void repeatableWrites() throws IOException {
MockHttpOutputMessage outputMessage1 = new MockHttpOutputMessage();
byte[] body = new byte[]{0x1, 0x2};
assertThat(converter.supportsRepeatableWrites(body)).isTrue();

converter.write(body, null, outputMessage1);
assertThat(outputMessage1.getBodyAsBytes()).isEqualTo(body);

MockHttpOutputMessage outputMessage2 = new MockHttpOutputMessage();
converter.write(body, null, outputMessage2);
assertThat(outputMessage2.getBodyAsBytes()).isEqualTo(body);
}

}
Loading

0 comments on commit 6dd93d4

Please sign in to comment.