Remove use of MonoProcessor.fromSinks

See gh-25884
This commit is contained in:
Rossen Stoyanchev
2020-10-09 20:45:27 +01:00
parent cdd48ddd7f
commit e73e489fd8
30 changed files with 300 additions and 227 deletions

View File

@@ -18,11 +18,10 @@ package org.springframework.web.reactive.result.method;
import java.lang.reflect.Method;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import java.util.stream.Collectors;
import reactor.core.publisher.MonoProcessor;
import reactor.core.publisher.Sinks;
import org.springframework.core.DefaultParameterNameDiscoverer;
import org.springframework.core.ParameterNameDiscoverer;
import org.springframework.lang.Nullable;
@@ -102,22 +101,26 @@ public class SyncInvocableHandlerMethod extends HandlerMethod {
public HandlerResult invokeForHandlerResult(ServerWebExchange exchange,
BindingContext bindingContext, Object... providedArgs) {
MonoProcessor<HandlerResult> processor = MonoProcessor.fromSink(Sinks.unsafe().one());
this.delegate.invoke(exchange, bindingContext, providedArgs).subscribeWith(processor);
CompletableFuture<HandlerResult> future =
this.delegate.invoke(exchange, bindingContext, providedArgs).toFuture();
if (processor.isTerminated()) {
Throwable ex = processor.getError();
if (ex != null) {
throw (ex instanceof ServerErrorException ? (ServerErrorException) ex :
new ServerErrorException("Failed to invoke: " + getShortLogMessage(), getMethod(), ex));
}
return processor.peek();
}
else {
// Should never happen...
if (!future.isDone()) {
throw new IllegalStateException(
"SyncInvocableHandlerMethod should have completed synchronously.");
}
Throwable failure;
try {
return future.get();
}
catch (ExecutionException ex) {
failure = ex.getCause();
}
catch (InterruptedException ex) {
failure = ex;
}
throw (new ServerErrorException(
"Failed to invoke: " + getShortLogMessage(), getMethod(), failure));
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2018 the original author or authors.
* Copyright 2002-2020 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.
@@ -56,6 +56,11 @@ public class ErrorsMethodArgumentResolver extends HandlerMethodArgumentResolverS
MethodParameter parameter, BindingContext context, ServerWebExchange exchange) {
Object errors = getErrors(parameter, context);
// Initially Errors/BindingResult is a Mono in the model even if it cannot be declared
// as an async argument. That way it can be resolved first while the Mono can complete
// later at which point the model is also updated for further use.
if (Mono.class.isAssignableFrom(errors.getClass())) {
return ((Mono<?>) errors).cast(Object.class);
}

View File

@@ -23,7 +23,6 @@ import java.util.Map;
import java.util.Optional;
import reactor.core.publisher.Mono;
import reactor.core.publisher.MonoProcessor;
import reactor.core.publisher.Sinks;
import org.springframework.beans.BeanUtils;
@@ -111,20 +110,23 @@ public class ModelAttributeMethodArgumentResolver extends HandlerMethodArgumentR
String name = ModelInitializer.getNameForParameter(parameter);
Mono<?> valueMono = prepareAttributeMono(name, valueType, context, exchange);
// unsafe(): we're intercepting, already serialized Publisher signals
Sinks.One<BindingResult> bindingResultSink = Sinks.unsafe().one();
Map<String, Object> model = context.getModel().asMap();
MonoProcessor<BindingResult> bindingResultMono = MonoProcessor.fromSink(Sinks.one());
model.put(BindingResult.MODEL_KEY_PREFIX + name, bindingResultMono);
model.put(BindingResult.MODEL_KEY_PREFIX + name, bindingResultSink.asMono());
return valueMono.flatMap(value -> {
WebExchangeDataBinder binder = context.createDataBinder(exchange, value, name);
return bindRequestParameters(binder, exchange)
.doOnError(bindingResultMono::onError)
.doOnError(ex -> bindingResultSink.emitError(ex, Sinks.EmitFailureHandler.FAIL_FAST))
.doOnSuccess(aVoid -> {
validateIfApplicable(binder, parameter);
BindingResult errors = binder.getBindingResult();
model.put(BindingResult.MODEL_KEY_PREFIX + name, errors);
BindingResult bindingResult = binder.getBindingResult();
model.put(BindingResult.MODEL_KEY_PREFIX + name, bindingResult);
model.put(name, value);
bindingResultMono.onNext(errors);
// serialized and buffered (should never fail)
bindingResultSink.tryEmitValue(bindingResult);
})
.then(Mono.fromCallable(() -> {
BindingResult errors = binder.getBindingResult();

View File

@@ -25,7 +25,6 @@ import org.reactivestreams.Subscriber;
import org.reactivestreams.Subscription;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import reactor.core.publisher.MonoProcessor;
import reactor.core.publisher.Sinks;
import reactor.util.concurrent.Queues;
@@ -65,7 +64,11 @@ public abstract class AbstractListenerWebSocketSession<T> extends AbstractWebSoc
@Nullable
private final MonoProcessor<Void> handlerCompletion;
private final Sinks.Empty<Void> handlerCompletionSink;
@Nullable
@SuppressWarnings("deprecation")
private final reactor.core.publisher.MonoProcessor<Void> handlerCompletionMono;
private final WebSocketReceivePublisher receivePublisher;
@@ -74,33 +77,53 @@ public abstract class AbstractListenerWebSocketSession<T> extends AbstractWebSoc
private final AtomicBoolean sendCalled = new AtomicBoolean();
private final MonoProcessor<CloseStatus> closeStatusProcessor = MonoProcessor.fromSink(Sinks.one());
private final Sinks.One<CloseStatus> closeStatusSink = Sinks.one();
/**
* Base constructor.
* @param delegate the native WebSocket session, channel, or connection
* @param id the session id
* @param handshakeInfo the handshake info
* @param info the handshake info
* @param bufferFactory the DataBuffer factor for the current connection
*/
public AbstractListenerWebSocketSession(
T delegate, String id, HandshakeInfo handshakeInfo, DataBufferFactory bufferFactory) {
T delegate, String id, HandshakeInfo info, DataBufferFactory bufferFactory) {
this(delegate, id, handshakeInfo, bufferFactory, null);
this(delegate, id, info, bufferFactory, (Sinks.Empty<Void>) null);
}
/**
* Alternative constructor with completion {@code Mono<Void>} to propagate
* session completion (success or error). This is primarily for use with the
* {@code WebSocketClient} to be able to report the end of execution.
* Alternative constructor with completion sink to use to signal when the
* handling of the session is complete, with success or error.
* <p>Primarily for use with {@code WebSocketClient} to be able to
* communicate the end of handling.
*/
public AbstractListenerWebSocketSession(T delegate, String id, HandshakeInfo info,
DataBufferFactory bufferFactory, @Nullable MonoProcessor<Void> handlerCompletion) {
DataBufferFactory bufferFactory, @Nullable Sinks.Empty<Void> handlerCompletionSink) {
super(delegate, id, info, bufferFactory);
this.receivePublisher = new WebSocketReceivePublisher();
this.handlerCompletion = handlerCompletion;
this.handlerCompletionSink = handlerCompletionSink;
this.handlerCompletionMono = null;
}
/**
* Alternative constructor with completion MonoProcessor to use to signal
* when the handling of the session is complete, with success or error.
* <p>Primarily for use with {@code WebSocketClient} to be able to
* communicate the end of handling.
* @deprecated as of 5.3 in favor of
* {@link #AbstractListenerWebSocketSession(Object, String, HandshakeInfo, DataBufferFactory, Sinks.Empty)}
*/
@Deprecated
public AbstractListenerWebSocketSession(T delegate, String id, HandshakeInfo info,
DataBufferFactory bufferFactory, @Nullable reactor.core.publisher.MonoProcessor<Void> handlerCompletion) {
super(delegate, id, info, bufferFactory);
this.receivePublisher = new WebSocketReceivePublisher();
this.handlerCompletionMono = handlerCompletion;
this.handlerCompletionSink = null;
}
@@ -133,7 +156,7 @@ public abstract class AbstractListenerWebSocketSession<T> extends AbstractWebSoc
@Override
public Mono<CloseStatus> closeStatus() {
return this.closeStatusProcessor;
return this.closeStatusSink.asMono();
}
/**
@@ -178,9 +201,10 @@ public abstract class AbstractListenerWebSocketSession<T> extends AbstractWebSoc
this.receivePublisher.handleMessage(message);
}
/** Handle an error callback from the WebSocketHandler adapter. */
/** Handle an error callback from the WebSocket engine. */
void handleError(Throwable ex) {
this.closeStatusProcessor.onComplete();
// Ignore result: can't overflow, ok if not first or no one listens
this.closeStatusSink.tryEmitEmpty();
this.receivePublisher.onError(ex);
WebSocketSendProcessor sendProcessor = this.sendProcessor;
if (sendProcessor != null) {
@@ -189,9 +213,10 @@ public abstract class AbstractListenerWebSocketSession<T> extends AbstractWebSoc
}
}
/** Handle a close callback from the WebSocketHandler adapter. */
/** Handle a close callback from the WebSocket engine. */
void handleClose(CloseStatus closeStatus) {
this.closeStatusProcessor.onNext(closeStatus);
// Ignore result: can't overflow, ok if not first or no one listens
this.closeStatusSink.tryEmitValue(closeStatus);
this.receivePublisher.onAllDataRead();
WebSocketSendProcessor sendProcessor = this.sendProcessor;
if (sendProcessor != null) {
@@ -215,16 +240,24 @@ public abstract class AbstractListenerWebSocketSession<T> extends AbstractWebSoc
@Override
public void onError(Throwable ex) {
if (this.handlerCompletion != null) {
this.handlerCompletion.onError(ex);
if (this.handlerCompletionSink != null) {
// Ignore result: can't overflow, ok if not first or no one listens
this.handlerCompletionSink.tryEmitError(ex);
}
if (this.handlerCompletionMono != null) {
this.handlerCompletionMono.onError(ex);
}
close(CloseStatus.SERVER_ERROR.withReason(ex.getMessage()));
}
@Override
public void onComplete() {
if (this.handlerCompletion != null) {
this.handlerCompletion.onComplete();
if (this.handlerCompletionSink != null) {
// Ignore result: can't overflow, ok if not first or no one listens
this.handlerCompletionSink.tryEmitEmpty();
}
if (this.handlerCompletionMono != null) {
this.handlerCompletionMono.onComplete();
}
close();
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2018 the original author or authors.
* Copyright 2002-2020 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.
@@ -24,7 +24,7 @@ import org.eclipse.jetty.websocket.api.Session;
import org.eclipse.jetty.websocket.api.SuspendToken;
import org.eclipse.jetty.websocket.api.WriteCallback;
import reactor.core.publisher.Mono;
import reactor.core.publisher.MonoProcessor;
import reactor.core.publisher.Sinks;
import org.springframework.core.io.buffer.DataBufferFactory;
import org.springframework.lang.Nullable;
@@ -50,17 +50,24 @@ public class JettyWebSocketSession extends AbstractListenerWebSocketSession<Sess
public JettyWebSocketSession(Session session, HandshakeInfo info, DataBufferFactory factory) {
this(session, info, factory, null);
this(session, info, factory, (Sinks.Empty<Void>) null);
}
public JettyWebSocketSession(Session session, HandshakeInfo info, DataBufferFactory factory,
@Nullable MonoProcessor<Void> completionMono) {
@Nullable Sinks.Empty<Void> completionSink) {
super(session, ObjectUtils.getIdentityHexString(session), info, factory, completionMono);
super(session, ObjectUtils.getIdentityHexString(session), info, factory, completionSink);
// TODO: suspend causes failures if invoked at this stage
// suspendReceiving();
}
@Deprecated
public JettyWebSocketSession(Session session, HandshakeInfo info, DataBufferFactory factory,
@Nullable reactor.core.publisher.MonoProcessor<Void> completionMono) {
super(session, ObjectUtils.getIdentityHexString(session), info, factory, completionMono);
}
@Override
protected boolean canSuspendReceiving() {

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2018 the original author or authors.
* Copyright 2002-2020 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.
@@ -27,7 +27,7 @@ import javax.websocket.SendResult;
import javax.websocket.Session;
import reactor.core.publisher.Mono;
import reactor.core.publisher.MonoProcessor;
import reactor.core.publisher.Sinks;
import org.springframework.core.io.buffer.DataBufferFactory;
import org.springframework.lang.Nullable;
@@ -47,11 +47,18 @@ import org.springframework.web.reactive.socket.WebSocketSession;
public class StandardWebSocketSession extends AbstractListenerWebSocketSession<Session> {
public StandardWebSocketSession(Session session, HandshakeInfo info, DataBufferFactory factory) {
this(session, info, factory, null);
this(session, info, factory, (Sinks.Empty<Void>) null);
}
public StandardWebSocketSession(Session session, HandshakeInfo info, DataBufferFactory factory,
@Nullable MonoProcessor<Void> completionMono) {
@Nullable Sinks.Empty<Void> completionSink) {
super(session, session.getId(), info, factory, completionSink);
}
@Deprecated
public StandardWebSocketSession(Session session, HandshakeInfo info, DataBufferFactory factory,
@Nullable reactor.core.publisher.MonoProcessor<Void> completionMono) {
super(session, session.getId(), info, factory, completionMono);
}

View File

@@ -21,7 +21,6 @@ import java.util.concurrent.atomic.AtomicIntegerFieldUpdater;
import javax.websocket.Session;
import org.apache.tomcat.websocket.WsSession;
import reactor.core.publisher.MonoProcessor;
import org.springframework.core.io.buffer.DataBufferFactory;
import org.springframework.web.reactive.socket.HandshakeInfo;
@@ -47,8 +46,9 @@ public class TomcatWebSocketSession extends StandardWebSocketSession {
super(session, info, factory);
}
@SuppressWarnings("deprecation")
public TomcatWebSocketSession(Session session, HandshakeInfo info, DataBufferFactory factory,
MonoProcessor<Void> completionMono) {
reactor.core.publisher.MonoProcessor<Void> completionMono) {
super(session, info, factory, completionMono);
suspendReceiving();

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2018 the original author or authors.
* Copyright 2002-2020 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,7 @@ import io.undertow.websockets.core.WebSocketCallback;
import io.undertow.websockets.core.WebSocketChannel;
import io.undertow.websockets.core.WebSockets;
import reactor.core.publisher.Mono;
import reactor.core.publisher.MonoProcessor;
import reactor.core.publisher.Sinks;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.core.io.buffer.DataBufferFactory;
@@ -48,11 +48,19 @@ import org.springframework.web.reactive.socket.WebSocketSession;
public class UndertowWebSocketSession extends AbstractListenerWebSocketSession<WebSocketChannel> {
public UndertowWebSocketSession(WebSocketChannel channel, HandshakeInfo info, DataBufferFactory factory) {
this(channel, info, factory, null);
this(channel, info, factory, (Sinks.Empty<Void>) null);
}
public UndertowWebSocketSession(WebSocketChannel channel, HandshakeInfo info,
DataBufferFactory factory, @Nullable MonoProcessor<Void> completionMono) {
DataBufferFactory factory, @Nullable Sinks.Empty<Void> completionSink) {
super(channel, ObjectUtils.getIdentityHexString(channel), info, factory, completionSink);
suspendReceiving();
}
@Deprecated
public UndertowWebSocketSession(WebSocketChannel channel, HandshakeInfo info,
DataBufferFactory factory, @Nullable reactor.core.publisher.MonoProcessor<Void> completionMono) {
super(channel, ObjectUtils.getIdentityHexString(channel), info, factory, completionMono);
suspendReceiving();

View File

@@ -26,7 +26,6 @@ import org.eclipse.jetty.websocket.api.UpgradeResponse;
import org.eclipse.jetty.websocket.client.ClientUpgradeRequest;
import org.eclipse.jetty.websocket.client.io.UpgradeListener;
import reactor.core.publisher.Mono;
import reactor.core.publisher.MonoProcessor;
import reactor.core.publisher.Sinks;
import org.springframework.context.Lifecycle;
@@ -137,26 +136,25 @@ public class JettyWebSocketClient implements WebSocketClient, Lifecycle {
}
private Mono<Void> executeInternal(URI url, HttpHeaders headers, WebSocketHandler handler) {
MonoProcessor<Void> completionMono = MonoProcessor.fromSink(Sinks.one());
Sinks.Empty<Void> completionSink = Sinks.empty();
return Mono.fromCallable(
() -> {
if (logger.isDebugEnabled()) {
logger.debug("Connecting to " + url);
}
Object jettyHandler = createHandler(url, handler, completionMono);
Object jettyHandler = createHandler(url, handler, completionSink);
ClientUpgradeRequest request = new ClientUpgradeRequest();
request.setSubProtocols(handler.getSubProtocols());
UpgradeListener upgradeListener = new DefaultUpgradeListener(headers);
return this.jettyClient.connect(jettyHandler, url, request, upgradeListener);
})
.then(completionMono);
.then(completionSink.asMono());
}
private Object createHandler(URI url, WebSocketHandler handler, MonoProcessor<Void> completion) {
private Object createHandler(URI url, WebSocketHandler handler, Sinks.Empty<Void> completion) {
return new JettyWebSocketHandlerAdapter(handler, session -> {
HandshakeInfo info = createHandshakeInfo(url, session);
return new JettyWebSocketSession(
session, info, DefaultDataBufferFactory.sharedInstance, completion);
return new JettyWebSocketSession(session, info, DefaultDataBufferFactory.sharedInstance, completion);
});
}

View File

@@ -31,7 +31,6 @@ import javax.websocket.WebSocketContainer;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import reactor.core.publisher.Mono;
import reactor.core.publisher.MonoProcessor;
import reactor.core.publisher.Sinks;
import reactor.core.scheduler.Schedulers;
@@ -96,7 +95,7 @@ public class StandardWebSocketClient implements WebSocketClient {
}
private Mono<Void> executeInternal(URI url, HttpHeaders requestHeaders, WebSocketHandler handler) {
MonoProcessor<Void> completionMono = MonoProcessor.fromSink(Sinks.one());
Sinks.Empty<Void> completionSink = Sinks.empty();
return Mono.fromCallable(
() -> {
if (logger.isDebugEnabled()) {
@@ -104,16 +103,16 @@ public class StandardWebSocketClient implements WebSocketClient {
}
List<String> protocols = handler.getSubProtocols();
DefaultConfigurator configurator = new DefaultConfigurator(requestHeaders);
Endpoint endpoint = createEndpoint(url, handler, completionMono, configurator);
Endpoint endpoint = createEndpoint(url, handler, completionSink, configurator);
ClientEndpointConfig config = createEndpointConfig(configurator, protocols);
return this.webSocketContainer.connectToServer(endpoint, config, url);
})
.subscribeOn(Schedulers.boundedElastic()) // connectToServer is blocking
.then(completionMono);
.then(completionSink.asMono());
}
private StandardWebSocketHandlerAdapter createEndpoint(URI url, WebSocketHandler handler,
MonoProcessor<Void> completion, DefaultConfigurator configurator) {
Sinks.Empty<Void> completion, DefaultConfigurator configurator) {
return new StandardWebSocketHandlerAdapter(handler, session ->
createWebSocketSession(session, createHandshakeInfo(url, configurator), completion));
@@ -126,9 +125,18 @@ public class StandardWebSocketClient implements WebSocketClient {
}
protected StandardWebSocketSession createWebSocketSession(Session session, HandshakeInfo info,
MonoProcessor<Void> completion) {
Sinks.Empty<Void> completionSink) {
return new StandardWebSocketSession(session, info, DefaultDataBufferFactory.sharedInstance, completion);
return new StandardWebSocketSession(
session, info, DefaultDataBufferFactory.sharedInstance, completionSink);
}
@Deprecated
protected StandardWebSocketSession createWebSocketSession(Session session, HandshakeInfo info,
reactor.core.publisher.MonoProcessor<Void> completionMono) {
return new StandardWebSocketSession(
session, info, DefaultDataBufferFactory.sharedInstance, completionMono);
}
private ClientEndpointConfig createEndpointConfig(Configurator configurator, List<String> subProtocols) {

View File

@@ -20,7 +20,6 @@ import javax.websocket.Session;
import javax.websocket.WebSocketContainer;
import org.apache.tomcat.websocket.WsWebSocketContainer;
import reactor.core.publisher.MonoProcessor;
import org.springframework.web.reactive.socket.HandshakeInfo;
import org.springframework.web.reactive.socket.adapter.StandardWebSocketSession;
@@ -45,10 +44,11 @@ public class TomcatWebSocketClient extends StandardWebSocketClient {
@Override
@SuppressWarnings("deprecation")
protected StandardWebSocketSession createWebSocketSession(Session session,
HandshakeInfo info, MonoProcessor<Void> completion) {
HandshakeInfo info, reactor.core.publisher.MonoProcessor<Void> completionMono) {
return new TomcatWebSocketSession(session, info, bufferFactory(), completion);
return new TomcatWebSocketSession(session, info, bufferFactory(), completionMono);
}
}

View File

@@ -33,7 +33,6 @@ import org.apache.commons.logging.LogFactory;
import org.xnio.IoFuture;
import org.xnio.XnioWorker;
import reactor.core.publisher.Mono;
import reactor.core.publisher.MonoProcessor;
import reactor.core.publisher.Sinks;
import org.springframework.core.io.buffer.DataBufferFactory;
@@ -155,7 +154,7 @@ public class UndertowWebSocketClient implements WebSocketClient {
}
private Mono<Void> executeInternal(URI url, HttpHeaders headers, WebSocketHandler handler) {
MonoProcessor<Void> completion = MonoProcessor.fromSink(Sinks.one());
Sinks.Empty<Void> completionSink = Sinks.empty();
return Mono.fromCallable(
() -> {
if (logger.isDebugEnabled()) {
@@ -169,15 +168,17 @@ public class UndertowWebSocketClient implements WebSocketClient {
new IoFuture.HandlingNotifier<WebSocketChannel, Object>() {
@Override
public void handleDone(WebSocketChannel channel, Object attachment) {
handleChannel(url, handler, completion, negotiation, channel);
handleChannel(url, handler, completionSink, negotiation, channel);
}
@Override
public void handleFailed(IOException ex, Object attachment) {
completion.onError(new IllegalStateException("Failed to connect to " + url, ex));
// Ignore result: can't overflow, ok if not first or no one listens
completionSink.tryEmitError(
new IllegalStateException("Failed to connect to " + url, ex));
}
}, null);
})
.then(completion);
.then(completionSink.asMono());
}
/**
@@ -194,12 +195,12 @@ public class UndertowWebSocketClient implements WebSocketClient {
return builder;
}
private void handleChannel(URI url, WebSocketHandler handler, MonoProcessor<Void> completion,
private void handleChannel(URI url, WebSocketHandler handler, Sinks.Empty<Void> completionSink,
DefaultNegotiation negotiation, WebSocketChannel channel) {
HandshakeInfo info = createHandshakeInfo(url, negotiation);
DataBufferFactory bufferFactory = DefaultDataBufferFactory.sharedInstance;
UndertowWebSocketSession session = new UndertowWebSocketSession(channel, info, bufferFactory, completion);
UndertowWebSocketSession session = new UndertowWebSocketSession(channel, info, bufferFactory, completionSink);
UndertowWebSocketHandlerAdapter adapter = new UndertowWebSocketHandlerAdapter(session);
channel.getReceiveSetter().set(adapter);

View File

@@ -20,8 +20,6 @@ import java.time.Duration;
import org.junit.jupiter.api.Test;
import reactor.core.publisher.Mono;
import reactor.core.publisher.MonoProcessor;
import reactor.core.publisher.Sinks;
import org.springframework.core.MethodParameter;
import org.springframework.core.ReactiveAdapterRegistry;
@@ -91,9 +89,7 @@ class ErrorsMethodArgumentResolverTests {
@Test
void resolveWithMono() {
BindingResult bindingResult = createBindingResult(new Foo(), "foo");
MonoProcessor<BindingResult> monoProcessor = MonoProcessor.fromSink(Sinks.one());
monoProcessor.onNext(bindingResult);
this.bindingContext.getModel().asMap().put(BindingResult.MODEL_KEY_PREFIX + "foo", monoProcessor);
this.bindingContext.getModel().asMap().put(BindingResult.MODEL_KEY_PREFIX + "foo", Mono.just(bindingResult));
MethodParameter parameter = this.testMethod.arg(Errors.class);
Object actual = this.resolver.resolveArgument(parameter, this.bindingContext, this.exchange)

View File

@@ -26,7 +26,7 @@ import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.MethodSource;
import reactor.core.publisher.Flux;
import reactor.core.publisher.MonoProcessor;
import reactor.core.publisher.Mono;
import reactor.core.publisher.Sinks;
import reactor.test.StepVerifier;
@@ -224,7 +224,9 @@ class SseIntegrationTests extends AbstractHttpHandlerIntegrationTests {
private static final Flux<Long> INTERVAL = testInterval(Duration.ofMillis(100), 50);
private MonoProcessor<Void> cancellation = MonoProcessor.fromSink(Sinks.one());
private final Sinks.Empty<Void> cancelSink = Sinks.empty();
private Mono<Void> cancellation = cancelSink.asMono();
@GetMapping("/string")
@@ -250,7 +252,7 @@ class SseIntegrationTests extends AbstractHttpHandlerIntegrationTests {
Flux<String> infinite() {
return Flux.just(0, 1).map(l -> "foo " + l)
.mergeWith(Flux.never())
.doOnCancel(() -> cancellation.onComplete());
.doOnCancel(() -> cancelSink.emitEmpty(Sinks.EmitFailureHandler.FAIL_FAST));
}
}

View File

@@ -27,8 +27,6 @@ import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import reactor.core.publisher.MonoProcessor;
import reactor.core.publisher.Sinks;
import reactor.util.retry.Retry;
import org.springframework.context.annotation.Bean;
@@ -99,7 +97,7 @@ class WebSocketIntegrationTests extends AbstractWebSocketIntegrationTests {
String protocol = "echo-v1";
AtomicReference<HandshakeInfo> infoRef = new AtomicReference<>();
MonoProcessor<Object> output = MonoProcessor.fromSink(Sinks.unsafe().one());
AtomicReference<Object> protocolRef = new AtomicReference<>();
this.client.execute(getUrl("/sub-protocol"),
new WebSocketHandler() {
@@ -113,7 +111,8 @@ class WebSocketIntegrationTests extends AbstractWebSocketIntegrationTests {
infoRef.set(session.getHandshakeInfo());
return session.receive()
.map(WebSocketMessage::getPayloadAsText)
.subscribeWith(output)
.doOnNext(protocolRef::set)
.doOnError(protocolRef::set)
.then();
}
})
@@ -123,7 +122,7 @@ class WebSocketIntegrationTests extends AbstractWebSocketIntegrationTests {
assertThat(info.getHeaders().getFirst("Upgrade")).isEqualToIgnoringCase("websocket");
assertThat(info.getHeaders().getFirst("Sec-WebSocket-Protocol")).isEqualTo(protocol);
assertThat(info.getSubProtocol()).as("Wrong protocol accepted").isEqualTo(protocol);
assertThat(output.block(TIMEOUT)).as("Wrong protocol detected on the server side").isEqualTo(protocol);
assertThat(protocolRef.get()).as("Wrong protocol detected on the server side").isEqualTo(protocol);
}
@ParameterizedWebSocketTest
@@ -132,27 +131,28 @@ class WebSocketIntegrationTests extends AbstractWebSocketIntegrationTests {
HttpHeaders headers = new HttpHeaders();
headers.add("my-header", "my-value");
MonoProcessor<Object> output = MonoProcessor.fromSink(Sinks.unsafe().one());
AtomicReference<Object> headerRef = new AtomicReference<>();
this.client.execute(getUrl("/custom-header"), headers,
session -> session.receive()
.map(WebSocketMessage::getPayloadAsText)
.subscribeWith(output)
.doOnNext(headerRef::set)
.doOnError(headerRef::set)
.then())
.block(TIMEOUT);
assertThat(output.block(TIMEOUT)).isEqualTo("my-header:my-value");
assertThat(headerRef.get()).isEqualTo("my-header:my-value");
}
@ParameterizedWebSocketTest
void sessionClosing(WebSocketClient client, HttpServer server, Class<?> serverConfigClass) throws Exception {
startServer(client, server, serverConfigClass);
MonoProcessor<CloseStatus> statusProcessor = MonoProcessor.fromSink(Sinks.unsafe().one());
AtomicReference<Object> statusRef = new AtomicReference<>();
this.client.execute(getUrl("/close"),
session -> {
logger.debug("Starting..");
session.closeStatus().subscribe(statusProcessor);
session.closeStatus().subscribe(statusRef::set, statusRef::set, () -> {});
return session.receive()
.doOnNext(s -> logger.debug("inbound " + s))
.then()
@@ -162,25 +162,26 @@ class WebSocketIntegrationTests extends AbstractWebSocketIntegrationTests {
})
.block(TIMEOUT);
assertThat(statusProcessor.block()).isEqualTo(CloseStatus.GOING_AWAY);
assertThat(statusRef.get()).isEqualTo(CloseStatus.GOING_AWAY);
}
@ParameterizedWebSocketTest
void cookie(WebSocketClient client, HttpServer server, Class<?> serverConfigClass) throws Exception {
startServer(client, server, serverConfigClass);
MonoProcessor<Object> output = MonoProcessor.fromSink(Sinks.unsafe().one());
AtomicReference<String> cookie = new AtomicReference<>();
AtomicReference<Object> receivedCookieRef = new AtomicReference<>();
this.client.execute(getUrl("/cookie"),
session -> {
cookie.set(session.getHandshakeInfo().getHeaders().getFirst("Set-Cookie"));
return session.receive()
.map(WebSocketMessage::getPayloadAsText)
.subscribeWith(output)
.doOnNext(receivedCookieRef::set)
.doOnError(receivedCookieRef::set)
.then();
})
.block(TIMEOUT);
assertThat(output.block(TIMEOUT)).isEqualTo("cookie");
assertThat(receivedCookieRef.get()).isEqualTo("cookie");
assertThat(cookie.get()).isEqualTo("project=spring");
}