diff --git a/spring-core/src/main/java/org/springframework/core/codec/StringDecoder.java b/spring-core/src/main/java/org/springframework/core/codec/StringDecoder.java
index fc403bebd9a..f8a376711ac 100644
--- a/spring-core/src/main/java/org/springframework/core/codec/StringDecoder.java
+++ b/spring-core/src/main/java/org/springframework/core/codec/StringDecoder.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2017 the original author or authors.
+ * Copyright 2002-2018 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.
@@ -20,26 +20,31 @@ import java.nio.CharBuffer;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
+import java.util.Arrays;
import java.util.List;
import java.util.Map;
-import java.util.function.IntPredicate;
+import java.util.stream.Collectors;
import org.reactivestreams.Publisher;
import reactor.core.publisher.Flux;
+import reactor.core.publisher.Mono;
import org.springframework.core.ResolvableType;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.core.io.buffer.DataBufferUtils;
+import org.springframework.core.io.buffer.DefaultDataBufferFactory;
import org.springframework.lang.Nullable;
+import org.springframework.util.Assert;
import org.springframework.util.MimeType;
import org.springframework.util.MimeTypeUtils;
/**
- * Decode from a bytes stream to a {@code String} stream.
+ * Decode from a data buffer stream to a {@code String} stream. Before decoding, this decoder
+ * realigns the incoming data buffers so that each buffer ends with a newline.
+ * This is to make sure that multibyte characters are decoded properly, and do not cross buffer
+ * boundaries. The default delimiters ({@code \n}, {@code \r\n})can be customized.
*
- *
By default, this decoder will split the received {@link DataBuffer}s
- * along newline characters ({@code \r\n}), but this can be changed by
- * passing {@code false} as a constructor argument.
+ *
Partially inspired by Netty's {@code DelimiterBasedFrameDecoder}.
*
* @author Sebastien Deleuze
* @author Brian Clozel
@@ -50,22 +55,28 @@ import org.springframework.util.MimeTypeUtils;
*/
public class StringDecoder extends AbstractDataBufferDecoder {
+ private static final DataBuffer END_FRAME = new DefaultDataBufferFactory().wrap(new byte[0]);
+
+ /**
+ * The default charset to use, i.e. "UTF-8".
+ */
public static final Charset DEFAULT_CHARSET = StandardCharsets.UTF_8;
- private static final IntPredicate NEWLINE_DELIMITER = b -> b == '\n' || b == '\r';
+ /**
+ * The default delimiter strings to use, i.e. {@code \n} and {@code \r\n}.
+ */
+ public static final List DEFAULT_DELIMITERS = Arrays.asList("\r\n", "\n");
- private final boolean splitOnNewline;
+ private final List delimiters;
+ private final boolean stripDelimiter;
- /**
- * Create a {@code StringDecoder} that decodes a bytes stream to a String stream
- * @param splitOnNewline whether this decoder should split the received data buffers
- * along newline characters
- */
- private StringDecoder(boolean splitOnNewline, MimeType... mimeTypes) {
+ private StringDecoder(List delimiters, boolean stripDelimiter, MimeType... mimeTypes) {
super(mimeTypes);
- this.splitOnNewline = splitOnNewline;
+ Assert.notEmpty(delimiters, "'delimiters' must not be empty");
+ this.delimiters = new ArrayList<>(delimiters);
+ this.stripDelimiter = stripDelimiter;
}
@@ -79,28 +90,112 @@ public class StringDecoder extends AbstractDataBufferDecoder {
public Flux decode(Publisher inputStream, ResolvableType elementType,
@Nullable MimeType mimeType, @Nullable Map hints) {
- Flux inputFlux = Flux.from(inputStream);
- if (this.splitOnNewline) {
- inputFlux = Flux.from(inputStream).flatMap(StringDecoder::splitOnNewline);
- }
+ List delimiterBytes = getDelimiterBytes(mimeType);
+
+ Flux inputFlux = Flux.from(inputStream)
+ .flatMap(dataBuffer -> splitOnDelimiter(dataBuffer, delimiterBytes))
+ .bufferUntil(StringDecoder::isEndFrame)
+ .flatMap(StringDecoder::joinUntilEndFrame);
return super.decode(inputFlux, elementType, mimeType, hints);
}
- private static Flux splitOnNewline(DataBuffer dataBuffer) {
- List results = new ArrayList<>();
- int startIdx = 0;
- int endIdx;
- final int limit = dataBuffer.readableByteCount();
+ private List getDelimiterBytes(@Nullable MimeType mimeType) {
+ Charset charset = getCharset(mimeType);
+ return this.delimiters.stream()
+ .map(s -> s.getBytes(charset))
+ .collect(Collectors.toList());
+ }
+
+ /**
+ * Splits the given data buffer on delimiter boundaries. The returned Flux contains a
+ * {@link #END_FRAME} buffer after each delimiter.
+ */
+ private Flux splitOnDelimiter(DataBuffer dataBuffer, List delimiterBytes) {
+ List frames = new ArrayList<>();
do {
- endIdx = dataBuffer.indexOf(NEWLINE_DELIMITER, startIdx);
- int length = endIdx != -1 ? endIdx - startIdx + 1 : limit - startIdx;
- DataBuffer token = dataBuffer.slice(startIdx, length);
- results.add(DataBufferUtils.retain(token));
- startIdx = endIdx + 1;
+ int length = Integer.MAX_VALUE;
+ byte[] matchingDelimiter = null;
+ for (byte[] delimiter : delimiterBytes) {
+ int idx = indexOf(dataBuffer, delimiter);
+ if (idx >= 0 && idx < length) {
+ length = idx;
+ matchingDelimiter = delimiter;
+ }
+ }
+ DataBuffer frame;
+ int readPosition = dataBuffer.readPosition();
+ if (matchingDelimiter != null) {
+ if (this.stripDelimiter) {
+ frame = dataBuffer.slice(readPosition, length);
+ }
+ else {
+ frame = dataBuffer.slice(readPosition, length + matchingDelimiter.length);
+ }
+ 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 (startIdx < limit && endIdx != -1);
+ while (dataBuffer.readableByteCount() > 0);
+
DataBufferUtils.release(dataBuffer);
- return Flux.fromIterable(results);
+ return Flux.fromIterable(frames);
+ }
+
+ /**
+ * Finds 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;
+ int delimiterPos = 0;
+ while (delimiterPos < delimiter.length) {
+ if (dataBuffer.getByte(dataBufferPos) != delimiter[delimiterPos]) {
+ break;
+ }
+ else {
+ dataBufferPos++;
+ if (dataBufferPos == dataBuffer.writePosition() &&
+ delimiterPos != delimiter.length - 1) {
+ return -1;
+ }
+ }
+ delimiterPos++;
+ }
+
+ if (delimiterPos == delimiter.length) {
+ return i - dataBuffer.readPosition();
+ }
+ }
+ return -1;
+ }
+
+ /**
+ * Checks whether the given buffer is {@link #END_FRAME}.
+ */
+ private static boolean isEndFrame(DataBuffer dataBuffer) {
+ return dataBuffer == END_FRAME;
+ }
+
+ /**
+ * Joins the given list of buffers into a single buffer.
+ */
+ private static Mono joinUntilEndFrame(List dataBuffers) {
+ if (dataBuffers.size() > 0) {
+ int lastIdx = dataBuffers.size() - 1;
+ if (isEndFrame(dataBuffers.get(lastIdx))) {
+ dataBuffers.remove(lastIdx);
+ }
+ }
+ Flux flux = Flux.fromIterable(dataBuffers);
+ return DataBufferUtils.join(flux);
}
@Override
@@ -113,7 +208,7 @@ public class StringDecoder extends AbstractDataBufferDecoder {
return charBuffer.toString();
}
- private Charset getCharset(@Nullable MimeType mimeType) {
+ private static Charset getCharset(@Nullable MimeType mimeType) {
if (mimeType != null && mimeType.getCharset() != null) {
return mimeType.getCharset();
}
@@ -125,19 +220,55 @@ public class StringDecoder extends AbstractDataBufferDecoder {
/**
* Create a {@code StringDecoder} for {@code "text/plain"}.
- * @param splitOnNewline whether to split the byte stream into lines
+ * @param ignored ignored
+ * @deprecated as of Spring 5.0.4, in favor of {@link #textPlainOnly()} or
+ * {@link #textPlainOnly(List, boolean)}.
+ */
+ @Deprecated
+ public static StringDecoder textPlainOnly(boolean ignored) {
+ return textPlainOnly();
+ }
+
+ /**
+ * Create a {@code StringDecoder} for {@code "text/plain"}.
+ */
+ public static StringDecoder textPlainOnly() {
+ return textPlainOnly(DEFAULT_DELIMITERS, true);
+ }
+
+ /**
+ * Create a {@code StringDecoder} for {@code "text/plain"}.
+ */
+ public static StringDecoder textPlainOnly(List delimiters, boolean stripDelimiter) {
+ return new StringDecoder(delimiters, stripDelimiter,
+ new MimeType("text", "plain", DEFAULT_CHARSET));
+ }
+
+ /**
+ * Create a {@code StringDecoder} that supports all MIME types.
+ * @param ignored ignored
+ * @deprecated as of Spring 5.0.4, in favor of {@link #allMimeTypes()} or
+ * {@link #allMimeTypes(List, boolean)}.
*/
- public static StringDecoder textPlainOnly(boolean splitOnNewline) {
- return new StringDecoder(splitOnNewline, new MimeType("text", "plain", DEFAULT_CHARSET));
+ @Deprecated
+ public static StringDecoder allMimeTypes(boolean ignored) {
+ return allMimeTypes();
}
/**
* Create a {@code StringDecoder} that supports all MIME types.
- * @param splitOnNewline whether to split the byte stream into lines
*/
- public static StringDecoder allMimeTypes(boolean splitOnNewline) {
- return new StringDecoder(splitOnNewline,
+ public static StringDecoder allMimeTypes() {
+ return allMimeTypes(DEFAULT_DELIMITERS, true);
+ }
+
+ /**
+ * Create a {@code StringDecoder} that supports all MIME types.
+ */
+ public static StringDecoder allMimeTypes(List delimiters, boolean stripDelimiter) {
+ return new StringDecoder(delimiters, stripDelimiter,
new MimeType("text", "plain", DEFAULT_CHARSET), MimeTypeUtils.ALL);
}
+
}
diff --git a/spring-core/src/main/java/org/springframework/core/io/buffer/DataBuffer.java b/spring-core/src/main/java/org/springframework/core/io/buffer/DataBuffer.java
index 6e83a894bb9..2102b5f30e3 100644
--- a/spring-core/src/main/java/org/springframework/core/io/buffer/DataBuffer.java
+++ b/spring-core/src/main/java/org/springframework/core/io/buffer/DataBuffer.java
@@ -140,7 +140,16 @@ public interface DataBuffer {
DataBuffer writePosition(int writePosition);
/**
- * Read a single byte from the current reading position of this data buffer.
+ * Read a single byte at the given index from this data buffer.
+ * @param index the index at which the byte will be read
+ * @return the byte at the given index
+ * @throws IndexOutOfBoundsException when {@code index} is out of bounds
+ * @since 5.0.4
+ */
+ byte getByte(int index);
+
+ /**
+ * Read a single byte from the current reading position from this data buffer.
* @return the byte at this buffer's current reading position
*/
byte read();
diff --git a/spring-core/src/main/java/org/springframework/core/io/buffer/DataBufferUtils.java b/spring-core/src/main/java/org/springframework/core/io/buffer/DataBufferUtils.java
index 5140a152ad5..308ecb8d955 100644
--- a/spring-core/src/main/java/org/springframework/core/io/buffer/DataBufferUtils.java
+++ b/spring-core/src/main/java/org/springframework/core/io/buffer/DataBufferUtils.java
@@ -515,7 +515,7 @@ public abstract class DataBufferUtils {
* @return a buffer that is composed from the {@code dataBuffers} argument
* @since 5.0.3
*/
- public static Mono join(Publisher extends DataBuffer> dataBuffers) {
+ public static Mono join(Publisher dataBuffers) {
Assert.notNull(dataBuffers, "'dataBuffers' must not be null");
return Flux.from(dataBuffers)
diff --git a/spring-core/src/main/java/org/springframework/core/io/buffer/DefaultDataBuffer.java b/spring-core/src/main/java/org/springframework/core/io/buffer/DefaultDataBuffer.java
index f902b005cb7..704ca10083e 100644
--- a/spring-core/src/main/java/org/springframework/core/io/buffer/DefaultDataBuffer.java
+++ b/spring-core/src/main/java/org/springframework/core/io/buffer/DefaultDataBuffer.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2017 the original author or authors.
+ * Copyright 2002-2018 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.
@@ -148,7 +148,7 @@ public class DefaultDataBuffer implements DataBuffer {
}
@Override
- public DataBuffer readPosition(int readPosition) {
+ public DefaultDataBuffer readPosition(int readPosition) {
assertIndex(readPosition >= 0, "'readPosition' %d must be >= 0", readPosition);
assertIndex(readPosition <= this.writePosition, "'readPosition' %d must be <= %d",
readPosition, this.writePosition);
@@ -163,7 +163,7 @@ public class DefaultDataBuffer implements DataBuffer {
}
@Override
- public DataBuffer writePosition(int writePosition) {
+ public DefaultDataBuffer writePosition(int writePosition) {
assertIndex(writePosition >= this.readPosition, "'writePosition' %d must be >= %d",
writePosition, this.readPosition);
assertIndex(writePosition <= this.capacity, "'writePosition' %d must be <= %d",
@@ -179,7 +179,7 @@ public class DefaultDataBuffer implements DataBuffer {
}
@Override
- public DataBuffer capacity(int newCapacity) {
+ public DefaultDataBuffer capacity(int newCapacity) {
Assert.isTrue(newCapacity > 0,
String.format("'newCapacity' %d must be higher than 0", newCapacity));
@@ -222,6 +222,15 @@ public class DefaultDataBuffer implements DataBuffer {
return direct ? ByteBuffer.allocateDirect(capacity) : ByteBuffer.allocate(capacity);
}
+ @Override
+ public byte getByte(int index) {
+ assertIndex(index >= 0, "index %d must be >= 0", index);
+ assertIndex(index <= this.writePosition - 1, "index %d must be <= %d",
+ index, this.writePosition - 1);
+
+ return this.byteBuffer.get(index);
+ }
+
@Override
public byte read() {
assertIndex(this.readPosition <= this.writePosition - 1, "readPosition %d must be <= %d",
@@ -286,7 +295,7 @@ public class DefaultDataBuffer implements DataBuffer {
}
@Override
- public DataBuffer write(DataBuffer... buffers) {
+ public DefaultDataBuffer write(DataBuffer... buffers) {
if (!ObjectUtils.isEmpty(buffers)) {
ByteBuffer[] byteBuffers =
Arrays.stream(buffers).map(DataBuffer::asByteBuffer)
@@ -315,7 +324,7 @@ public class DefaultDataBuffer implements DataBuffer {
}
@Override
- public DataBuffer slice(int index, int length) {
+ public DefaultDataBuffer slice(int index, int length) {
checkIndex(index, length);
int oldPosition = this.byteBuffer.position();
// Explicit access via Buffer base type for compatibility
@@ -488,7 +497,7 @@ public class DefaultDataBuffer implements DataBuffer {
}
@Override
- public DataBuffer capacity(int newCapacity) {
+ public DefaultDataBuffer capacity(int newCapacity) {
throw new UnsupportedOperationException(
"Changing the capacity of a sliced buffer is not supported");
}
diff --git a/spring-core/src/main/java/org/springframework/core/io/buffer/NettyDataBuffer.java b/spring-core/src/main/java/org/springframework/core/io/buffer/NettyDataBuffer.java
index 7225962159c..ee41d4db89c 100644
--- a/spring-core/src/main/java/org/springframework/core/io/buffer/NettyDataBuffer.java
+++ b/spring-core/src/main/java/org/springframework/core/io/buffer/NettyDataBuffer.java
@@ -107,7 +107,7 @@ public class NettyDataBuffer implements PooledDataBuffer {
}
@Override
- public DataBuffer readPosition(int readPosition) {
+ public NettyDataBuffer readPosition(int readPosition) {
this.byteBuf.readerIndex(readPosition);
return this;
}
@@ -118,18 +118,23 @@ public class NettyDataBuffer implements PooledDataBuffer {
}
@Override
- public DataBuffer writePosition(int writePosition) {
+ public NettyDataBuffer writePosition(int writePosition) {
this.byteBuf.writerIndex(writePosition);
return this;
}
+ @Override
+ public byte getByte(int index) {
+ return this.byteBuf.getByte(index);
+ }
+
@Override
public int capacity() {
return this.byteBuf.capacity();
}
@Override
- public DataBuffer capacity(int capacity) {
+ public NettyDataBuffer capacity(int capacity) {
this.byteBuf.capacity(capacity);
return this;
}
@@ -225,7 +230,7 @@ public class NettyDataBuffer implements PooledDataBuffer {
}
@Override
- public DataBuffer slice(int index, int length) {
+ public NettyDataBuffer slice(int index, int length) {
ByteBuf slice = this.byteBuf.slice(index, length);
return new NettyDataBuffer(slice, this.dataBufferFactory);
}
diff --git a/spring-core/src/test/java/org/springframework/core/codec/StringDecoderTests.java b/spring-core/src/test/java/org/springframework/core/codec/StringDecoderTests.java
index e71b7b5e75a..8fd79c9c236 100644
--- a/spring-core/src/test/java/org/springframework/core/codec/StringDecoderTests.java
+++ b/spring-core/src/test/java/org/springframework/core/codec/StringDecoderTests.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2016 the original author or authors.
+ * Copyright 2002-2018 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.
@@ -16,7 +16,10 @@
package org.springframework.core.codec;
+import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
import java.util.Collections;
+import java.util.List;
import org.junit.Test;
import reactor.core.publisher.Flux;
@@ -28,8 +31,7 @@ import org.springframework.core.io.buffer.AbstractDataBufferAllocatingTestCase;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.util.MimeTypeUtils;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.*;
/**
* @author Sebastien Deleuze
@@ -38,7 +40,7 @@ import static org.junit.Assert.assertTrue;
*/
public class StringDecoderTests extends AbstractDataBufferAllocatingTestCase {
- private StringDecoder decoder = StringDecoder.allMimeTypes(true);
+ private StringDecoder decoder = StringDecoder.allMimeTypes();
@Test
@@ -56,36 +58,88 @@ public class StringDecoderTests extends AbstractDataBufferAllocatingTestCase {
}
@Test
- public void decode() throws InterruptedException {
- this.decoder = StringDecoder.allMimeTypes(false);
- Flux source = Flux.just(stringBuffer("foo"), stringBuffer("bar"), stringBuffer("baz"));
+ public void decodeMultibyteCharacter() {
+ String s = "üéø";
+ Flux source = toSingleByteDataBuffers(s);
+
+ Flux output = this.decoder.decode(source, ResolvableType.forClass(String.class),
+ null, Collections.emptyMap());
+ StepVerifier.create(output)
+ .expectNext(s)
+ .verifyComplete();
+ }
+
+ private Flux toSingleByteDataBuffers(String s) {
+ byte[] bytes = s.getBytes(StandardCharsets.UTF_8);
+
+ List dataBuffers = new ArrayList<>();
+ for (byte b : bytes) {
+ dataBuffers.add(this.bufferFactory.wrap(new byte[]{b}));
+ }
+ return Flux.fromIterable(dataBuffers);
+ }
+
+ @Test
+ public void decodeNewLine() {
+ Flux source = Flux.just(
+ stringBuffer("\r\nabc\n"),
+ stringBuffer("def"),
+ stringBuffer("ghi\r\n\n"),
+ stringBuffer("jkl"),
+ stringBuffer("mno\npqr\n"),
+ stringBuffer("stu"),
+ stringBuffer("vw"),
+ stringBuffer("xyz")
+ );
+
Flux output = this.decoder.decode(source, ResolvableType.forClass(String.class),
null, Collections.emptyMap());
StepVerifier.create(output)
- .expectNext("foo", "bar", "baz")
+ .expectNext("")
+ .expectNext("abc")
+ .expectNext("defghi")
+ .expectNext("")
+ .expectNext("jklmno")
+ .expectNext("pqr")
+ .expectNext("stuvwxyz")
.expectComplete()
.verify();
-
}
@Test
- public void decodeNewLine() throws InterruptedException {
- DataBuffer fooBar = stringBuffer("\nfoo\r\nbar\r");
- DataBuffer baz = stringBuffer("\nbaz");
- Flux source = Flux.just(fooBar, baz);
- Flux output = decoder.decode(source, ResolvableType.forClass(String.class),
+ public void decodeNewLineIncludeDelimiters() {
+
+ decoder = StringDecoder.allMimeTypes(StringDecoder.DEFAULT_DELIMITERS, false);
+
+ Flux source = Flux.just(
+ stringBuffer("\r\nabc\n"),
+ stringBuffer("def"),
+ stringBuffer("ghi\r\n\n"),
+ stringBuffer("jkl"),
+ stringBuffer("mno\npqr\n"),
+ stringBuffer("stu"),
+ stringBuffer("vw"),
+ stringBuffer("xyz")
+ );
+
+ Flux output = this.decoder.decode(source, ResolvableType.forClass(String.class),
null, Collections.emptyMap());
StepVerifier.create(output)
- .expectNext("\n", "foo\r", "\n", "bar\r", "\n", "baz")
+ .expectNext("\r\n")
+ .expectNext("abc\n")
+ .expectNext("defghi\r\n")
+ .expectNext("\n")
+ .expectNext("jklmno\n")
+ .expectNext("pqr\n")
+ .expectNext("stuvwxyz")
.expectComplete()
.verify();
-
}
@Test
- public void decodeEmptyFlux() throws InterruptedException {
+ public void decodeEmptyFlux() {
Flux source = Flux.empty();
Flux output = this.decoder.decode(source, ResolvableType.forClass(String.class),
null, Collections.emptyMap());
@@ -98,7 +152,7 @@ public class StringDecoderTests extends AbstractDataBufferAllocatingTestCase {
}
@Test
- public void decodeEmptyString() throws InterruptedException {
+ public void decodeEmptyDataBuffer() {
Flux source = Flux.just(stringBuffer(""));
Flux output = this.decoder.decode(source,
ResolvableType.forClass(String.class), null, Collections.emptyMap());
@@ -110,8 +164,7 @@ public class StringDecoderTests extends AbstractDataBufferAllocatingTestCase {
}
@Test
- public void decodeToMono() throws InterruptedException {
- this.decoder = StringDecoder.allMimeTypes(false);
+ public void decodeToMono() {
Flux source = Flux.just(stringBuffer("foo"), stringBuffer("bar"), stringBuffer("baz"));
Mono output = this.decoder.decodeToMono(source,
ResolvableType.forClass(String.class), null, Collections.emptyMap());
diff --git a/spring-core/src/test/java/org/springframework/core/io/buffer/DataBufferTests.java b/spring-core/src/test/java/org/springframework/core/io/buffer/DataBufferTests.java
index e3b9fbb1dce..cdb8adf97a9 100644
--- a/spring-core/src/test/java/org/springframework/core/io/buffer/DataBufferTests.java
+++ b/spring-core/src/test/java/org/springframework/core/io/buffer/DataBufferTests.java
@@ -492,5 +492,28 @@ public class DataBufferTests extends AbstractDataBufferAllocatingTestCase {
release(composite);
}
+ @Test
+ public void getByte() {
+ DataBuffer buffer = stringBuffer("abc");
+
+ assertEquals('a', buffer.getByte(0));
+ assertEquals('b', buffer.getByte(1));
+ assertEquals('c', buffer.getByte(2));
+ try {
+ buffer.getByte(-1);
+ fail("IndexOutOfBoundsException expected");
+ }
+ catch (IndexOutOfBoundsException ignored) {
+ }
+
+ try {
+ buffer.getByte(3);
+ fail("IndexOutOfBoundsException expected");
+ }
+ catch (IndexOutOfBoundsException ignored) {
+ }
+
+ release(buffer);
+ }
}
\ No newline at end of file
diff --git a/spring-core/src/test/java/org/springframework/core/io/buffer/DataBufferUtilsTests.java b/spring-core/src/test/java/org/springframework/core/io/buffer/DataBufferUtilsTests.java
index de737fc5c43..f76cde68b69 100644
--- a/spring-core/src/test/java/org/springframework/core/io/buffer/DataBufferUtilsTests.java
+++ b/spring-core/src/test/java/org/springframework/core/io/buffer/DataBufferUtilsTests.java
@@ -335,5 +335,4 @@ public class DataBufferUtilsTests extends AbstractDataBufferAllocatingTestCase {
release(result);
}
-
}
diff --git a/spring-web/src/main/java/org/springframework/http/codec/ServerSentEventHttpMessageReader.java b/spring-web/src/main/java/org/springframework/http/codec/ServerSentEventHttpMessageReader.java
index baa26ced9a8..6567a252c78 100644
--- a/spring-web/src/main/java/org/springframework/http/codec/ServerSentEventHttpMessageReader.java
+++ b/spring-web/src/main/java/org/springframework/http/codec/ServerSentEventHttpMessageReader.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2017 the original author or authors.
+ * Copyright 2002-2018 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.
@@ -40,7 +40,7 @@ import org.springframework.http.MediaType;
import org.springframework.http.ReactiveHttpInputMessage;
import org.springframework.lang.Nullable;
-import static java.util.stream.Collectors.*;
+import static java.util.stream.Collectors.joining;
/**
* Reader that supports a stream of {@link ServerSentEvent}s and also plain
@@ -56,7 +56,7 @@ public class ServerSentEventHttpMessageReader implements HttpMessageReader(new ByteBufferDecoder()));
result.add(new DecoderHttpMessageReader<>(new DataBufferDecoder()));
result.add(new DecoderHttpMessageReader<>(new ResourceDecoder()));
- result.add(new DecoderHttpMessageReader<>(StringDecoder.textPlainOnly(splitTextOnNewLine())));
+ result.add(new DecoderHttpMessageReader<>(StringDecoder.textPlainOnly()));
return result;
}
- abstract boolean splitTextOnNewLine();
-
List> getObjectReaders() {
if (!this.registerDefaults) {
return Collections.emptyList();
@@ -216,7 +214,7 @@ abstract class AbstractCodecConfigurer implements CodecConfigurer {
return Collections.emptyList();
}
List> result = new ArrayList<>();
- result.add(new DecoderHttpMessageReader<>(StringDecoder.allMimeTypes(splitTextOnNewLine())));
+ result.add(new DecoderHttpMessageReader<>(StringDecoder.allMimeTypes()));
return result;
}
diff --git a/spring-web/src/main/java/org/springframework/http/codec/support/DefaultClientCodecConfigurer.java b/spring-web/src/main/java/org/springframework/http/codec/support/DefaultClientCodecConfigurer.java
index 15a265bb3b6..e3a0bd0bc87 100644
--- a/spring-web/src/main/java/org/springframework/http/codec/support/DefaultClientCodecConfigurer.java
+++ b/spring-web/src/main/java/org/springframework/http/codec/support/DefaultClientCodecConfigurer.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2017 the original author or authors.
+ * Copyright 2002-2018 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.
@@ -70,11 +70,6 @@ public class DefaultClientCodecConfigurer extends AbstractCodecConfigurer implem
this.sseDecoder = decoder;
}
- @Override
- boolean splitTextOnNewLine() {
- return false;
- }
-
@Override
List> getObjectReaders() {
if (!shouldRegisterDefaults()) {
@@ -110,8 +105,7 @@ public class DefaultClientCodecConfigurer extends AbstractCodecConfigurer implem
}
else {
DefaultCustomCodecs customCodecs = getCustomCodecs();
- partWriters = new ArrayList<>();
- partWriters.addAll(super.getTypedWriters());
+ partWriters = new ArrayList<>(super.getTypedWriters());
if (customCodecs != null) {
partWriters.addAll(customCodecs.getTypedWriters());
}
diff --git a/spring-web/src/main/java/org/springframework/http/codec/support/DefaultServerCodecConfigurer.java b/spring-web/src/main/java/org/springframework/http/codec/support/DefaultServerCodecConfigurer.java
index 6c0420c3983..7e92a181311 100644
--- a/spring-web/src/main/java/org/springframework/http/codec/support/DefaultServerCodecConfigurer.java
+++ b/spring-web/src/main/java/org/springframework/http/codec/support/DefaultServerCodecConfigurer.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2017 the original author or authors.
+ * Copyright 2002-2018 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.
@@ -66,11 +66,6 @@ public class DefaultServerCodecConfigurer extends AbstractCodecConfigurer implem
this.sseEncoder = encoder;
}
- @Override
- boolean splitTextOnNewLine() {
- return true;
- }
-
@Override
List> getTypedReaders() {
if (!shouldRegisterDefaults()) {
diff --git a/spring-web/src/main/java/org/springframework/http/server/reactive/UndertowServerHttpRequest.java b/spring-web/src/main/java/org/springframework/http/server/reactive/UndertowServerHttpRequest.java
index e6fc8150735..045b98e547c 100644
--- a/spring-web/src/main/java/org/springframework/http/server/reactive/UndertowServerHttpRequest.java
+++ b/spring-web/src/main/java/org/springframework/http/server/reactive/UndertowServerHttpRequest.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2017 the original author or authors.
+ * Copyright 2002-2018 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 @@ class UndertowServerHttpRequest extends AbstractServerHttpRequest {
return this.dataBuffer.capacity(newCapacity);
}
+ @Override
+ public byte getByte(int index) {
+ return this.dataBuffer.getByte(index);
+ }
+
@Override
public byte read() {
return this.dataBuffer.read();
diff --git a/spring-web/src/test/java/org/springframework/http/codec/support/ClientCodecConfigurerTests.java b/spring-web/src/test/java/org/springframework/http/codec/support/ClientCodecConfigurerTests.java
index ff59c9b79d7..9657f9cb98f 100644
--- a/spring-web/src/test/java/org/springframework/http/codec/support/ClientCodecConfigurerTests.java
+++ b/spring-web/src/test/java/org/springframework/http/codec/support/ClientCodecConfigurerTests.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2017 the original author or authors.
+ * Copyright 2002-2018 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.
@@ -18,6 +18,7 @@ package org.springframework.http.codec.support;
import java.nio.charset.StandardCharsets;
import java.time.Duration;
+import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;
@@ -55,10 +56,7 @@ import org.springframework.http.codec.xml.Jaxb2XmlDecoder;
import org.springframework.http.codec.xml.Jaxb2XmlEncoder;
import org.springframework.util.MimeTypeUtils;
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertSame;
-import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.*;
import static org.springframework.core.ResolvableType.forClass;
/**
@@ -141,7 +139,7 @@ public class ClientCodecConfigurerTests {
Flux.just(new DefaultDataBufferFactory().wrap("line1\nline2".getBytes(StandardCharsets.UTF_8))),
ResolvableType.forClass(String.class), MimeTypeUtils.TEXT_PLAIN, Collections.emptyMap());
- assertEquals(Collections.singletonList("line1\nline2"), decoded.collectList().block(Duration.ZERO));
+ assertEquals(Arrays.asList("line1", "line2"), decoded.collectList().block(Duration.ZERO));
}
private void assertStringEncoder(Encoder> encoder, boolean textOnly) {
diff --git a/spring-web/src/test/java/org/springframework/http/codec/support/CodecConfigurerTests.java b/spring-web/src/test/java/org/springframework/http/codec/support/CodecConfigurerTests.java
index 7e623c1dccd..22190815a1b 100644
--- a/spring-web/src/test/java/org/springframework/http/codec/support/CodecConfigurerTests.java
+++ b/spring-web/src/test/java/org/springframework/http/codec/support/CodecConfigurerTests.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2017 the original author or authors.
+ * Copyright 2002-2018 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.
@@ -50,7 +50,7 @@ import org.springframework.util.MimeTypeUtils;
import static org.junit.Assert.*;
import static org.mockito.Mockito.*;
-import static org.springframework.core.ResolvableType.*;
+import static org.springframework.core.ResolvableType.forClass;
/**
* Unit tests for {@link AbstractCodecConfigurer.AbstractDefaultCodecs}.
@@ -292,10 +292,6 @@ public class CodecConfigurerTests {
private static class TestDefaultCodecs extends AbstractDefaultCodecs {
- @Override
- boolean splitTextOnNewLine() {
- return true;
- }
}
}
diff --git a/spring-web/src/test/java/org/springframework/http/codec/support/ServerCodecConfigurerTests.java b/spring-web/src/test/java/org/springframework/http/codec/support/ServerCodecConfigurerTests.java
index 15a8639de1b..cee6aec2138 100644
--- a/spring-web/src/test/java/org/springframework/http/codec/support/ServerCodecConfigurerTests.java
+++ b/spring-web/src/test/java/org/springframework/http/codec/support/ServerCodecConfigurerTests.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2017 the original author or authors.
+ * Copyright 2002-2018 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.
@@ -59,7 +59,7 @@ import org.springframework.http.codec.xml.Jaxb2XmlEncoder;
import org.springframework.util.MimeTypeUtils;
import static org.junit.Assert.*;
-import static org.springframework.core.ResolvableType.*;
+import static org.springframework.core.ResolvableType.forClass;
/**
* Unit tests for {@link ServerCodecConfigurer}.
@@ -143,7 +143,7 @@ public class ServerCodecConfigurerTests {
Flux.just(new DefaultDataBufferFactory().wrap("line1\nline2".getBytes(StandardCharsets.UTF_8))),
ResolvableType.forClass(String.class), MimeTypeUtils.TEXT_PLAIN, Collections.emptyMap());
- assertEquals(Arrays.asList("line1\n", "line2"), flux.collectList().block(Duration.ZERO));
+ assertEquals(Arrays.asList("line1", "line2"), flux.collectList().block(Duration.ZERO));
}
private void assertStringEncoder(Encoder> encoder, boolean textOnly) {
diff --git a/spring-webflux/src/test/java/org/springframework/web/reactive/FlushingIntegrationTests.java b/spring-webflux/src/test/java/org/springframework/web/reactive/FlushingIntegrationTests.java
index e85223570de..8d30429729f 100644
--- a/spring-webflux/src/test/java/org/springframework/web/reactive/FlushingIntegrationTests.java
+++ b/spring-webflux/src/test/java/org/springframework/web/reactive/FlushingIntegrationTests.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2017 the original author or authors.
+ * Copyright 2002-2018 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.
@@ -34,7 +34,7 @@ import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.web.reactive.function.client.WebClient;
-import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.*;
/**
* @author Sebastien Deleuze
@@ -121,7 +121,7 @@ public class FlushingIntegrationTests extends AbstractHttpHandlerIntegrationTest
if (path.endsWith("write-and-flush")) {
Flux> responseBody = Flux
.interval(Duration.ofMillis(50))
- .map(l -> toDataBuffer("data" + l, response.bufferFactory()))
+ .map(l -> toDataBuffer("data" + l + "\n", response.bufferFactory()))
.take(2)
.map(Flux::just);
responseBody = responseBody.concatWith(Flux.never());
@@ -131,14 +131,14 @@ public class FlushingIntegrationTests extends AbstractHttpHandlerIntegrationTest
Flux responseBody = Flux
.just("0123456789")
.repeat(20000)
- .map(value -> toDataBuffer(value, response.bufferFactory()));
+ .map(value -> toDataBuffer(value + "\n", response.bufferFactory()));
return response.writeWith(responseBody);
}
else if (path.endsWith("write-and-never-complete")) {
Flux responseBody = Flux
.just("0123456789")
.repeat(20000)
- .map(value -> toDataBuffer(value, response.bufferFactory()))
+ .map(value -> toDataBuffer(value + "\n", response.bufferFactory()))
.mergeWith(Flux.never());
return response.writeWith(responseBody);
}
diff --git a/spring-webflux/src/test/java/org/springframework/web/reactive/result/method/annotation/HttpEntityArgumentResolverTests.java b/spring-webflux/src/test/java/org/springframework/web/reactive/result/method/annotation/HttpEntityArgumentResolverTests.java
index 1d032ef001b..fbd5233bf95 100644
--- a/spring-webflux/src/test/java/org/springframework/web/reactive/result/method/annotation/HttpEntityArgumentResolverTests.java
+++ b/spring-webflux/src/test/java/org/springframework/web/reactive/result/method/annotation/HttpEntityArgumentResolverTests.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2017 the original author or authors.
+ * Copyright 2002-2018 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.
@@ -47,12 +47,7 @@ import org.springframework.web.reactive.BindingContext;
import org.springframework.web.server.ServerWebExchange;
import org.springframework.web.server.ServerWebInputException;
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertNull;
-import static org.junit.Assert.assertTrue;
-import static org.junit.Assert.fail;
+import static org.junit.Assert.*;
import static org.springframework.core.ResolvableType.forClassWithGenerics;
import static org.springframework.http.MediaType.TEXT_PLAIN;
import static org.springframework.mock.http.server.reactive.test.MockServerHttpRequest.post;
@@ -282,9 +277,9 @@ public class HttpEntityArgumentResolverTests {
assertEquals(exchange.getRequest().getHeaders(), httpEntity.getHeaders());
StepVerifier.create(httpEntity.getBody())
- .expectNext("line1\n")
- .expectNext("line2\n")
- .expectNext("line3\n")
+ .expectNext("line1")
+ .expectNext("line2")
+ .expectNext("line3")
.expectComplete()
.verify();
}