[英]How do I use convertAndSendToUser() with an external broker such as RabbitMQ in Spring4?
I am trying to configure web socket support in Spring 4 using RabbitMQ as the external broker, but as soon as I switch to RabbitMQ, I get the following error on start-up in the client:我正在尝试使用 RabbitMQ 作为外部代理在 Spring 4 中配置 Web 套接字支持,但是一旦我切换到 RabbitMQ,我在客户端启动时收到以下错误:
'/user/queue/changes' is not a valid destination.
Valid destination types are: /temp-queue, /exchange, /topic, /queue, /amq/queue, /reply-queue/.
On the server I am using convertAndSendToUser
and this works fine with the simple broker, but as soon as I switch to RabbitMQ, I get this error.在服务器上,我正在使用convertAndSendToUser
,这在简单的代理上运行良好,但是一旦我切换到 RabbitMQ,我就会收到此错误。 Note that RabbitMQ works fine for normal topic broadcasts - it's just the /user
channel that falls over.请注意,RabbitMQ 适用于普通主题广播 - 它只是/user
频道失败。
Do I need to do something special to get /user
to work with RabbitMQ?我是否需要做一些特别的事情才能让/user
与 RabbitMQ 一起工作?
Edit to include Web Socket Config编辑以包含 Web Socket 配置
My WebSocketConfig
is pretty standard with a few customisations to integrate it with spring-session :我的WebSocketConfig
非常标准,有一些定制可以将它与spring-session集成:
@Configuration
@EnableWebSocketMessageBroker
public class WebSocketConfig extends AbstractSessionWebSocketMessageBrokerConfigurer<ExpiringSession> {
@Override
public void configureStompEndpoints(StompEndpointRegistry registry) {
registry.addEndpoint("/changes").withSockJS();
}
@Override
public void configureMessageBroker(MessageBrokerRegistry config) {
//config.enableSimpleBroker("/queue", "/topic");
StompBrokerRelayRegistration r = config.enableStompBrokerRelay("/user", "/topic");
try {
String rabbitUrl = System.getenv("CLOUDAMQP_URL");
if(rabbitUrl != null) {
log.info("RABBIT URL detected: " + rabbitUrl);
URI uri = new URI(rabbitUrl);
String host = uri.getHost();
String login = uri.getUserInfo().split(":",2)[0];
String passCode = uri.getUserInfo().split(":",2)[1];
String vhost = uri.getPath().substring(1);
r.setRelayHost(host);
r.setSystemLogin(login);
r.setSystemPasscode(passCode);
r.setClientLogin(login);
r.setClientPasscode(passCode);
r.setVirtualHost(vhost);
}
} catch(Exception e) {
log.error("Error setting up RabbitMQ", e);
}
config.setApplicationDestinationPrefixes("/app");
}
}
I met the similar error, but I didn't find an intuitive solution after searching Internet.遇到了类似的错误,但是在网上搜索后没有找到直观的解决方法。
I would like to share my findings after going throught the doc of RabbitMQ STOMP .我想在阅读RabbitMQ STOMP的文档后分享我的发现。
According to the doc of RabbitMQ STOMP , only destination starting with /exchange
, /queue
, /amq/queue
, /topic
and temp-queue
are allowed.根据RabbitMQ STOMP的文档,只允许以/exchange
、 /queue
、 /amq/queue
、 /topic
和temp-queue
开头的目的地。 The destination /user/*
would NOT be allowed.目标/user/*
将不被允许。 You can choose the destination per the requirements of your message.您可以根据消息的要求选择目的地。
/exchange -- SEND to arbitrary routing keys and SUBSCRIBE to arbitrary binding patterns; /exchange -- 发送到任意路由键并订阅任意绑定模式;
/queue -- SEND and SUBSCRIBE to queues managed by the STOMP gateway; /queue -- 发送和订阅由 STOMP 网关管理的队列;
/amq/queue -- SEND and SUBSCRIBE to queues created outside the STOMP gateway; /amq/queue -- 发送和订阅在 STOMP 网关外创建的队列;
/topic -- SEND and SUBSCRIBE to transient and durable topics; /topic -- 发送和订阅临时和持久的主题;
/temp-queue/ -- create temporary queues (in reply-to headers only). /temp-queue/ -- 创建临时队列(仅在回复标头中)。
For example, I would like to send a message to a topic to notify all subscribers.例如,我想向某个主题发送消息以通知所有订阅者。
For simple topic destinations which deliver a copy of each message to all active subscribers, destinations of the form /topic/< name > can be used.对于将每条消息的副本传递给所有活动订阅者的简单主题目的地,可以使用/topic/<name>形式的目的地。 Topic destinations support all the routing patterns of AMQP topic exchanges.主题目的地支持 AMQP 主题交换的所有路由模式。
Messages sent to a topic destination that has no active subscribers are simply discarded.发送到没有活动订阅者的主题目的地的消息将被简单地丢弃。
AMQP 0-9-1 Semantics AMQP 0-9-1 语义
For SEND frames, the message is sent to the amq.topic exchange with the routing key < name >.对于 SEND 帧,消息被发送到带有路由键 <name> 的amq.topic交换。
For SUBSCRIBE frames, an autodeleted, non-durable queue is created and bound to the amq.topic exchange with routing key < name >.对于 SUBSCRIBE 帧,会创建一个自动删除的非持久队列,并使用路由键 <name> 绑定到amq.topic交换。 A subscription is created against the queue.针对队列创建订阅。
The spec means that the stomp sub message with destination /topic/<name>
will use the default exchange amp.topic of rabbitmq, then a binding might be created with the variable name if it does not exist and a queue also be created to bind the exchange amp.topic by that binding.该规范意味着目标为/topic/<name>
的 stomp 子消息将使用rabbitmq的默认交换amp.topic ,如果变量名称不存在,则可能会创建一个绑定,并创建一个队列来绑定通过该绑定交换amp.topic 。
If you want to create a durable subscription, the client should send subscribe message with below headers.如果要创建持久订阅,客户端应发送带有以下标头的订阅消息。
durable:true
auto-delete:false
In my app the websocket server receives message /watch/{liveid}
, then reply another message to topic /topic/watchinfo-{liveid}
.在我的应用程序中,websocket 服务器接收消息/watch/{liveid}
,然后将另一条消息回复到主题/topic/watchinfo-{liveid}
。
@Secured(User.ROLE_USER)
@MessageMapping("/watch/{liveid}")
@SendTo("/topic/watchinfo-{liveid}")
@JsonView(View.Live.class)
public LiveWatchInfoMessage liveinfo(@DestinationVariable("liveid") String liveid,
@AuthenticationPrincipal UserDetails activeUser) {
...
return LiveWatchInfoMessage.builder().build();
}
It is quite an old question, but I just met the same error so my solution might works for others.这是一个很老的问题,但我刚刚遇到了同样的错误,所以我的解决方案可能适用于其他人。 It's basicly what @Memo 313 MediaSA said(and why his answer is downvoted).这基本上是@Memo 313 MediaSA 所说的(以及为什么他的回答被否决)。 The two key points I have found are:我发现的两个关键点是:
After doing those, everything should work as what doc for setUserDestinationPrefix methord says:完成这些之后,一切都应该像 setUserDestinationPrefix 方法的文档所说的那样:
For example when a user attempts to subscribe to "/user/queue/position-updates", the destination may be translated to "/queue/position-updatesi9oqdfzo" yielding a unique queue name that does not collide with any other user attempting to do the same.例如,当用户尝试订阅“/user/queue/position-updates”时,目的地可能会被转换为“/queue/position-updatesi9oqdfzo”,从而产生一个唯一的队列名称,该名称不会与任何其他尝试执行的用户发生冲突相同。 Subsequently when messages are sent to "/user/{username}/queue/position-updates", the destination is translated to "/queue/position-updatesi9oqdfzo".随后,当消息发送到“/user/{username}/queue/position-updates”时,目的地被转换为“/queue/position-updatesi9oqdfzo”。
Also, be sure that the {username} your application is using is the same with principal.此外,请确保您的应用程序使用的 {username} 与主体相同。
Remove the "/user"
from config.enableStompBrokerRelay("/user", "/topic");
从config.enableStompBrokerRelay("/user", "/topic");
删除"/user"
config.enableStompBrokerRelay("/user", "/topic");
and add the "/queue"
, spring automatically adds the /user
when you send-to-a-specific user
并添加"/queue"
,当您send-to-a-specific user
时, spring 会自动添加/user
send-to-a-specific user
And just sendTo(username, "/queue/(what_you_want)", msg)
and subscibe to "/queue/(what_you_want)"
.只需sendTo(username, "/queue/(what_you_want)", msg)
并"/queue/(what_you_want)"
。
The bold statement came directly from RabbitMq website I tried to find the link but I got lazy大胆的声明直接来自RabbitMq网站我试图找到链接但我很懒惰
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.