Browse Source
Simplify handling by eliminating the use of a message channel. Instead MessageHandlerAcceptor now extends from RSocketMessageHandler and delegates directly to it. See gh-21987pull/22513/head
13 changed files with 223 additions and 467 deletions
@ -1,38 +0,0 @@
@@ -1,38 +0,0 @@
|
||||
/* |
||||
* Copyright 2002-2019 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. |
||||
* You may obtain a copy of the License at |
||||
* |
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software |
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
* See the License for the specific language governing permissions and |
||||
* limitations under the License. |
||||
*/ |
||||
package org.springframework.messaging; |
||||
|
||||
import reactor.core.publisher.Mono; |
||||
|
||||
/** |
||||
* Contract for reactive, non-blocking sending of messages. |
||||
* |
||||
* @author Rossen Stoyanchev |
||||
* @since 5.2 |
||||
*/ |
||||
public interface ReactiveMessageChannel { |
||||
|
||||
/** |
||||
* Send a {@link Message} to this channel. If the message is sent |
||||
* successfully, return {@code true}. Or if not sent due to a non-fatal |
||||
* reason, return {@code false}. |
||||
* @param message the message to send |
||||
* @return completion {@link Mono} returning {@code true} on success, |
||||
* {@code false} if not sent, or an error signal. |
||||
*/ |
||||
Mono<Boolean> send(Message<?> message); |
||||
|
||||
} |
||||
@ -1,42 +0,0 @@
@@ -1,42 +0,0 @@
|
||||
/* |
||||
* Copyright 2002-2013 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. |
||||
* You may obtain a copy of the License at |
||||
* |
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software |
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
* See the License for the specific language governing permissions and |
||||
* limitations under the License. |
||||
*/ |
||||
|
||||
package org.springframework.messaging; |
||||
|
||||
/** |
||||
* {@link MessageChannel} that maintains a registry of subscribers to handle |
||||
* messages sent through this channel. |
||||
* |
||||
* @author Rossen Stoyanchev |
||||
* @since 5.2 |
||||
*/ |
||||
public interface ReactiveSubscribableChannel extends ReactiveMessageChannel { |
||||
|
||||
/** |
||||
* Register a message handler. |
||||
* @return {@code true} if the handler was subscribed or {@code false} if it |
||||
* was already subscribed. |
||||
*/ |
||||
boolean subscribe(ReactiveMessageHandler handler); |
||||
|
||||
/** |
||||
* Un-register a message handler. |
||||
* @return {@code true} if the handler was un-registered, or {@code false} |
||||
* if was not registered. |
||||
*/ |
||||
boolean unsubscribe(ReactiveMessageHandler handler); |
||||
|
||||
} |
||||
@ -0,0 +1,77 @@
@@ -0,0 +1,77 @@
|
||||
/* |
||||
* Copyright 2002-2019 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. |
||||
* You may obtain a copy of the License at |
||||
* |
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software |
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
* See the License for the specific language governing permissions and |
||||
* limitations under the License. |
||||
*/ |
||||
package org.springframework.messaging.rsocket; |
||||
|
||||
import java.util.function.Function; |
||||
|
||||
import io.rsocket.ConnectionSetupPayload; |
||||
import io.rsocket.RSocket; |
||||
import io.rsocket.SocketAcceptor; |
||||
import reactor.core.publisher.Mono; |
||||
|
||||
import org.springframework.lang.Nullable; |
||||
import org.springframework.messaging.Message; |
||||
import org.springframework.util.MimeType; |
||||
|
||||
/** |
||||
* Extension of {@link RSocketMessageHandler} that can be plugged directly into |
||||
* RSocket to receive connections either on the |
||||
* {@link io.rsocket.RSocketFactory.ClientRSocketFactory#acceptor(Function) client} or on the |
||||
* {@link io.rsocket.RSocketFactory.ServerRSocketFactory#acceptor(SocketAcceptor) server} |
||||
* side. Requests are handled by delegating to the "super" {@link #handleMessage(Message)}. |
||||
* |
||||
* @author Rossen Stoyanchev |
||||
* @since 5.2 |
||||
*/ |
||||
public final class MessageHandlerAcceptor extends RSocketMessageHandler |
||||
implements SocketAcceptor, Function<RSocket, RSocket> { |
||||
|
||||
@Nullable |
||||
private MimeType defaultDataMimeType; |
||||
|
||||
|
||||
/** |
||||
* Configure the default content type to use for data payloads. |
||||
* <p>By default this is not set. However a server acceptor will use the |
||||
* content type from the {@link ConnectionSetupPayload}, so this is typically |
||||
* required for clients but can also be used on servers as a fallback. |
||||
* @param defaultDataMimeType the MimeType to use |
||||
*/ |
||||
public void setDefaultDataMimeType(@Nullable MimeType defaultDataMimeType) { |
||||
this.defaultDataMimeType = defaultDataMimeType; |
||||
} |
||||
|
||||
|
||||
@Override |
||||
public Mono<RSocket> accept(ConnectionSetupPayload setupPayload, RSocket sendingRSocket) { |
||||
MessagingRSocket rsocket = createRSocket(sendingRSocket); |
||||
// Allow handling of the ConnectionSetupPayload via @MessageMapping methods.
|
||||
// However, if the handling is to make requests to the client, it's expected
|
||||
// it will do so decoupled from the handling, e.g. via .subscribe().
|
||||
return rsocket.handleConnectionSetupPayload(setupPayload).then(Mono.just(rsocket)); |
||||
} |
||||
|
||||
@Override |
||||
public RSocket apply(RSocket sendingRSocket) { |
||||
return createRSocket(sendingRSocket); |
||||
} |
||||
|
||||
private MessagingRSocket createRSocket(RSocket rsocket) { |
||||
return new MessagingRSocket( |
||||
this::handleMessage, rsocket, this.defaultDataMimeType, getRSocketStrategies()); |
||||
} |
||||
|
||||
} |
||||
@ -1,107 +0,0 @@
@@ -1,107 +0,0 @@
|
||||
/* |
||||
* Copyright 2002-2019 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. |
||||
* You may obtain a copy of the License at |
||||
* |
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software |
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
* See the License for the specific language governing permissions and |
||||
* limitations under the License. |
||||
*/ |
||||
package org.springframework.messaging.rsocket; |
||||
|
||||
import java.util.function.Function; |
||||
import java.util.function.Predicate; |
||||
|
||||
import io.rsocket.ConnectionSetupPayload; |
||||
import io.rsocket.RSocket; |
||||
import io.rsocket.SocketAcceptor; |
||||
import reactor.core.publisher.Mono; |
||||
|
||||
import org.springframework.lang.Nullable; |
||||
import org.springframework.messaging.Message; |
||||
import org.springframework.messaging.ReactiveMessageChannel; |
||||
import org.springframework.util.Assert; |
||||
import org.springframework.util.MimeType; |
||||
|
||||
/** |
||||
* RSocket acceptor for |
||||
* {@link io.rsocket.RSocketFactory.ClientRSocketFactory#acceptor(Function) client} or |
||||
* {@link io.rsocket.RSocketFactory.ServerRSocketFactory#acceptor(SocketAcceptor) server} |
||||
* side use. It wraps requests with a {@link Message} envelope and sends them |
||||
* to a {@link ReactiveMessageChannel} for handling, e.g. via |
||||
* {@code @MessageMapping} method. |
||||
* |
||||
* @author Rossen Stoyanchev |
||||
* @since 5.2 |
||||
*/ |
||||
public final class MessagingAcceptor implements SocketAcceptor, Function<RSocket, RSocket> { |
||||
|
||||
private final ReactiveMessageChannel messageChannel; |
||||
|
||||
private final RSocketStrategies rsocketStrategies; |
||||
|
||||
@Nullable |
||||
private MimeType defaultDataMimeType; |
||||
|
||||
|
||||
/** |
||||
* Constructor with a message channel to send messages to. |
||||
* @param messageChannel the message channel to use |
||||
* <p>This assumes a Spring configuration setup with a |
||||
* {@code ReactiveMessageChannel} and an {@link RSocketMessageHandler} which |
||||
* by default auto-detects {@code @MessageMapping} methods in |
||||
* {@code @Controller} classes, but can also be configured with a |
||||
* {@link RSocketMessageHandler#setHandlerPredicate(Predicate) handlerPredicate} |
||||
* or with handler instances. |
||||
*/ |
||||
public MessagingAcceptor(ReactiveMessageChannel messageChannel) { |
||||
this(messageChannel, RSocketStrategies.builder().build()); |
||||
} |
||||
|
||||
/** |
||||
* Variant of {@link #MessagingAcceptor(ReactiveMessageChannel)} with an |
||||
* {@link RSocketStrategies} for wrapping the sending {@link RSocket} as |
||||
* {@link RSocketRequester}. |
||||
*/ |
||||
public MessagingAcceptor(ReactiveMessageChannel messageChannel, RSocketStrategies rsocketStrategies) { |
||||
Assert.notNull(messageChannel, "ReactiveMessageChannel is required"); |
||||
Assert.notNull(rsocketStrategies, "RSocketStrategies is required"); |
||||
this.messageChannel = messageChannel; |
||||
this.rsocketStrategies = rsocketStrategies; |
||||
} |
||||
|
||||
|
||||
/** |
||||
* Configure the default content type to use for data payloads. |
||||
* <p>By default this is not set. However a server acceptor will use the |
||||
* content type from the {@link ConnectionSetupPayload}. |
||||
* @param defaultDataMimeType the MimeType to use |
||||
*/ |
||||
public void setDefaultDataMimeType(@Nullable MimeType defaultDataMimeType) { |
||||
this.defaultDataMimeType = defaultDataMimeType; |
||||
} |
||||
|
||||
|
||||
@Override |
||||
public Mono<RSocket> accept(ConnectionSetupPayload setupPayload, RSocket sendingRSocket) { |
||||
MessagingRSocket rsocket = createRSocket(sendingRSocket); |
||||
rsocket.handleConnectionSetupPayload(setupPayload).subscribe(); |
||||
return Mono.just(rsocket); |
||||
} |
||||
|
||||
@Override |
||||
public RSocket apply(RSocket sendingRSocket) { |
||||
return createRSocket(sendingRSocket); |
||||
} |
||||
|
||||
private MessagingRSocket createRSocket(RSocket rsocket) { |
||||
return new MessagingRSocket(this.messageChannel, rsocket, this.defaultDataMimeType, this.rsocketStrategies); |
||||
} |
||||
|
||||
} |
||||
@ -1,102 +0,0 @@
@@ -1,102 +0,0 @@
|
||||
/* |
||||
* Copyright 2002-2019 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. |
||||
* You may obtain a copy of the License at |
||||
* |
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software |
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
* See the License for the specific language governing permissions and |
||||
* limitations under the License. |
||||
*/ |
||||
package org.springframework.messaging.support; |
||||
|
||||
import java.util.Set; |
||||
import java.util.concurrent.CopyOnWriteArraySet; |
||||
|
||||
import org.apache.commons.logging.Log; |
||||
import org.apache.commons.logging.LogFactory; |
||||
import reactor.core.publisher.Flux; |
||||
import reactor.core.publisher.Mono; |
||||
|
||||
import org.springframework.beans.factory.BeanNameAware; |
||||
import org.springframework.messaging.Message; |
||||
import org.springframework.messaging.ReactiveMessageHandler; |
||||
import org.springframework.messaging.ReactiveSubscribableChannel; |
||||
import org.springframework.util.ObjectUtils; |
||||
|
||||
/** |
||||
* Default implementation of {@link ReactiveSubscribableChannel}. |
||||
* |
||||
* @author Rossen Stoyanchev |
||||
* @since 5.2 |
||||
*/ |
||||
public class DefaultReactiveMessageChannel implements ReactiveSubscribableChannel, BeanNameAware { |
||||
|
||||
private static final Mono<Boolean> SUCCESS_RESULT = Mono.just(true); |
||||
|
||||
private static Log logger = LogFactory.getLog(DefaultReactiveMessageChannel.class); |
||||
|
||||
|
||||
private final Set<ReactiveMessageHandler> handlers = new CopyOnWriteArraySet<>(); |
||||
|
||||
private String beanName; |
||||
|
||||
|
||||
public DefaultReactiveMessageChannel() { |
||||
this.beanName = getClass().getSimpleName() + "@" + ObjectUtils.getIdentityHexString(this); |
||||
} |
||||
|
||||
|
||||
/** |
||||
* A message channel uses the bean name primarily for logging purposes. |
||||
*/ |
||||
@Override |
||||
public void setBeanName(String name) { |
||||
this.beanName = name; |
||||
} |
||||
|
||||
/** |
||||
* Return the bean name for this message channel. |
||||
*/ |
||||
public String getBeanName() { |
||||
return this.beanName; |
||||
} |
||||
|
||||
|
||||
@Override |
||||
public boolean subscribe(ReactiveMessageHandler handler) { |
||||
boolean result = this.handlers.add(handler); |
||||
if (result) { |
||||
if (logger.isDebugEnabled()) { |
||||
logger.debug(getBeanName() + " added " + handler); |
||||
} |
||||
} |
||||
return result; |
||||
} |
||||
|
||||
|
||||
@Override |
||||
public boolean unsubscribe(ReactiveMessageHandler handler) { |
||||
boolean result = this.handlers.remove(handler); |
||||
if (result) { |
||||
if (logger.isDebugEnabled()) { |
||||
logger.debug(getBeanName() + " removed " + handler); |
||||
} |
||||
} |
||||
return result; |
||||
} |
||||
|
||||
|
||||
@Override |
||||
public Mono<Boolean> send(Message<?> message) { |
||||
return Flux.fromIterable(this.handlers) |
||||
.concatMap(handler -> handler.handleMessage(message)) |
||||
.then(SUCCESS_RESULT); |
||||
} |
||||
|
||||
} |
||||
Loading…
Reference in new issue