Polish during review of DataBuffer handling
This commit is contained in:
@@ -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.
|
||||
@@ -54,17 +54,17 @@ public abstract class AbstractDataBufferDecoder<T> extends AbstractDecoder<T> {
|
||||
|
||||
|
||||
@Override
|
||||
public Flux<T> decode(Publisher<DataBuffer> inputStream, ResolvableType elementType,
|
||||
public Flux<T> decode(Publisher<DataBuffer> input, ResolvableType elementType,
|
||||
@Nullable MimeType mimeType, @Nullable Map<String, Object> hints) {
|
||||
|
||||
return Flux.from(inputStream).map(buffer -> decodeDataBuffer(buffer, elementType, mimeType, hints));
|
||||
return Flux.from(input).map(buffer -> decodeDataBuffer(buffer, elementType, mimeType, hints));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<T> decodeToMono(Publisher<DataBuffer> inputStream, ResolvableType elementType,
|
||||
public Mono<T> decodeToMono(Publisher<DataBuffer> input, ResolvableType elementType,
|
||||
@Nullable MimeType mimeType, @Nullable Map<String, Object> hints) {
|
||||
|
||||
return DataBufferUtils.join(inputStream)
|
||||
return DataBufferUtils.join(input)
|
||||
.map(buffer -> decodeDataBuffer(buffer, elementType, mimeType, hints));
|
||||
}
|
||||
|
||||
|
||||
@@ -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.
|
||||
@@ -78,7 +78,12 @@ public abstract class AbstractDecoder<T> implements Decoder<T> {
|
||||
if (mimeType == null) {
|
||||
return true;
|
||||
}
|
||||
return this.decodableMimeTypes.stream().anyMatch(candidate -> candidate.isCompatibleWith(mimeType));
|
||||
for (MimeType candidate : this.decodableMimeTypes) {
|
||||
if (candidate.isCompatibleWith(mimeType)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@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.
|
||||
@@ -74,7 +74,12 @@ public abstract class AbstractEncoder<T> implements Encoder<T> {
|
||||
if (mimeType == null) {
|
||||
return true;
|
||||
}
|
||||
return this.encodableMimeTypes.stream().anyMatch(candidate -> candidate.isCompatibleWith(mimeType));
|
||||
for (MimeType candidate : this.encodableMimeTypes) {
|
||||
if (candidate.isCompatibleWith(mimeType)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -57,10 +57,10 @@ public class DataBufferDecoder extends AbstractDataBufferDecoder<DataBuffer> {
|
||||
}
|
||||
|
||||
@Override
|
||||
public Flux<DataBuffer> decode(Publisher<DataBuffer> inputStream, ResolvableType elementType,
|
||||
public Flux<DataBuffer> decode(Publisher<DataBuffer> input, ResolvableType elementType,
|
||||
@Nullable MimeType mimeType, @Nullable Map<String, Object> hints) {
|
||||
|
||||
return Flux.from(inputStream);
|
||||
return Flux.from(input);
|
||||
}
|
||||
|
||||
@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.
|
||||
@@ -65,15 +65,14 @@ public class ResourceEncoder extends AbstractSingleValueEncoder<Resource> {
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Flux<DataBuffer> encode(Resource resource, DataBufferFactory dataBufferFactory,
|
||||
protected Flux<DataBuffer> encode(Resource resource, DataBufferFactory bufferFactory,
|
||||
ResolvableType type, @Nullable MimeType mimeType, @Nullable Map<String, Object> hints) {
|
||||
|
||||
if (logger.isDebugEnabled() && !Hints.isLoggingSuppressed(hints)) {
|
||||
String logPrefix = Hints.getLogPrefix(hints);
|
||||
logger.debug(logPrefix + "Writing [" + resource + "]");
|
||||
}
|
||||
|
||||
return DataBufferUtils.read(resource, dataBufferFactory, this.bufferSize);
|
||||
return DataBufferUtils.read(resource, bufferFactory, this.bufferSize);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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.
|
||||
@@ -76,16 +76,16 @@ public class ResourceRegionEncoder extends AbstractEncoder<ResourceRegion> {
|
||||
}
|
||||
|
||||
@Override
|
||||
public Flux<DataBuffer> encode(Publisher<? extends ResourceRegion> inputStream,
|
||||
public Flux<DataBuffer> encode(Publisher<? extends ResourceRegion> input,
|
||||
DataBufferFactory bufferFactory, ResolvableType elementType, @Nullable MimeType mimeType,
|
||||
@Nullable Map<String, Object> hints) {
|
||||
|
||||
Assert.notNull(inputStream, "'inputStream' must not be null");
|
||||
Assert.notNull(input, "'inputStream' must not be null");
|
||||
Assert.notNull(bufferFactory, "'bufferFactory' must not be null");
|
||||
Assert.notNull(elementType, "'elementType' must not be null");
|
||||
|
||||
if (inputStream instanceof Mono) {
|
||||
return Mono.from(inputStream)
|
||||
if (input instanceof Mono) {
|
||||
return Mono.from(input)
|
||||
.flatMapMany(region -> {
|
||||
if (!region.getResource().isReadable()) {
|
||||
return Flux.error(new EncodingException(
|
||||
@@ -96,32 +96,25 @@ public class ResourceRegionEncoder extends AbstractEncoder<ResourceRegion> {
|
||||
}
|
||||
else {
|
||||
final String boundaryString = Hints.getRequiredHint(hints, BOUNDARY_STRING_HINT);
|
||||
byte[] startBoundary = getAsciiBytes("\r\n--" + boundaryString + "\r\n");
|
||||
byte[] contentType = mimeType != null ? getAsciiBytes("Content-Type: " + mimeType + "\r\n") : new byte[0];
|
||||
byte[] startBoundary = toAsciiBytes("\r\n--" + boundaryString + "\r\n");
|
||||
byte[] contentType = mimeType != null ? toAsciiBytes("Content-Type: " + mimeType + "\r\n") : new byte[0];
|
||||
|
||||
return Flux.from(inputStream).
|
||||
concatMap(region -> {
|
||||
return Flux.from(input)
|
||||
.concatMap(region -> {
|
||||
if (!region.getResource().isReadable()) {
|
||||
return Flux.error(new EncodingException(
|
||||
"Resource " + region.getResource() + " is not readable"));
|
||||
}
|
||||
else {
|
||||
return Flux.concat(
|
||||
getRegionPrefix(bufferFactory, startBoundary, contentType, region),
|
||||
writeResourceRegion(region, bufferFactory, hints));
|
||||
}
|
||||
Flux<DataBuffer> prefix = Flux.just(
|
||||
bufferFactory.wrap(startBoundary),
|
||||
bufferFactory.wrap(contentType),
|
||||
bufferFactory.wrap(getContentRangeHeader(region))); // only wrapping, no allocation
|
||||
|
||||
return prefix.concatWith(writeResourceRegion(region, bufferFactory, hints));
|
||||
})
|
||||
.concatWith(getRegionSuffix(bufferFactory, boundaryString));
|
||||
.concatWithValues(getRegionSuffix(bufferFactory, boundaryString));
|
||||
}
|
||||
}
|
||||
|
||||
private Flux<DataBuffer> getRegionPrefix(DataBufferFactory bufferFactory, byte[] startBoundary,
|
||||
byte[] contentType, ResourceRegion region) {
|
||||
|
||||
return Flux.just(
|
||||
bufferFactory.wrap(startBoundary),
|
||||
bufferFactory.wrap(contentType),
|
||||
bufferFactory.wrap(getContentRangeHeader(region))); // only wrapping, no allocation
|
||||
// No doOnDiscard (no caching after DataBufferUtils#read)
|
||||
}
|
||||
|
||||
private Flux<DataBuffer> writeResourceRegion(
|
||||
@@ -140,12 +133,12 @@ public class ResourceRegionEncoder extends AbstractEncoder<ResourceRegion> {
|
||||
return DataBufferUtils.takeUntilByteCount(in, count);
|
||||
}
|
||||
|
||||
private Flux<DataBuffer> getRegionSuffix(DataBufferFactory bufferFactory, String boundaryString) {
|
||||
byte[] endBoundary = getAsciiBytes("\r\n--" + boundaryString + "--");
|
||||
return Flux.just(bufferFactory.wrap(endBoundary));
|
||||
private DataBuffer getRegionSuffix(DataBufferFactory bufferFactory, String boundaryString) {
|
||||
byte[] endBoundary = toAsciiBytes("\r\n--" + boundaryString + "--");
|
||||
return bufferFactory.wrap(endBoundary);
|
||||
}
|
||||
|
||||
private byte[] getAsciiBytes(String in) {
|
||||
private byte[] toAsciiBytes(String in) {
|
||||
return in.getBytes(StandardCharsets.US_ASCII);
|
||||
}
|
||||
|
||||
@@ -155,10 +148,10 @@ public class ResourceRegionEncoder extends AbstractEncoder<ResourceRegion> {
|
||||
OptionalLong contentLength = contentLength(region.getResource());
|
||||
if (contentLength.isPresent()) {
|
||||
long length = contentLength.getAsLong();
|
||||
return getAsciiBytes("Content-Range: bytes " + start + '-' + end + '/' + length + "\r\n\r\n");
|
||||
return toAsciiBytes("Content-Range: bytes " + start + '-' + end + '/' + length + "\r\n\r\n");
|
||||
}
|
||||
else {
|
||||
return getAsciiBytes("Content-Range: bytes " + start + '-' + end + "\r\n\r\n");
|
||||
return toAsciiBytes("Content-Range: bytes " + start + '-' + end + "\r\n\r\n");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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.
|
||||
@@ -25,7 +25,6 @@ import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.ConcurrentMap;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.reactivestreams.Publisher;
|
||||
import reactor.core.publisher.Flux;
|
||||
@@ -88,14 +87,14 @@ public final class StringDecoder extends AbstractDataBufferDecoder<String> {
|
||||
}
|
||||
|
||||
@Override
|
||||
public Flux<String> decode(Publisher<DataBuffer> inputStream, ResolvableType elementType,
|
||||
public Flux<String> decode(Publisher<DataBuffer> input, ResolvableType elementType,
|
||||
@Nullable MimeType mimeType, @Nullable Map<String, Object> hints) {
|
||||
|
||||
List<byte[]> delimiterBytes = getDelimiterBytes(mimeType);
|
||||
|
||||
Flux<DataBuffer> inputFlux = Flux.from(inputStream)
|
||||
.flatMapIterable(dataBuffer -> splitOnDelimiter(dataBuffer, delimiterBytes))
|
||||
.bufferUntil(StringDecoder::isEndFrame)
|
||||
Flux<DataBuffer> inputFlux = Flux.from(input)
|
||||
.flatMapIterable(buffer -> splitOnDelimiter(buffer, delimiterBytes))
|
||||
.bufferUntil(buffer -> buffer == END_FRAME)
|
||||
.map(StringDecoder::joinUntilEndFrame)
|
||||
.doOnDiscard(PooledDataBuffer.class, DataBufferUtils::release);
|
||||
|
||||
@@ -103,51 +102,60 @@ public final class StringDecoder extends AbstractDataBufferDecoder<String> {
|
||||
}
|
||||
|
||||
private List<byte[]> getDelimiterBytes(@Nullable MimeType mimeType) {
|
||||
return this.delimitersCache.computeIfAbsent(getCharset(mimeType),
|
||||
charset -> this.delimiters.stream()
|
||||
.map(s -> s.getBytes(charset))
|
||||
.collect(Collectors.toList()));
|
||||
return this.delimitersCache.computeIfAbsent(getCharset(mimeType), charset -> {
|
||||
List<byte[]> list = new ArrayList<>();
|
||||
for (String delimiter : this.delimiters) {
|
||||
byte[] bytes = delimiter.getBytes(charset);
|
||||
list.add(bytes);
|
||||
}
|
||||
return list;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Split the given data buffer on delimiter boundaries.
|
||||
* The returned Flux contains an {@link #END_FRAME} buffer after each delimiter.
|
||||
*/
|
||||
private List<DataBuffer> splitOnDelimiter(DataBuffer dataBuffer, List<byte[]> delimiterBytes) {
|
||||
private List<DataBuffer> splitOnDelimiter(DataBuffer buffer, List<byte[]> delimiterBytes) {
|
||||
List<DataBuffer> frames = new ArrayList<>();
|
||||
do {
|
||||
int length = Integer.MAX_VALUE;
|
||||
byte[] matchingDelimiter = null;
|
||||
for (byte[] delimiter : delimiterBytes) {
|
||||
int index = indexOf(dataBuffer, delimiter);
|
||||
if (index >= 0 && index < length) {
|
||||
length = index;
|
||||
matchingDelimiter = delimiter;
|
||||
try {
|
||||
do {
|
||||
int length = Integer.MAX_VALUE;
|
||||
byte[] matchingDelimiter = null;
|
||||
for (byte[] delimiter : delimiterBytes) {
|
||||
int index = indexOf(buffer, delimiter);
|
||||
if (index >= 0 && index < length) {
|
||||
length = index;
|
||||
matchingDelimiter = delimiter;
|
||||
}
|
||||
}
|
||||
}
|
||||
DataBuffer frame;
|
||||
int readPosition = dataBuffer.readPosition();
|
||||
if (matchingDelimiter != null) {
|
||||
if (this.stripDelimiter) {
|
||||
frame = dataBuffer.slice(readPosition, length);
|
||||
DataBuffer frame;
|
||||
int readPosition = buffer.readPosition();
|
||||
if (matchingDelimiter != null) {
|
||||
frame = this.stripDelimiter ?
|
||||
buffer.slice(readPosition, length) :
|
||||
buffer.slice(readPosition, length + matchingDelimiter.length);
|
||||
buffer.readPosition(readPosition + length + matchingDelimiter.length);
|
||||
frames.add(DataBufferUtils.retain(frame));
|
||||
frames.add(END_FRAME);
|
||||
}
|
||||
else {
|
||||
frame = dataBuffer.slice(readPosition, length + matchingDelimiter.length);
|
||||
frame = buffer.slice(readPosition, buffer.readableByteCount());
|
||||
buffer.readPosition(readPosition + buffer.readableByteCount());
|
||||
frames.add(DataBufferUtils.retain(frame));
|
||||
}
|
||||
dataBuffer.readPosition(readPosition + length + matchingDelimiter.length);
|
||||
|
||||
frames.add(DataBufferUtils.retain(frame));
|
||||
frames.add(END_FRAME);
|
||||
}
|
||||
else {
|
||||
frame = dataBuffer.slice(readPosition, dataBuffer.readableByteCount());
|
||||
dataBuffer.readPosition(readPosition + dataBuffer.readableByteCount());
|
||||
frames.add(DataBufferUtils.retain(frame));
|
||||
}
|
||||
while (buffer.readableByteCount() > 0);
|
||||
}
|
||||
catch (Throwable ex) {
|
||||
for (DataBuffer frame : frames) {
|
||||
DataBufferUtils.release(frame);
|
||||
}
|
||||
throw ex;
|
||||
}
|
||||
finally {
|
||||
DataBufferUtils.release(buffer);
|
||||
}
|
||||
while (dataBuffer.readableByteCount() > 0);
|
||||
|
||||
DataBufferUtils.release(dataBuffer);
|
||||
return frames;
|
||||
}
|
||||
|
||||
@@ -155,44 +163,38 @@ public final class StringDecoder extends AbstractDataBufferDecoder<String> {
|
||||
* Find the given delimiter in the given data buffer.
|
||||
* @return the index of the delimiter, or -1 if not found.
|
||||
*/
|
||||
private static int indexOf(DataBuffer dataBuffer, byte[] delimiter) {
|
||||
for (int i = dataBuffer.readPosition(); i < dataBuffer.writePosition(); i++) {
|
||||
int dataBufferPos = i;
|
||||
private static int indexOf(DataBuffer buffer, byte[] delimiter) {
|
||||
for (int i = buffer.readPosition(); i < buffer.writePosition(); i++) {
|
||||
int bufferPos = i;
|
||||
int delimiterPos = 0;
|
||||
while (delimiterPos < delimiter.length) {
|
||||
if (dataBuffer.getByte(dataBufferPos) != delimiter[delimiterPos]) {
|
||||
if (buffer.getByte(bufferPos) != delimiter[delimiterPos]) {
|
||||
break;
|
||||
}
|
||||
else {
|
||||
dataBufferPos++;
|
||||
if (dataBufferPos == dataBuffer.writePosition() &&
|
||||
delimiterPos != delimiter.length - 1) {
|
||||
bufferPos++;
|
||||
boolean endOfBuffer = bufferPos == buffer.writePosition();
|
||||
boolean endOfDelimiter = delimiterPos == delimiter.length - 1;
|
||||
if (endOfBuffer && !endOfDelimiter) {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
delimiterPos++;
|
||||
}
|
||||
if (delimiterPos == delimiter.length) {
|
||||
return i - dataBuffer.readPosition();
|
||||
return i - buffer.readPosition();
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check whether the given buffer is {@link #END_FRAME}.
|
||||
*/
|
||||
private static boolean isEndFrame(DataBuffer dataBuffer) {
|
||||
return dataBuffer == END_FRAME;
|
||||
}
|
||||
|
||||
/**
|
||||
* Join the given list of buffers into a single buffer.
|
||||
*/
|
||||
private static DataBuffer joinUntilEndFrame(List<DataBuffer> dataBuffers) {
|
||||
if (!dataBuffers.isEmpty()) {
|
||||
int lastIdx = dataBuffers.size() - 1;
|
||||
if (isEndFrame(dataBuffers.get(lastIdx))) {
|
||||
if (dataBuffers.get(lastIdx) == END_FRAME) {
|
||||
dataBuffers.remove(lastIdx);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user