Add PartHttpMessageWriter
Closes gh-24535
This commit is contained in:
@@ -17,11 +17,9 @@
|
||||
package org.springframework.http.codec.multipart;
|
||||
|
||||
import java.nio.charset.Charset;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
@@ -49,11 +47,9 @@ import org.springframework.http.ReactiveHttpOutputMessage;
|
||||
import org.springframework.http.codec.EncoderHttpMessageWriter;
|
||||
import org.springframework.http.codec.FormHttpMessageWriter;
|
||||
import org.springframework.http.codec.HttpMessageWriter;
|
||||
import org.springframework.http.codec.LoggingCodecSupport;
|
||||
import org.springframework.http.codec.ResourceHttpMessageWriter;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.MimeTypeUtils;
|
||||
import org.springframework.util.MultiValueMap;
|
||||
|
||||
/**
|
||||
@@ -77,14 +73,9 @@ import org.springframework.util.MultiValueMap;
|
||||
* @since 5.0
|
||||
* @see FormHttpMessageWriter
|
||||
*/
|
||||
public class MultipartHttpMessageWriter extends LoggingCodecSupport
|
||||
public class MultipartHttpMessageWriter extends MultipartWriterSupport
|
||||
implements HttpMessageWriter<MultiValueMap<String, ?>> {
|
||||
|
||||
/**
|
||||
* THe default charset used by the writer.
|
||||
*/
|
||||
public static final Charset DEFAULT_CHARSET = StandardCharsets.UTF_8;
|
||||
|
||||
/** Suppress logging from individual part writers (full map logged at this level). */
|
||||
private static final Map<String, Object> DEFAULT_HINTS = Hints.from(Hints.SUPPRESS_LOGGING_HINT, true);
|
||||
|
||||
@@ -94,10 +85,6 @@ public class MultipartHttpMessageWriter extends LoggingCodecSupport
|
||||
@Nullable
|
||||
private final HttpMessageWriter<MultiValueMap<String, String>> formWriter;
|
||||
|
||||
private Charset charset = DEFAULT_CHARSET;
|
||||
|
||||
private final List<MediaType> supportedMediaTypes;
|
||||
|
||||
|
||||
/**
|
||||
* Constructor with a default list of part writers (String and Resource).
|
||||
@@ -126,9 +113,9 @@ public class MultipartHttpMessageWriter extends LoggingCodecSupport
|
||||
public MultipartHttpMessageWriter(List<HttpMessageWriter<?>> partWriters,
|
||||
@Nullable HttpMessageWriter<MultiValueMap<String, String>> formWriter) {
|
||||
|
||||
super(initMediaTypes(formWriter));
|
||||
this.partWriters = partWriters;
|
||||
this.formWriter = formWriter;
|
||||
this.supportedMediaTypes = initMediaTypes(formWriter);
|
||||
}
|
||||
|
||||
private static List<MediaType> initMediaTypes(@Nullable HttpMessageWriter<?> formWriter) {
|
||||
@@ -168,25 +155,6 @@ public class MultipartHttpMessageWriter extends LoggingCodecSupport
|
||||
this.charset = charset;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the configured charset for part headers.
|
||||
*/
|
||||
public Charset getCharset() {
|
||||
return this.charset;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public List<MediaType> getWritableMediaTypes() {
|
||||
return this.supportedMediaTypes;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canWrite(ResolvableType elementType, @Nullable MediaType mediaType) {
|
||||
return (MultiValueMap.class.isAssignableFrom(elementType.toClass()) &&
|
||||
(mediaType == null ||
|
||||
this.supportedMediaTypes.stream().anyMatch(element -> element.isCompatibleWith(mediaType))));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<Void> write(Publisher<? extends MultiValueMap<String, ?>> inputStream,
|
||||
@@ -225,16 +193,7 @@ public class MultipartHttpMessageWriter extends LoggingCodecSupport
|
||||
|
||||
byte[] boundary = generateMultipartBoundary();
|
||||
|
||||
Map<String, String> params = new HashMap<>();
|
||||
if (mediaType != null) {
|
||||
params.putAll(mediaType.getParameters());
|
||||
}
|
||||
params.put("boundary", new String(boundary, StandardCharsets.US_ASCII));
|
||||
params.put("charset", getCharset().name());
|
||||
|
||||
mediaType = (mediaType != null ? mediaType : MediaType.MULTIPART_FORM_DATA);
|
||||
mediaType = new MediaType(mediaType, params);
|
||||
|
||||
mediaType = getMultipartMediaType(mediaType, boundary);
|
||||
outputMessage.getHeaders().setContentType(mediaType);
|
||||
|
||||
LogFormatUtils.traceDebug(logger, traceOn -> Hints.getLogPrefix(hints) + "Encoding " +
|
||||
@@ -252,14 +211,6 @@ public class MultipartHttpMessageWriter extends LoggingCodecSupport
|
||||
return outputMessage.writeWith(body);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate a multipart boundary.
|
||||
* <p>By default delegates to {@link MimeTypeUtils#generateMultipartBoundary()}.
|
||||
*/
|
||||
protected byte[] generateMultipartBoundary() {
|
||||
return MimeTypeUtils.generateMultipartBoundary();
|
||||
}
|
||||
|
||||
private Flux<DataBuffer> encodePartValues(
|
||||
byte[] boundary, String name, List<?> values, DataBufferFactory bufferFactory) {
|
||||
|
||||
@@ -268,15 +219,15 @@ public class MultipartHttpMessageWriter extends LoggingCodecSupport
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private <T> Flux<DataBuffer> encodePart(byte[] boundary, String name, T value, DataBufferFactory bufferFactory) {
|
||||
MultipartHttpOutputMessage outputMessage = new MultipartHttpOutputMessage(bufferFactory, getCharset());
|
||||
HttpHeaders outputHeaders = outputMessage.getHeaders();
|
||||
private <T> Flux<DataBuffer> encodePart(byte[] boundary, String name, T value, DataBufferFactory factory) {
|
||||
MultipartHttpOutputMessage message = new MultipartHttpOutputMessage(factory);
|
||||
HttpHeaders headers = message.getHeaders();
|
||||
|
||||
T body;
|
||||
ResolvableType resolvableType = null;
|
||||
if (value instanceof HttpEntity) {
|
||||
HttpEntity<T> httpEntity = (HttpEntity<T>) value;
|
||||
outputHeaders.putAll(httpEntity.getHeaders());
|
||||
headers.putAll(httpEntity.getHeaders());
|
||||
body = httpEntity.getBody();
|
||||
Assert.state(body != null, "MultipartHttpMessageWriter only supports HttpEntity with body");
|
||||
if (httpEntity instanceof ResolvableTypeProvider) {
|
||||
@@ -290,20 +241,20 @@ public class MultipartHttpMessageWriter extends LoggingCodecSupport
|
||||
resolvableType = ResolvableType.forClass(body.getClass());
|
||||
}
|
||||
|
||||
if (!outputHeaders.containsKey(HttpHeaders.CONTENT_DISPOSITION)) {
|
||||
if (!headers.containsKey(HttpHeaders.CONTENT_DISPOSITION)) {
|
||||
if (body instanceof Resource) {
|
||||
outputHeaders.setContentDispositionFormData(name, ((Resource) body).getFilename());
|
||||
headers.setContentDispositionFormData(name, ((Resource) body).getFilename());
|
||||
}
|
||||
else if (resolvableType.resolve() == Resource.class) {
|
||||
body = (T) Mono.from((Publisher<?>) body).doOnNext(o -> outputHeaders
|
||||
body = (T) Mono.from((Publisher<?>) body).doOnNext(o -> headers
|
||||
.setContentDispositionFormData(name, ((Resource) o).getFilename()));
|
||||
}
|
||||
else {
|
||||
outputHeaders.setContentDispositionFormData(name, null);
|
||||
headers.setContentDispositionFormData(name, null);
|
||||
}
|
||||
}
|
||||
|
||||
MediaType contentType = outputHeaders.getContentType();
|
||||
MediaType contentType = headers.getContentType();
|
||||
|
||||
final ResolvableType finalBodyType = resolvableType;
|
||||
Optional<HttpMessageWriter<?>> writer = this.partWriters.stream()
|
||||
@@ -321,62 +272,24 @@ public class MultipartHttpMessageWriter extends LoggingCodecSupport
|
||||
// but only stores the body Flux and returns Mono.empty().
|
||||
|
||||
Mono<Void> partContentReady = ((HttpMessageWriter<T>) writer.get())
|
||||
.write(bodyPublisher, resolvableType, contentType, outputMessage, DEFAULT_HINTS);
|
||||
.write(bodyPublisher, resolvableType, contentType, message, DEFAULT_HINTS);
|
||||
|
||||
// After partContentReady, we can access the part content from MultipartHttpOutputMessage
|
||||
// and use it for writing to the actual request body
|
||||
|
||||
Flux<DataBuffer> partContent = partContentReady.thenMany(Flux.defer(outputMessage::getBody));
|
||||
Flux<DataBuffer> partContent = partContentReady.thenMany(Flux.defer(message::getBody));
|
||||
|
||||
return Flux.concat(
|
||||
generateBoundaryLine(boundary, bufferFactory),
|
||||
generateBoundaryLine(boundary, factory),
|
||||
partContent,
|
||||
generateNewLine(bufferFactory));
|
||||
generateNewLine(factory));
|
||||
}
|
||||
|
||||
|
||||
private Mono<DataBuffer> generateBoundaryLine(byte[] boundary, DataBufferFactory bufferFactory) {
|
||||
return Mono.fromCallable(() -> {
|
||||
DataBuffer buffer = bufferFactory.allocateBuffer(boundary.length + 4);
|
||||
buffer.write((byte)'-');
|
||||
buffer.write((byte)'-');
|
||||
buffer.write(boundary);
|
||||
buffer.write((byte)'\r');
|
||||
buffer.write((byte)'\n');
|
||||
return buffer;
|
||||
});
|
||||
}
|
||||
|
||||
private Mono<DataBuffer> generateNewLine(DataBufferFactory bufferFactory) {
|
||||
return Mono.fromCallable(() -> {
|
||||
DataBuffer buffer = bufferFactory.allocateBuffer(2);
|
||||
buffer.write((byte)'\r');
|
||||
buffer.write((byte)'\n');
|
||||
return buffer;
|
||||
});
|
||||
}
|
||||
|
||||
private Mono<DataBuffer> generateLastLine(byte[] boundary, DataBufferFactory bufferFactory) {
|
||||
return Mono.fromCallable(() -> {
|
||||
DataBuffer buffer = bufferFactory.allocateBuffer(boundary.length + 6);
|
||||
buffer.write((byte)'-');
|
||||
buffer.write((byte)'-');
|
||||
buffer.write(boundary);
|
||||
buffer.write((byte)'-');
|
||||
buffer.write((byte)'-');
|
||||
buffer.write((byte)'\r');
|
||||
buffer.write((byte)'\n');
|
||||
return buffer;
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
private static class MultipartHttpOutputMessage implements ReactiveHttpOutputMessage {
|
||||
private class MultipartHttpOutputMessage implements ReactiveHttpOutputMessage {
|
||||
|
||||
private final DataBufferFactory bufferFactory;
|
||||
|
||||
private final Charset charset;
|
||||
|
||||
private final HttpHeaders headers = new HttpHeaders();
|
||||
|
||||
private final AtomicBoolean committed = new AtomicBoolean();
|
||||
@@ -384,9 +297,8 @@ public class MultipartHttpMessageWriter extends LoggingCodecSupport
|
||||
@Nullable
|
||||
private Flux<DataBuffer> body;
|
||||
|
||||
public MultipartHttpOutputMessage(DataBufferFactory bufferFactory, Charset charset) {
|
||||
public MultipartHttpOutputMessage(DataBufferFactory bufferFactory) {
|
||||
this.bufferFactory = bufferFactory;
|
||||
this.charset = charset;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -414,33 +326,12 @@ public class MultipartHttpMessageWriter extends LoggingCodecSupport
|
||||
if (this.body != null) {
|
||||
return Mono.error(new IllegalStateException("Multiple calls to writeWith() not supported"));
|
||||
}
|
||||
this.body = generateHeaders().concatWith(body);
|
||||
this.body = generatePartHeaders(this.headers, this.bufferFactory).concatWith(body);
|
||||
|
||||
// We don't actually want to write (just save the body Flux)
|
||||
return Mono.empty();
|
||||
}
|
||||
|
||||
private Mono<DataBuffer> generateHeaders() {
|
||||
return Mono.fromCallable(() -> {
|
||||
DataBuffer buffer = this.bufferFactory.allocateBuffer();
|
||||
for (Map.Entry<String, List<String>> entry : this.headers.entrySet()) {
|
||||
byte[] headerName = entry.getKey().getBytes(this.charset);
|
||||
for (String headerValueString : entry.getValue()) {
|
||||
byte[] headerValue = headerValueString.getBytes(this.charset);
|
||||
buffer.write(headerName);
|
||||
buffer.write((byte)':');
|
||||
buffer.write((byte)' ');
|
||||
buffer.write(headerValue);
|
||||
buffer.write((byte)'\r');
|
||||
buffer.write((byte)'\n');
|
||||
}
|
||||
}
|
||||
buffer.write((byte)'\r');
|
||||
buffer.write((byte)'\n');
|
||||
return buffer;
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<Void> writeAndFlushWith(Publisher<? extends Publisher<? extends DataBuffer>> body) {
|
||||
return Mono.error(new UnsupportedOperationException());
|
||||
|
||||
@@ -0,0 +1,168 @@
|
||||
/*
|
||||
* Copyright 2002-2020 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.multipart;
|
||||
|
||||
import java.nio.charset.Charset;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import org.springframework.core.ResolvableType;
|
||||
import org.springframework.core.io.buffer.DataBuffer;
|
||||
import org.springframework.core.io.buffer.DataBufferFactory;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.http.codec.LoggingCodecSupport;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.MimeTypeUtils;
|
||||
import org.springframework.util.MultiValueMap;
|
||||
|
||||
/**
|
||||
* Support class for multipart HTTP message writers.
|
||||
*
|
||||
* @author Rossen Stoyanchev
|
||||
* @since 5.3
|
||||
*/
|
||||
public class MultipartWriterSupport extends LoggingCodecSupport {
|
||||
|
||||
/** THe default charset used by the writer. */
|
||||
public static final Charset DEFAULT_CHARSET = StandardCharsets.UTF_8;
|
||||
|
||||
protected final List<MediaType> supportedMediaTypes;
|
||||
|
||||
protected Charset charset = DEFAULT_CHARSET;
|
||||
|
||||
|
||||
/**
|
||||
* Constructor with the list of supported media types.
|
||||
*/
|
||||
protected MultipartWriterSupport(List<MediaType> supportedMediaTypes) {
|
||||
this.supportedMediaTypes = supportedMediaTypes;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Return the configured charset for part headers.
|
||||
*/
|
||||
public Charset getCharset() {
|
||||
return this.charset;
|
||||
}
|
||||
|
||||
public List<MediaType> getWritableMediaTypes() {
|
||||
return this.supportedMediaTypes;
|
||||
}
|
||||
|
||||
|
||||
public boolean canWrite(ResolvableType elementType, @Nullable MediaType mediaType) {
|
||||
if (MultiValueMap.class.isAssignableFrom(elementType.toClass())) {
|
||||
if (mediaType == null) {
|
||||
return true;
|
||||
}
|
||||
for (MediaType supportedMediaType : this.supportedMediaTypes) {
|
||||
if (supportedMediaType.isCompatibleWith(mediaType)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate a multipart boundary.
|
||||
* <p>By default delegates to {@link MimeTypeUtils#generateMultipartBoundary()}.
|
||||
*/
|
||||
protected byte[] generateMultipartBoundary() {
|
||||
return MimeTypeUtils.generateMultipartBoundary();
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepare the {@code MediaType} to use by adding "boundary" and "charset"
|
||||
* parameters to the given {@code mediaType} or "mulitpart/form-data"
|
||||
* otherwise by default.
|
||||
*/
|
||||
protected MediaType getMultipartMediaType(@Nullable MediaType mediaType, byte[] boundary) {
|
||||
Map<String, String> params = new HashMap<>();
|
||||
if (mediaType != null) {
|
||||
params.putAll(mediaType.getParameters());
|
||||
}
|
||||
params.put("boundary", new String(boundary, StandardCharsets.US_ASCII));
|
||||
params.put("charset", getCharset().name());
|
||||
|
||||
mediaType = (mediaType != null ? mediaType : MediaType.MULTIPART_FORM_DATA);
|
||||
mediaType = new MediaType(mediaType, params);
|
||||
return mediaType;
|
||||
}
|
||||
|
||||
protected Mono<DataBuffer> generateBoundaryLine(byte[] boundary, DataBufferFactory bufferFactory) {
|
||||
return Mono.fromCallable(() -> {
|
||||
DataBuffer buffer = bufferFactory.allocateBuffer(boundary.length + 4);
|
||||
buffer.write((byte)'-');
|
||||
buffer.write((byte)'-');
|
||||
buffer.write(boundary);
|
||||
buffer.write((byte)'\r');
|
||||
buffer.write((byte)'\n');
|
||||
return buffer;
|
||||
});
|
||||
}
|
||||
|
||||
protected Mono<DataBuffer> generateNewLine(DataBufferFactory bufferFactory) {
|
||||
return Mono.fromCallable(() -> {
|
||||
DataBuffer buffer = bufferFactory.allocateBuffer(2);
|
||||
buffer.write((byte)'\r');
|
||||
buffer.write((byte)'\n');
|
||||
return buffer;
|
||||
});
|
||||
}
|
||||
|
||||
protected Mono<DataBuffer> generateLastLine(byte[] boundary, DataBufferFactory bufferFactory) {
|
||||
return Mono.fromCallable(() -> {
|
||||
DataBuffer buffer = bufferFactory.allocateBuffer(boundary.length + 6);
|
||||
buffer.write((byte)'-');
|
||||
buffer.write((byte)'-');
|
||||
buffer.write(boundary);
|
||||
buffer.write((byte)'-');
|
||||
buffer.write((byte)'-');
|
||||
buffer.write((byte)'\r');
|
||||
buffer.write((byte)'\n');
|
||||
return buffer;
|
||||
});
|
||||
}
|
||||
|
||||
protected Mono<DataBuffer> generatePartHeaders(HttpHeaders headers, DataBufferFactory bufferFactory) {
|
||||
return Mono.fromCallable(() -> {
|
||||
DataBuffer buffer = bufferFactory.allocateBuffer();
|
||||
for (Map.Entry<String, List<String>> entry : headers.entrySet()) {
|
||||
byte[] headerName = entry.getKey().getBytes(getCharset());
|
||||
for (String headerValueString : entry.getValue()) {
|
||||
byte[] headerValue = headerValueString.getBytes(getCharset());
|
||||
buffer.write(headerName);
|
||||
buffer.write((byte)':');
|
||||
buffer.write((byte)' ');
|
||||
buffer.write(headerValue);
|
||||
buffer.write((byte)'\r');
|
||||
buffer.write((byte)'\n');
|
||||
}
|
||||
}
|
||||
buffer.write((byte)'\r');
|
||||
buffer.write((byte)'\n');
|
||||
return buffer;
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,90 @@
|
||||
/*
|
||||
* Copyright 2002-2020 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.multipart;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import org.reactivestreams.Publisher;
|
||||
import reactor.core.publisher.Flux;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import org.springframework.core.ResolvableType;
|
||||
import org.springframework.core.codec.Hints;
|
||||
import org.springframework.core.io.buffer.DataBuffer;
|
||||
import org.springframework.core.io.buffer.DataBufferFactory;
|
||||
import org.springframework.core.io.buffer.PooledDataBuffer;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.http.ReactiveHttpOutputMessage;
|
||||
import org.springframework.http.codec.HttpMessageWriter;
|
||||
import org.springframework.lang.Nullable;
|
||||
|
||||
/**
|
||||
* {@link HttpMessageWriter} for writing with {@link Part}. This can be useful
|
||||
* on the server side to write a {@code Flux<Part>} received from a client to
|
||||
* some remote service.
|
||||
*
|
||||
* @author Rossen Stoyanchev
|
||||
* @since 5.3
|
||||
*/
|
||||
public class PartHttpMessageWriter extends MultipartWriterSupport implements HttpMessageWriter<Part> {
|
||||
|
||||
|
||||
public PartHttpMessageWriter() {
|
||||
super(MultipartHttpMessageReader.MIME_TYPES);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public Mono<Void> write(Publisher<? extends Part> parts,
|
||||
ResolvableType elementType, @Nullable MediaType mediaType, ReactiveHttpOutputMessage outputMessage,
|
||||
Map<String, Object> hints) {
|
||||
|
||||
byte[] boundary = generateMultipartBoundary();
|
||||
|
||||
mediaType = getMultipartMediaType(mediaType, boundary);
|
||||
outputMessage.getHeaders().setContentType(mediaType);
|
||||
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug(Hints.getLogPrefix(hints) + "Encoding Publisher<Part>");
|
||||
}
|
||||
|
||||
Flux<DataBuffer> body = Flux.from(parts)
|
||||
.concatMap(part -> encodePart(boundary, part, outputMessage.bufferFactory()))
|
||||
.concatWith(generateLastLine(boundary, outputMessage.bufferFactory()))
|
||||
.doOnDiscard(PooledDataBuffer.class, PooledDataBuffer::release);
|
||||
|
||||
return outputMessage.writeWith(body);
|
||||
}
|
||||
|
||||
private <T> Flux<DataBuffer> encodePart(byte[] boundary, Part part, DataBufferFactory bufferFactory) {
|
||||
HttpHeaders headers = new HttpHeaders(part.headers());
|
||||
|
||||
String name = part.name();
|
||||
if (!headers.containsKey(HttpHeaders.CONTENT_DISPOSITION)) {
|
||||
headers.setContentDispositionFormData(name,
|
||||
(part instanceof FilePart ? ((FilePart) part).filename() : null));
|
||||
}
|
||||
|
||||
return Flux.concat(
|
||||
generateBoundaryLine(boundary, bufferFactory),
|
||||
generatePartHeaders(headers, bufferFactory),
|
||||
part.content(),
|
||||
generateNewLine(bufferFactory));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2019 the original author or authors.
|
||||
* Copyright 2002-2020 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.
|
||||
@@ -23,6 +23,7 @@ import org.springframework.http.codec.HttpMessageWriter;
|
||||
import org.springframework.http.codec.ServerCodecConfigurer;
|
||||
import org.springframework.http.codec.ServerSentEventHttpMessageWriter;
|
||||
import org.springframework.http.codec.multipart.MultipartHttpMessageReader;
|
||||
import org.springframework.http.codec.multipart.PartHttpMessageWriter;
|
||||
import org.springframework.http.codec.multipart.SynchronossPartHttpMessageReader;
|
||||
import org.springframework.lang.Nullable;
|
||||
|
||||
@@ -74,6 +75,11 @@ class ServerDefaultCodecsImpl extends BaseDefaultCodecs implements ServerCodecCo
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void extendTypedWriters(List<HttpMessageWriter<?>> typedWriters) {
|
||||
addCodec(typedWriters, new PartHttpMessageWriter());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void extendObjectWriters(List<HttpMessageWriter<?>> objectWriters) {
|
||||
objectWriters.add(new ServerSentEventHttpMessageWriter(getSseEncoder()));
|
||||
|
||||
Reference in New Issue
Block a user