|
|
|
@ -21,13 +21,11 @@ import java.time.Duration; |
|
|
|
|
|
|
|
|
|
import org.junit.Before; |
|
|
|
|
import org.junit.Test; |
|
|
|
|
import org.reactivestreams.Publisher; |
|
|
|
|
import reactor.core.publisher.Flux; |
|
|
|
|
import reactor.core.publisher.Mono; |
|
|
|
|
import reactor.test.StepVerifier; |
|
|
|
|
|
|
|
|
|
import org.springframework.core.io.buffer.DataBuffer; |
|
|
|
|
import org.springframework.core.io.buffer.DataBufferFactory; |
|
|
|
|
import org.springframework.http.server.reactive.AbstractHttpHandlerIntegrationTests; |
|
|
|
|
import org.springframework.http.server.reactive.HttpHandler; |
|
|
|
|
import org.springframework.http.server.reactive.ServerHttpRequest; |
|
|
|
@ -37,7 +35,9 @@ import org.springframework.web.reactive.function.client.WebClient; |
|
|
|
|
import static org.junit.Assert.*; |
|
|
|
|
|
|
|
|
|
/** |
|
|
|
|
* Integration tests for server response flushing behavior. |
|
|
|
|
* @author Sebastien Deleuze |
|
|
|
|
* @author Rossen Stoyanchev |
|
|
|
|
* @since 5.0 |
|
|
|
|
*/ |
|
|
|
|
public class FlushingIntegrationTests extends AbstractHttpHandlerIntegrationTests { |
|
|
|
@ -72,12 +72,11 @@ public class FlushingIntegrationTests extends AbstractHttpHandlerIntegrationTest |
|
|
|
|
Mono<String> result = this.webClient.get() |
|
|
|
|
.uri("/write-and-complete") |
|
|
|
|
.retrieve() |
|
|
|
|
.bodyToFlux(String.class) |
|
|
|
|
.reduce((s1, s2) -> s1 + s2); |
|
|
|
|
.bodyToMono(String.class); |
|
|
|
|
|
|
|
|
|
try { |
|
|
|
|
StepVerifier.create(result) |
|
|
|
|
.consumeNextWith(value -> assertTrue(value.length() >= 20000 * "0123456789".length())) |
|
|
|
|
.consumeNextWith(value -> assertEquals(64 * 1024, value.length())) |
|
|
|
|
.expectComplete() |
|
|
|
|
.verify(Duration.ofSeconds(10L)); |
|
|
|
|
} |
|
|
|
@ -119,35 +118,46 @@ public class FlushingIntegrationTests extends AbstractHttpHandlerIntegrationTest |
|
|
|
|
@Override |
|
|
|
|
public Mono<Void> handle(ServerHttpRequest request, ServerHttpResponse response) { |
|
|
|
|
String path = request.getURI().getPath(); |
|
|
|
|
if (path.endsWith("write-and-flush")) { |
|
|
|
|
Flux<Publisher<DataBuffer>> responseBody = interval(Duration.ofMillis(50), 2) |
|
|
|
|
.map(l -> toDataBuffer("data" + l + "\n", response.bufferFactory())) |
|
|
|
|
.map(Flux::just); |
|
|
|
|
return response.writeAndFlushWith(responseBody.concatWith(Flux.never())); |
|
|
|
|
} |
|
|
|
|
else if (path.endsWith("write-and-complete")) { |
|
|
|
|
Flux<DataBuffer> responseBody = Flux |
|
|
|
|
.just("0123456789") |
|
|
|
|
.repeat(20000) |
|
|
|
|
.map(value -> toDataBuffer(value + "\n", response.bufferFactory())); |
|
|
|
|
return response.writeWith(responseBody); |
|
|
|
|
} |
|
|
|
|
else if (path.endsWith("write-and-never-complete")) { |
|
|
|
|
Flux<DataBuffer> responseBody = Flux |
|
|
|
|
.just("0123456789") |
|
|
|
|
.repeat(20000) |
|
|
|
|
.map(value -> toDataBuffer(value + "\n", response.bufferFactory())); |
|
|
|
|
return response.writeWith(responseBody.mergeWith(Flux.never())); |
|
|
|
|
switch (path) { |
|
|
|
|
case "/write-and-flush": |
|
|
|
|
return response.writeAndFlushWith( |
|
|
|
|
testInterval(Duration.ofMillis(50), 2) |
|
|
|
|
.map(longValue -> wrap("data" + longValue + "\n", response)) |
|
|
|
|
.map(Flux::just) |
|
|
|
|
.mergeWith(Flux.never())); |
|
|
|
|
|
|
|
|
|
case "/write-and-complete": |
|
|
|
|
return response.writeWith( |
|
|
|
|
chunks1K().take(64).map(s -> wrap(s, response))); |
|
|
|
|
|
|
|
|
|
case "/write-and-never-complete": |
|
|
|
|
// Reactor requires at least 50 to flush, Tomcat/Undertow 8, Jetty 1
|
|
|
|
|
return response.writeWith( |
|
|
|
|
chunks1K().take(64).map(s -> wrap(s, response)).mergeWith(Flux.never())); |
|
|
|
|
|
|
|
|
|
default: |
|
|
|
|
return response.writeWith(Flux.empty()); |
|
|
|
|
} |
|
|
|
|
return response.writeWith(Flux.empty()); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
private DataBuffer toDataBuffer(String value, DataBufferFactory factory) { |
|
|
|
|
byte[] data = (value).getBytes(StandardCharsets.UTF_8); |
|
|
|
|
DataBuffer buffer = factory.allocateBuffer(data.length); |
|
|
|
|
buffer.write(data); |
|
|
|
|
return buffer; |
|
|
|
|
private Flux<String> chunks1K() { |
|
|
|
|
return Flux.generate(sink -> { |
|
|
|
|
StringBuilder sb = new StringBuilder(); |
|
|
|
|
do { |
|
|
|
|
for (char c : "0123456789".toCharArray()) { |
|
|
|
|
sb.append(c); |
|
|
|
|
if (sb.length() + 1 == 1024) { |
|
|
|
|
sink.next(sb.append("\n").toString()); |
|
|
|
|
return; |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
} while (true); |
|
|
|
|
}); |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
private DataBuffer wrap(String value, ServerHttpResponse response) { |
|
|
|
|
byte[] bytes = value.getBytes(StandardCharsets.UTF_8); |
|
|
|
|
return response.bufferFactory().wrap(bytes); |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|