mirror of
https://github.com/spring-projects/spring-framework.git
synced 2026-09-17 08:24:13 +00:00
Merge branch '7.0.x'
This commit is contained in:
+44
-15
@@ -54,6 +54,8 @@ import org.springframework.util.MimeType;
|
||||
*/
|
||||
public class ProtobufJsonEncoder implements HttpMessageEncoder<Message> {
|
||||
|
||||
private static final byte[] NEWLINE_SEPARATOR = {'\n'};
|
||||
|
||||
private static final byte[] EMPTY_BYTES = new byte[0];
|
||||
|
||||
private static final ResolvableType MESSAGE_TYPE = ResolvableType.forClass(Message.class);
|
||||
@@ -117,22 +119,33 @@ public class ProtobufJsonEncoder implements HttpMessageEncoder<Message> {
|
||||
.map(value -> encodeValue(value, bufferFactory, elementType, mimeType, hints))
|
||||
.flux();
|
||||
}
|
||||
JsonArrayJoinHelper helper = new JsonArrayJoinHelper();
|
||||
|
||||
// Do not prepend JSON array prefix until first signal is known, onNext vs onError
|
||||
// Keeps response not committed for error handling
|
||||
return Flux.from(inputStream)
|
||||
.map(value -> {
|
||||
byte[] prefix = helper.getPrefix();
|
||||
byte[] delimiter = helper.getDelimiter();
|
||||
DataBuffer delimiterBuffer = bufferFactory.wrap(delimiter);
|
||||
DataBuffer dataBuffer = encodeValue(value, bufferFactory, MESSAGE_TYPE, mimeType, hints);
|
||||
return (prefix.length > 0 ?
|
||||
bufferFactory.join(List.of(bufferFactory.wrap(prefix), delimiterBuffer, dataBuffer)) :
|
||||
bufferFactory.join(List.of(delimiterBuffer, dataBuffer)));
|
||||
})
|
||||
.switchIfEmpty(Mono.fromCallable(() -> bufferFactory.wrap(helper.getPrefix())))
|
||||
.concatWith(Mono.fromCallable(() -> bufferFactory.wrap(helper.getSuffix())));
|
||||
byte[] separator = getStreamingMediaTypeSeparator(mimeType);
|
||||
if (separator != null) {
|
||||
return Flux.from(inputStream)
|
||||
.map(value -> {
|
||||
DataBuffer dataBuffer = encodeValue(value, bufferFactory, MESSAGE_TYPE, mimeType, hints);
|
||||
return bufferFactory.join(List.of(dataBuffer, bufferFactory.wrap(separator)));
|
||||
});
|
||||
}
|
||||
else {
|
||||
JsonArrayJoinHelper helper = new JsonArrayJoinHelper();
|
||||
|
||||
// Do not prepend JSON array prefix until first signal is known, onNext vs onError
|
||||
// Keeps response not committed for error handling
|
||||
return Flux.from(inputStream)
|
||||
.map(value -> {
|
||||
byte[] prefix = helper.getPrefix();
|
||||
byte[] delimiter = helper.getDelimiter();
|
||||
DataBuffer delimiterBuffer = bufferFactory.wrap(delimiter);
|
||||
DataBuffer dataBuffer = encodeValue(value, bufferFactory, MESSAGE_TYPE, mimeType, hints);
|
||||
return (prefix.length > 0 ?
|
||||
bufferFactory.join(List.of(bufferFactory.wrap(prefix), delimiterBuffer, dataBuffer)) :
|
||||
bufferFactory.join(List.of(delimiterBuffer, dataBuffer)));
|
||||
})
|
||||
.switchIfEmpty(Mono.fromCallable(() -> bufferFactory.wrap(helper.getPrefix())))
|
||||
.concatWith(Mono.fromCallable(() -> bufferFactory.wrap(helper.getSuffix())));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -153,6 +166,22 @@ public class ProtobufJsonEncoder implements HttpMessageEncoder<Message> {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the separator to use for the given mime type.
|
||||
* <p>By default, this method returns new line {@code "\n"} if the given
|
||||
* mime type is one of the configured {@link #getStreamingMediaTypes
|
||||
* streaming} mime types.
|
||||
* @since 7.0.10
|
||||
*/
|
||||
protected byte @Nullable [] getStreamingMediaTypeSeparator(@Nullable MimeType mimeType) {
|
||||
for (MediaType streamingMediaType : getStreamingMediaTypes()) {
|
||||
if (streamingMediaType.isCompatibleWith(mimeType)) {
|
||||
return NEWLINE_SEPARATOR;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
private static class JsonArrayJoinHelper {
|
||||
|
||||
|
||||
+1
-1
@@ -103,7 +103,7 @@ class DefaultServerHttpRequestBuilder implements ServerHttpRequest.Builder {
|
||||
}
|
||||
|
||||
@Override
|
||||
public ServerHttpRequest.Builder contextPath(String contextPath) {
|
||||
public ServerHttpRequest.Builder contextPath(@Nullable String contextPath) {
|
||||
this.contextPath = contextPath;
|
||||
return this;
|
||||
}
|
||||
|
||||
+2
-1
@@ -146,8 +146,9 @@ public interface ServerHttpRequest extends HttpRequest, ReactiveHttpInputMessage
|
||||
* contextPath} and it must match the start of the path of the URI of
|
||||
* the request. That means changing the contextPath, implies also
|
||||
* changing the path via {@link #path(String)}.
|
||||
* <p>Use {@code null} to clear the context path.
|
||||
*/
|
||||
Builder contextPath(String contextPath);
|
||||
Builder contextPath(@Nullable String contextPath);
|
||||
|
||||
/**
|
||||
* Set or override the specified header values under the given name.
|
||||
|
||||
+12
-2
@@ -92,7 +92,7 @@ class ProtobufJsonEncoderTests extends AbstractEncoderTests<ProtobufJsonEncoder>
|
||||
}
|
||||
|
||||
@Test
|
||||
void encodeStream() {
|
||||
void encodeNonStream() {
|
||||
Flux<Message> input = Flux.just(this.msg1, this.msg2);
|
||||
ResolvableType inputType = forClass(Msg.class);
|
||||
|
||||
@@ -104,7 +104,7 @@ class ProtobufJsonEncoderTests extends AbstractEncoderTests<ProtobufJsonEncoder>
|
||||
}
|
||||
|
||||
@Test
|
||||
void encodeEmptyFlux() {
|
||||
void encodeNonStreamEmpty() {
|
||||
Flux<Message> input = Flux.empty();
|
||||
ResolvableType inputType = forClass(Msg.class);
|
||||
Flux<DataBuffer> result = this.encoder.encode(input, this.bufferFactory, inputType,
|
||||
@@ -115,6 +115,16 @@ class ProtobufJsonEncoderTests extends AbstractEncoderTests<ProtobufJsonEncoder>
|
||||
.verifyComplete();
|
||||
}
|
||||
|
||||
@Test
|
||||
void encodeStream() {
|
||||
Flux<Message> input = Flux.just(this.msg1, this.msg2);
|
||||
ResolvableType inputType = forClass(Msg.class);
|
||||
|
||||
testEncode(input, inputType, MediaType.APPLICATION_NDJSON, null, step -> step
|
||||
.assertNext(buffer -> assertBufferEqualsJson(buffer, "{\"foo\":\"Foo\",\"blah\":{\"blah\":123}}\n"))
|
||||
.assertNext(buffer -> assertBufferEqualsJson(buffer, "{\"foo\":\"Bar\",\"blah\":{\"blah\":456}}\n"))
|
||||
.verifyComplete());
|
||||
}
|
||||
|
||||
private void assertBufferEqualsJson(DataBuffer actual, String expected) {
|
||||
byte[] bytes = DataBufferTestUtils.dumpBytes(actual);
|
||||
|
||||
+10
@@ -194,6 +194,16 @@ class ServerHttpRequestTests {
|
||||
assertThat(mutated.getURI().getRawPath()).isEqualTo("/other/path");
|
||||
}
|
||||
|
||||
@Test // gh-37099
|
||||
void mutateContextPathToNull() throws Exception {
|
||||
ServerHttpRequest request = createRequest("/context/path", "/context");
|
||||
|
||||
ServerHttpRequest mutated = request.mutate().contextPath(null).path("/path").build();
|
||||
assertThat(mutated.getPath().contextPath().value()).isEmpty();
|
||||
assertThat(mutated.getPath().pathWithinApplication().value()).isEqualTo("/path");
|
||||
assertThat(mutated.getURI().getRawPath()).isEqualTo("/path");
|
||||
}
|
||||
|
||||
@Test
|
||||
void mutateContextPathWithoutUpdatingPathShouldFail() throws Exception {
|
||||
ServerHttpRequest request = createRequest("/context/path", "/context");
|
||||
|
||||
Reference in New Issue
Block a user