Remove use of MonoProcessor.fromSinks
See gh-25884
This commit is contained in:
@@ -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));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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");
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user