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 @@ |
|||||||
/* |
|
||||||
* 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 @@ |
|||||||
/* |
|
||||||
* 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 @@ |
|||||||
|
/* |
||||||
|
* 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 @@ |
|||||||
/* |
|
||||||
* 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 @@ |
|||||||
/* |
|
||||||
* 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