Spring WebSocket @SendToSession: отправить сообщение на определенный сеанс

можно ли отправить сообщение на конкретный сеанс?

у меня есть неавторизованный websocket между клиентами и сервлетом Spring. Мне нужно отправить незапрошенное сообщение определенному соединению, когда асинхронное задание заканчивается.

@Controller
public class WebsocketTest {


     @Autowired
    public SimpMessageSendingOperations messagingTemplate;

    ExecutorService executor = Executors.newSingleThreadExecutor();

    @MessageMapping("/start")
    public void start(SimpMessageHeaderAccessor accessor) throws Exception {
        String applicantId=accessor.getSessionId();        
        executor.submit(() -> {
            //... slow job
            jobEnd(applicantId);
        });
    }

    public void jobEnd(String sessionId){
        messagingTemplate.convertAndSend("/queue/jobend"); //how to send only to that session?
    }
}

как вы можете видеть в этом коде, клиент может запустить асинхронное задание, и когда оно завершится, ему понадобится конечное сообщение. Очевидно, что мне нужно сообщение только заявителю и не транслировать всем. Было бы здорово иметь @SendToSession аннотация или messagingTemplate.convertAndSendToSession метод.

обновление

Я попытался это:

messagingTemplate.convertAndSend("/queue/jobend", true, Collections.singletonMap(SimpMessageHeaderAccessor.SESSION_ID_HEADER, sessionId));

но это вещает на все сеансы, а не только на один указанный.

обновление 2

тест с помощью метода convertAndSendToUser (). Этот тест и взломать официальный весенний учебник:https://spring.io/guides/gs/messaging-stomp-websocket/

это сервер код:

@Controller
public class WebsocketTest {

    @PostConstruct
    public void init(){
        ScheduledExecutorService statusTimerExecutor=Executors.newSingleThreadScheduledExecutor();
        statusTimerExecutor.scheduleAtFixedRate(new Runnable() {                
            @Override
            public void run() {
                messagingTemplate.convertAndSendToUser("1","/queue/test", new Return("test"));
            }
        }, 5000,5000, TimeUnit.MILLISECONDS);
    } 

     @Autowired
        public SimpMessageSendingOperations messagingTemplate;
}

и это код клиента:

function connect() {
            var socket = new WebSocket('ws://localhost:8080/hello');
            stompClient = Stomp.over(socket);
            stompClient.connect({}, function(frame) {
                setConnected(true);
                console.log('Connected: ' + frame);
                stompClient.subscribe('/user/queue/test', function(greeting){
                    console.log(JSON.parse(greeting.body));
                });
            });
        }

к сожалению, клиент не получает свой ответ за сеанс каждые 5000ms, как ожидалось. Я уверен, что "1" является допустимым sessionId для 2-го клиента, подключенного, потому что я вижу его в режиме отладки с SimpMessageHeaderAccessor.getSessionId()

СЦЕНАРИЙ

Я хочу создать индикатор выполнения для удаленного задания, клиент запрашивает сервер для асинхронного задания, и он проверяет его ход по websocket сообщение отправлено с сервера. Это не загрузка файла, а удаленное вычисление, поэтому только сервер знает ход выполнения каждого задания. Мне нужно отправить сообщение на определенный сеанс, потому что каждое задание запускается сеансом. Клиент запрашивает удаленное вычисление Сервер запускает это задание и для каждого шага задания отвечает клиенту-кандидату со статусом выполнения задания. Клиент получает сообщения о своей работе и создать прогресс бар состояния. Вот почему мне нужны сообщения за сеанс. Я также могу использовать каждого пользователя сообщения, но весна не дает в сообщения пользователей. (не удается отправить сообщение пользователя с Spring Websocket)

РАБОЧЕЕ РЕШЕНИЕ

 __      __ ___   ___  _  __ ___  _  _   ___      ___   ___   _    _   _  _____  ___  ___   _  _ 
      / // _  | _ | |/ /|_ _|| | | / __|    / __| / _  | |  | | | ||_   _||_ _|/ _  | | |
   // /| (_) ||   /| ' <  | | | .` || (_ |    __ | (_) || |__| |_| |  | |   | || (_) || .` |
   _/_/  ___/ |_|_|_|_|___||_|_| ___|    |___/ ___/ |____|___/   |_|  |___|___/ |_|_|

начиная с решения UPDATE2, мне пришлось завершить метод convertAndSendToUser с последним параметром (MessageHeaders):

messagingTemplate.convertAndSendToUser("1","/queue/test", new Return("test"), createHeaders("1"));

здесь createHeaders() этот метод:

private MessageHeaders createHeaders(String sessionId) {
        SimpMessageHeaderAccessor headerAccessor = SimpMessageHeaderAccessor.create(SimpMessageType.MESSAGE);
        headerAccessor.setSessionId(sessionId);
        headerAccessor.setLeaveMutable(true);
        return headerAccessor.getMessageHeaders();
    }

3 ответов


нет необходимости создавать конкретные пункты назначения, это уже сделано из коробки с весны 4.1 (см. SPR-11309).

данные пользователи подписываются на /user/queue/something очереди, вы можете отправить сообщение на один сеанс:

As указано в SimpMessageSendingOperations Javadoc, так как ваше имя пользователя на самом деле является sessionId, вы должны установить это как заголовок, а иначе DefaultUserDestinationResolver не сможет направить сообщение и упадет он.

SimpMessageHeaderAccessor headerAccessor = SimpMessageHeaderAccessor
    .create(SimpMessageType.MESSAGE);
headerAccessor.setSessionId(sessionId);
headerAccessor.setLeaveMutable(true);

messagingTemplate.convertAndSendToUser(sessionId,"/queue/something", payload, 
    headerAccessor.getMessageHeaders());

для этого не требуется аутентификация пользователей.


- Это очень сложный и на мой взгляд, не стоит. Вам нужно создать подписку для каждого пользователя (даже не прошедшего проверку подлинности) по идентификатору сеанса.

предположим, что каждый пользователь подписывается на уникальную очередь только для него:

stompClient.subscribe('/session/specific' + uuid, handler);

на сервере, прежде чем пользователь подпишется вам нужно будет уведомить и отправить сообщение для конкретного сеанса и сохранить на карте:

    @MessageMapping("/putAnonymousSession/{sessionId}")
    public void start(@DestinationVariable sessionId) throws Exception {
        anonymousUserSession.put(key, sessionId);
    }

после этого, когда вы хотите отправить сообщение пользователю, вы надо:

messagingTemplate.convertAndSend("/session/specific" + key); 

но я действительно не знаю, что вы пытаетесь сделать и как вы найдете конкретные сессии (кто является анонимным).


вам нужно просто добавить идентификатор сессии в

  • Сервер

    convertAndSendToUser (sessionId,apiName,responseObject);

  • Стороне Клиента

    $ stomp.подписаться ('/user/+sessionId+' / apiName', обработчик);

Примечание:
Не забудьте добавить '/user' в конечной точке на стороне сервера.