Merge branch '5.1.x'
This commit is contained in:
@@ -125,13 +125,19 @@ public class EncoderHttpMessageWriter<T> implements HttpMessageWriter<T> {
|
||||
}))
|
||||
.flatMap(buffer -> {
|
||||
headers.setContentLength(buffer.readableByteCount());
|
||||
return message.writeWith(Mono.fromCallable(() -> buffer)
|
||||
.doOnDiscard(PooledDataBuffer.class, PooledDataBuffer::release));
|
||||
return message.writeWith(
|
||||
Mono.fromCallable(() -> buffer)
|
||||
.doOnDiscard(PooledDataBuffer.class, PooledDataBuffer::release));
|
||||
});
|
||||
}
|
||||
|
||||
return (isStreamingMediaType(contentType) ?
|
||||
message.writeAndFlushWith(body.map(Flux::just)) : message.writeWith(body));
|
||||
if (isStreamingMediaType(contentType)) {
|
||||
return message.writeAndFlushWith(body.map(buffer ->
|
||||
Mono.fromCallable(() -> buffer)
|
||||
.doOnDiscard(PooledDataBuffer.class, PooledDataBuffer::release)));
|
||||
}
|
||||
|
||||
return message.writeWith(body);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@@ -162,10 +168,16 @@ public class EncoderHttpMessageWriter<T> implements HttpMessageWriter<T> {
|
||||
}
|
||||
|
||||
private boolean isStreamingMediaType(@Nullable MediaType contentType) {
|
||||
return (contentType != null && this.encoder instanceof HttpMessageEncoder &&
|
||||
((HttpMessageEncoder<?>) this.encoder).getStreamingMediaTypes().stream()
|
||||
.anyMatch(streamingMediaType -> contentType.isCompatibleWith(streamingMediaType) &&
|
||||
contentType.getParameters().entrySet().containsAll(streamingMediaType.getParameters().keySet())));
|
||||
if (contentType == null || !(this.encoder instanceof HttpMessageEncoder)) {
|
||||
return false;
|
||||
}
|
||||
for (MediaType mediaType : ((HttpMessageEncoder<?>) this.encoder).getStreamingMediaTypes()) {
|
||||
if (contentType.isCompatibleWith(mediaType) &&
|
||||
contentType.getParameters().entrySet().containsAll(mediaType.getParameters().keySet())) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -56,7 +56,7 @@ public class FormHttpMessageReader extends LoggingCodecSupport
|
||||
*/
|
||||
public static final Charset DEFAULT_CHARSET = StandardCharsets.UTF_8;
|
||||
|
||||
private static final ResolvableType MULTIVALUE_TYPE =
|
||||
private static final ResolvableType MULTIVALUE_STRINGS_TYPE =
|
||||
ResolvableType.forClassWithGenerics(MultiValueMap.class, String.class, String.class);
|
||||
|
||||
|
||||
@@ -83,9 +83,11 @@ public class FormHttpMessageReader extends LoggingCodecSupport
|
||||
|
||||
@Override
|
||||
public boolean canRead(ResolvableType elementType, @Nullable MediaType mediaType) {
|
||||
return ((MULTIVALUE_TYPE.isAssignableFrom(elementType) ||
|
||||
(elementType.hasUnresolvableGenerics() &&
|
||||
MultiValueMap.class.isAssignableFrom(elementType.toClass()))) &&
|
||||
boolean multiValueUnresolved =
|
||||
elementType.hasUnresolvableGenerics() &&
|
||||
MultiValueMap.class.isAssignableFrom(elementType.toClass());
|
||||
|
||||
return ((MULTIVALUE_STRINGS_TYPE.isAssignableFrom(elementType) || multiValueUnresolved) &&
|
||||
(mediaType == null || MediaType.APPLICATION_FORM_URLENCODED.isCompatibleWith(mediaType)));
|
||||
}
|
||||
|
||||
|
||||
@@ -164,8 +164,8 @@ public class ServerSentEventHttpMessageReader implements HttpMessageReader<Objec
|
||||
}
|
||||
|
||||
byte[] bytes = data.getBytes(StandardCharsets.UTF_8);
|
||||
Mono<DataBuffer> input = Mono.just(bufferFactory.wrap(bytes));
|
||||
return this.decoder.decodeToMono(input, dataType, MediaType.TEXT_EVENT_STREAM, hints);
|
||||
DataBuffer buffer = bufferFactory.wrap(bytes); // wrapping only, no allocation
|
||||
return this.decoder.decodeToMono(Mono.just(buffer), dataType, MediaType.TEXT_EVENT_STREAM, hints);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -184,7 +184,7 @@ public class ServerSentEventHttpMessageWriter implements HttpMessageWriter<Objec
|
||||
private Mono<DataBuffer> encodeText(CharSequence text, MediaType mediaType, DataBufferFactory bufferFactory) {
|
||||
Assert.notNull(mediaType.getCharset(), "Expected MediaType with charset");
|
||||
byte[] bytes = text.toString().getBytes(mediaType.getCharset());
|
||||
return Mono.fromCallable(() -> bufferFactory.wrap(bytes)); // wrapping, not allocating
|
||||
return Mono.just(bufferFactory.wrap(bytes)); // wrapping, not allocating
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -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.
|
||||
@@ -77,6 +77,7 @@ public class ProtobufDecoder extends ProtobufCodecSupport implements Decoder<Mes
|
||||
|
||||
private static final ConcurrentMap<Class<?>, Method> methodCache = new ConcurrentReferenceHashMap<>();
|
||||
|
||||
|
||||
private final ExtensionRegistry extensionRegistry;
|
||||
|
||||
private int maxMessageSize = DEFAULT_MESSAGE_MAX_SIZE;
|
||||
@@ -114,8 +115,12 @@ public class ProtobufDecoder extends ProtobufCodecSupport implements Decoder<Mes
|
||||
public Flux<Message> decode(Publisher<DataBuffer> inputStream, ResolvableType elementType,
|
||||
@Nullable MimeType mimeType, @Nullable Map<String, Object> hints) {
|
||||
|
||||
MessageDecoderFunction decoderFunction =
|
||||
new MessageDecoderFunction(elementType, this.maxMessageSize);
|
||||
|
||||
return Flux.from(inputStream)
|
||||
.flatMapIterable(new MessageDecoderFunction(elementType, this.maxMessageSize));
|
||||
.flatMapIterable(decoderFunction)
|
||||
.doOnTerminate(decoderFunction::discard);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -212,12 +217,13 @@ public class ProtobufDecoder extends ProtobufCodecSupport implements Decoder<Mes
|
||||
this.messageBytesToRead -= chunkBytesToRead;
|
||||
|
||||
if (this.messageBytesToRead == 0) {
|
||||
Message.Builder builder = getMessageBuilder(this.elementType.toClass());
|
||||
ByteBuffer buffer = this.output.asByteBuffer();
|
||||
builder.mergeFrom(CodedInputStream.newInstance(buffer), extensionRegistry);
|
||||
messages.add(builder.build());
|
||||
CodedInputStream stream = CodedInputStream.newInstance(this.output.asByteBuffer());
|
||||
DataBufferUtils.release(this.output);
|
||||
this.output = null;
|
||||
Message message = getMessageBuilder(this.elementType.toClass())
|
||||
.mergeFrom(stream, extensionRegistry)
|
||||
.build();
|
||||
messages.add(message);
|
||||
}
|
||||
} while (remainingBytesToRead > 0);
|
||||
return messages;
|
||||
@@ -286,6 +292,12 @@ public class ProtobufDecoder extends ProtobufCodecSupport implements Decoder<Mes
|
||||
this.offset = 0;
|
||||
throw new DecodingException("Cannot parse message size: malformed varint");
|
||||
}
|
||||
|
||||
public void discard() {
|
||||
if (this.output != null) {
|
||||
DataBufferUtils.release(this.output);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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.
|
||||
@@ -17,7 +17,6 @@
|
||||
package org.springframework.http.codec.protobuf;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
@@ -31,6 +30,7 @@ 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.core.io.buffer.DataBufferUtils;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.http.codec.HttpMessageEncoder;
|
||||
import org.springframework.lang.Nullable;
|
||||
@@ -73,26 +73,29 @@ public class ProtobufEncoder extends ProtobufCodecSupport implements HttpMessage
|
||||
public Flux<DataBuffer> encode(Publisher<? extends Message> inputStream, DataBufferFactory bufferFactory,
|
||||
ResolvableType elementType, @Nullable MimeType mimeType, @Nullable Map<String, Object> hints) {
|
||||
|
||||
return Flux
|
||||
.from(inputStream)
|
||||
.map(message -> encodeMessage(message, bufferFactory, !(inputStream instanceof Mono)));
|
||||
}
|
||||
|
||||
private DataBuffer encodeMessage(Message message, DataBufferFactory bufferFactory, boolean streaming) {
|
||||
DataBuffer buffer = bufferFactory.allocateBuffer();
|
||||
OutputStream outputStream = buffer.asOutputStream();
|
||||
try {
|
||||
if (streaming) {
|
||||
message.writeDelimitedTo(outputStream);
|
||||
}
|
||||
else {
|
||||
message.writeTo(outputStream);
|
||||
}
|
||||
return buffer;
|
||||
}
|
||||
catch (IOException ex) {
|
||||
throw new IllegalStateException("Unexpected I/O error while writing to data buffer", ex);
|
||||
}
|
||||
return Flux.from(inputStream)
|
||||
.map(message -> {
|
||||
DataBuffer buffer = bufferFactory.allocateBuffer();
|
||||
boolean release = true;
|
||||
try {
|
||||
if (!(inputStream instanceof Mono)) {
|
||||
message.writeDelimitedTo(buffer.asOutputStream());
|
||||
}
|
||||
else {
|
||||
message.writeTo(buffer.asOutputStream());
|
||||
}
|
||||
release = false;
|
||||
return buffer;
|
||||
}
|
||||
catch (IOException ex) {
|
||||
throw new IllegalStateException("Unexpected I/O error while writing to data buffer", ex);
|
||||
}
|
||||
finally {
|
||||
if (release) {
|
||||
DataBufferUtils.release(buffer);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -111,13 +111,13 @@ public class Jaxb2XmlEncoder extends AbstractSingleValueEncoder<Object> {
|
||||
return Flux.defer(() -> {
|
||||
boolean release = true;
|
||||
DataBuffer buffer = bufferFactory.allocateBuffer(1024);
|
||||
OutputStream outputStream = buffer.asOutputStream();
|
||||
Class<?> clazz = ClassUtils.getUserClass(value);
|
||||
try {
|
||||
OutputStream outputStream = buffer.asOutputStream();
|
||||
Class<?> clazz = ClassUtils.getUserClass(value);
|
||||
Marshaller marshaller = initMarshaller(clazz);
|
||||
marshaller.marshal(value, outputStream);
|
||||
release = false;
|
||||
return Mono.fromCallable(() -> buffer); // Rely on doOnDiscard in base class
|
||||
return Mono.fromCallable(() -> buffer); // relying on doOnDiscard in base class
|
||||
}
|
||||
catch (MarshalException ex) {
|
||||
return Flux.error(new EncodingException(
|
||||
|
||||
@@ -35,7 +35,6 @@ import com.fasterxml.aalto.stax.InputFactoryImpl;
|
||||
import org.reactivestreams.Publisher;
|
||||
import reactor.core.Exceptions;
|
||||
import reactor.core.publisher.Flux;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import org.springframework.core.ResolvableType;
|
||||
import org.springframework.core.codec.AbstractDecoder;
|
||||
@@ -97,30 +96,32 @@ public class XmlEventDecoder extends AbstractDecoder<XMLEvent> {
|
||||
|
||||
@Override
|
||||
@SuppressWarnings({"rawtypes", "unchecked", "cast"}) // on JDK 9 where XMLEventReader is Iterator<Object> instead of simply Iterator
|
||||
public Flux<XMLEvent> decode(Publisher<DataBuffer> inputStream, ResolvableType elementType,
|
||||
public Flux<XMLEvent> decode(Publisher<DataBuffer> input, ResolvableType elementType,
|
||||
@Nullable MimeType mimeType, @Nullable Map<String, Object> hints) {
|
||||
|
||||
Flux<DataBuffer> flux = Flux.from(inputStream);
|
||||
if (this.useAalto) {
|
||||
AaltoDataBufferToXmlEvent aaltoMapper = new AaltoDataBufferToXmlEvent();
|
||||
return flux.flatMap(aaltoMapper)
|
||||
.doFinally(signalType -> aaltoMapper.endOfInput());
|
||||
AaltoDataBufferToXmlEvent mapper = new AaltoDataBufferToXmlEvent();
|
||||
return Flux.from(input)
|
||||
.flatMapIterable(mapper)
|
||||
.doFinally(signalType -> mapper.endOfInput());
|
||||
}
|
||||
else {
|
||||
Mono<DataBuffer> singleBuffer = DataBufferUtils.join(flux);
|
||||
return singleBuffer.flatMapIterable(dataBuffer -> {
|
||||
InputStream is = dataBuffer.asInputStream();
|
||||
return () -> {
|
||||
try {
|
||||
// Explicit cast to (Iterator) is necessary on JDK 9+ since XMLEventReader
|
||||
// now extends Iterator<Object> instead of simply Iterator
|
||||
return (Iterator) inputFactory.createXMLEventReader(is);
|
||||
}
|
||||
catch (XMLStreamException ex) {
|
||||
throw Exceptions.propagate(ex);
|
||||
}
|
||||
};
|
||||
});
|
||||
return DataBufferUtils.join(input).
|
||||
flatMapIterable(buffer -> {
|
||||
try {
|
||||
InputStream is = buffer.asInputStream();
|
||||
Iterator eventReader = inputFactory.createXMLEventReader(is);
|
||||
List<XMLEvent> result = new ArrayList<>();
|
||||
eventReader.forEachRemaining(event -> result.add((XMLEvent) event));
|
||||
return result;
|
||||
}
|
||||
catch (XMLStreamException ex) {
|
||||
throw Exceptions.propagate(ex);
|
||||
}
|
||||
finally {
|
||||
DataBufferUtils.release(buffer);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -128,7 +129,7 @@ public class XmlEventDecoder extends AbstractDecoder<XMLEvent> {
|
||||
/*
|
||||
* Separate static class to isolate Aalto dependency.
|
||||
*/
|
||||
private static class AaltoDataBufferToXmlEvent implements Function<DataBuffer, Publisher<? extends XMLEvent>> {
|
||||
private static class AaltoDataBufferToXmlEvent implements Function<DataBuffer, List<? extends XMLEvent>> {
|
||||
|
||||
private static final AsyncXMLInputFactory inputFactory =
|
||||
StaxUtils.createDefensiveInputFactory(InputFactoryImpl::new);
|
||||
@@ -140,7 +141,7 @@ public class XmlEventDecoder extends AbstractDecoder<XMLEvent> {
|
||||
|
||||
|
||||
@Override
|
||||
public Publisher<? extends XMLEvent> apply(DataBuffer dataBuffer) {
|
||||
public List<? extends XMLEvent> apply(DataBuffer dataBuffer) {
|
||||
try {
|
||||
this.streamReader.getInputFeeder().feedInput(dataBuffer.asByteBuffer());
|
||||
List<XMLEvent> events = new ArrayList<>();
|
||||
@@ -157,10 +158,10 @@ public class XmlEventDecoder extends AbstractDecoder<XMLEvent> {
|
||||
}
|
||||
}
|
||||
}
|
||||
return Flux.fromIterable(events);
|
||||
return events;
|
||||
}
|
||||
catch (XMLStreamException ex) {
|
||||
return Mono.error(ex);
|
||||
throw Exceptions.propagate(ex);
|
||||
}
|
||||
finally {
|
||||
DataBufferUtils.release(dataBuffer);
|
||||
|
||||
@@ -180,8 +180,7 @@ public abstract class AbstractServerHttpResponse implements ServerHttpResponse {
|
||||
|
||||
@Override
|
||||
public final Mono<Void> writeAndFlushWith(Publisher<? extends Publisher<? extends DataBuffer>> body) {
|
||||
return new ChannelSendOperator<>(body,
|
||||
writePublisher -> doCommit(() -> writeAndFlushWithInternal(writePublisher)))
|
||||
return new ChannelSendOperator<>(body, inner -> doCommit(() -> writeAndFlushWithInternal(inner)))
|
||||
.doOnError(t -> removeContentLength());
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user