Note: see update at the bottom of the question for what I eventually concluded.
注意:请参阅问题底部的更新,了解我最终得出的结论。
I need to send multiple responses to a request over the web socket that sent the request message, the first one quickly, and the others after the data is verified (somewhere between 10 and 60 seconds later, from multiple parallel threads).
我需要通过发送请求消息的Web套接字向请求发送多个响应,第一个快速响应,其他响应在数据验证之后(10到60秒之后,从多个并行线程)。
I am having trouble getting the later responses to stop broadcasting over all open web sockets. How do I get them to only send to the initial web socket? Or should I use something besides Spring STOMP (because, to be honest, all I want is the message routing to various functions, I don't need or want the ability to broadcast to other web sockets, so I suspect I could write the message distributor myself, even though it is reinventing the wheel).
我无法让后来的响应停止在所有打开的网络套接字上播放。如何让它们只发送到初始Web套接字?或者我应该使用Spring STOMP之外的东西(因为,老实说,我想要的是路由到各种功能的消息,我不需要或者想要能够广播到其他网络套接字,所以我怀疑我可以写信息经销商自己,即使它正在重新发明*)。
I am not using Spring Authentication (this is being retrofitted into legacy code).
我没有使用Spring身份验证(这是在改进遗留代码中)。
On the initial return message, I can use @SendToUser, and even though we don't have a user, Spring only sends the return value to the websocket that sent the message. (see this question).
在初始返回消息中,我可以使用@SendToUser,即使我们没有用户,Spring也只将返回值发送到发送消息的websocket。 (见这个问题)。
With the slower responses, though, I think I need to use SimpMessagingTemplate.convertAndSendToUser(user, destination, message), but I can't, because I have to pass in the user, and I can't figure out what user the @SendToUser used. I tried to follow the steps in this question, but didn't get it to work when not authenticated (principal.getName() returns null in this case).
但是,响应速度较慢,我认为我需要使用SimpMessagingTemplate.convertAndSendToUser(用户,目的地,消息),但我不能,因为我必须传入用户,而我无法弄清楚用户是什么用户使用SendToUser。我试图按照这个问题中的步骤进行操作,但是在未经过身份验证时没有使它工作(在这种情况下,principal.getName()返回null)。
I've simplified this considerably for the prototype, so don't worry about synchronizing threads or anything. I just want the web sockets to work correctly.
我已经为原型大大简化了这一点,所以不要担心同步线程或任何东西。我只是希望网络套接字正常工作。
Here is my controller:
这是我的控制器:
@Controller
public class TestSocketController
{
private SimpMessagingTemplate template;
@Autowired
public TestSocketController(SimpMessagingTemplate template)
{
this.template = template;
}
// This doesn't work because I need to pass something for the first parameter.
// If I just use convertAndSend, it broacasts the response to all browsers
void setResults(String ret)
{
template.convertAndSendToUser("", "/user/topic/testwsresponse", ret);
}
// this only sends "Testing Return" to the browser tab hooked to this websocket
@MessageMapping(value="/testws")
@SendToUser("/topic/testwsresponse")
public String handleTestWS(String msg) throws InterruptedException
{
(new Thread(new Later(this))).start();
return "Testing Return";
}
public class Later implements Runnable
{
TestSocketController Controller;
public Later(TestSocketController controller)
{
Controller = controller;
}
public void run()
{
try
{
java.lang.Thread.sleep(2000);
Controller.setResults("Testing Later Return");
}
catch (Exception e)
{
}
}
}
}
For the record, here is the browser side:
对于记录,这是浏览器端:
var client = null;
function sendMessage()
{
client.send('/app/testws', {}, 'Test');
}
// hooked to a button
function test()
{
if (client != null)
{
sendMessage();
return;
}
var socket = new SockJS('/application-name/sendws/');
client = Stomp.over(socket);
client.connect({}, function(frame)
{
client.subscribe('/user/topic/testwsresponse', function(message)
{
alert(message);
});
sendMessage();
});
});
And here is the config:
这是配置:
@Configuration
@EnableWebSocketMessageBroker
public class TestSocketConfig extends AbstractWebSocketMessageBrokerConfigurer
{
@Override
public void configureMessageBroker(MessageBrokerRegistry config)
{
config.setApplicationDestinationPrefixes("/app");
config.enableSimpleBroker("/queue", "/topic");
config.setUserDestinationPrefix("/user");
}
@Override
public void registerStompEndpoints(StompEndpointRegistry registry)
{
registry.addEndpoint("/sendws").withSockJS();
}
}
UPDATE: Due to the security issues involved with the possibility of information being sent over other websockets than the originating socket, I ended up recommending to my group that we do not use the Spring 4.0 implementation of STOMP over Web Sockets. I understand why the Spring team did it the way they did it, and it is more power then we needed, but the security restrictions on our project were severe enough, and the actual requirements were simple enough, that we decided to go a different way. That doesn't invalidate the answers below, so make your own decision based on your projects needs. At least we have hopefully all learned the limitations of the technology, for good or bad.
更新:由于安全问题涉及通过其他websockets发送信息的可能性而不是原始套接字,我最后向我的小组推荐我们不使用STOMP的Spring 4.0实现而不是Web套接字。我理解为什么Spring团队以他们这样做的方式做到了,而且它比我们需要的更强大,但我们项目的安全限制足够严重,实际要求很简单,我们决定采用不同的方式。这并不会使下面的答案无效,因此请根据您的项目需求做出自己的决定。至少我们希望所有人都了解技术的局限性,无论好坏。
2 个解决方案
#1
Why don't you use a separate topic for each client?
为什么不为每个客户使用单独的主题?
-
Client generates a session id.
客户端生成会话ID。
var sessionId = Math.random().toString(36).substring(7);
var sessionId = Math.random()。toString(36).substring(7);
-
Client subscribes to /topic/testwsresponse/{sessionId}, then sends a message to '/app/testws/{sessionId}'.
客户订阅/ topic / testwsresponse / {sessionId},然后向'/ app / testws / {sessionId}'发送消息。
- In your controller you use @MessageMapping(value="/testws/{sessionId}") and remove @SendToUser. You can use @DestinationVariable to access sessionId in your method.
- The controller sends further responses to /topic/testwsresponse/{sessionId}.
在您的控制器中,您使用@MessageMapping(value =“/ testws / {sessionId}”)并删除@SendToUser。您可以使用@DestinationVariable访问方法中的sessionId。
控制器向/ topic / testwsresponse / {sessionId}发送进一步的响应。
Essentially Spring does a similar thing internally when you use user destinations. Since you don't use Spring Authentication you cannot rely on this mechanism but you can easily implement your own as I described above.
当您使用用户目标时,本质上Spring在内部执行类似的操作。由于您不使用Spring身份验证,因此您无法依赖此机制,但您可以轻松实现自己的身份,如上所述。
var client = null;
var sessionId = Math.random().toString(36).substring(7);
function sendMessage()
{
client.send('/app/testws/' + sessionId, {}, 'Test');
}
// hooked to a button
function test()
{
if (client != null)
{
sendMessage();
return;
}
var socket = new SockJS('/application-name/sendws/');
client = Stomp.over(socket);
client.connect({}, function(frame)
{
client.subscribe('/topic/testwsresponse/' + sessionId, function(message)
{
alert(message);
});
// Need to wait until subscription is complete
setTimeout(sendMessage, 1000);
});
});
Controller:
@Controller
public class TestSocketController
{
private SimpMessagingTemplate template;
@Autowired
public TestSocketController(SimpMessagingTemplate template)
{
this.template = template;
}
void setResults(String ret, String sessionId)
{
template.convertAndSend("/topic/testwsresponse/" + sessionId, ret);
}
@MessageMapping(value="/testws/{sessionId}")
public void handleTestWS(@DestinationVariable String sessionId, @Payload String msg) throws InterruptedException
{
(new Thread(new Later(this, sessionId))).start();
setResults("Testing Return", sessionId);
}
public class Later implements Runnable
{
TestSocketController Controller;
String sessionId;
public Later(TestSocketController controller, String sessionId)
{
Controller = controller;
this.sessionId = sessionId;
}
public void run()
{
try
{
java.lang.Thread.sleep(2000);
Controller.setResults("Testing Later Return", sessionId);
}
catch (Exception e)
{
}
}
}
}
Just tested it, works as expected.
刚刚测试过,按预期工作。
#2
This is not full answer. Just general consideration and suggestion. You cannot do different stuff or type of connection via the same socket. Why not have different sockets for different work? Some with authentication and some without. Some for quick task and some for long execution.
这不是完整的答案。只是一般的考虑和建议。您不能通过同一插槽执行不同的连接或类型的连接。为什么不为不同的工作设置不同的插座?一些有认证,有些没有。一些用于快速任务,一些用于长时间执行。
#1
Why don't you use a separate topic for each client?
为什么不为每个客户使用单独的主题?
-
Client generates a session id.
客户端生成会话ID。
var sessionId = Math.random().toString(36).substring(7);
var sessionId = Math.random()。toString(36).substring(7);
-
Client subscribes to /topic/testwsresponse/{sessionId}, then sends a message to '/app/testws/{sessionId}'.
客户订阅/ topic / testwsresponse / {sessionId},然后向'/ app / testws / {sessionId}'发送消息。
- In your controller you use @MessageMapping(value="/testws/{sessionId}") and remove @SendToUser. You can use @DestinationVariable to access sessionId in your method.
- The controller sends further responses to /topic/testwsresponse/{sessionId}.
在您的控制器中,您使用@MessageMapping(value =“/ testws / {sessionId}”)并删除@SendToUser。您可以使用@DestinationVariable访问方法中的sessionId。
控制器向/ topic / testwsresponse / {sessionId}发送进一步的响应。
Essentially Spring does a similar thing internally when you use user destinations. Since you don't use Spring Authentication you cannot rely on this mechanism but you can easily implement your own as I described above.
当您使用用户目标时,本质上Spring在内部执行类似的操作。由于您不使用Spring身份验证,因此您无法依赖此机制,但您可以轻松实现自己的身份,如上所述。
var client = null;
var sessionId = Math.random().toString(36).substring(7);
function sendMessage()
{
client.send('/app/testws/' + sessionId, {}, 'Test');
}
// hooked to a button
function test()
{
if (client != null)
{
sendMessage();
return;
}
var socket = new SockJS('/application-name/sendws/');
client = Stomp.over(socket);
client.connect({}, function(frame)
{
client.subscribe('/topic/testwsresponse/' + sessionId, function(message)
{
alert(message);
});
// Need to wait until subscription is complete
setTimeout(sendMessage, 1000);
});
});
Controller:
@Controller
public class TestSocketController
{
private SimpMessagingTemplate template;
@Autowired
public TestSocketController(SimpMessagingTemplate template)
{
this.template = template;
}
void setResults(String ret, String sessionId)
{
template.convertAndSend("/topic/testwsresponse/" + sessionId, ret);
}
@MessageMapping(value="/testws/{sessionId}")
public void handleTestWS(@DestinationVariable String sessionId, @Payload String msg) throws InterruptedException
{
(new Thread(new Later(this, sessionId))).start();
setResults("Testing Return", sessionId);
}
public class Later implements Runnable
{
TestSocketController Controller;
String sessionId;
public Later(TestSocketController controller, String sessionId)
{
Controller = controller;
this.sessionId = sessionId;
}
public void run()
{
try
{
java.lang.Thread.sleep(2000);
Controller.setResults("Testing Later Return", sessionId);
}
catch (Exception e)
{
}
}
}
}
Just tested it, works as expected.
刚刚测试过,按预期工作。
#2
This is not full answer. Just general consideration and suggestion. You cannot do different stuff or type of connection via the same socket. Why not have different sockets for different work? Some with authentication and some without. Some for quick task and some for long execution.
这不是完整的答案。只是一般的考虑和建议。您不能通过同一插槽执行不同的连接或类型的连接。为什么不为不同的工作设置不同的插座?一些有认证,有些没有。一些用于快速任务,一些用于长时间执行。