Несколько раз ответив на веб-сокет без аутентификации spring

Примечание: см. обновление в нижней части вопроса для того, что я в итоге сделал.

Мне нужно отправить несколько ответов на запрос через веб-сокет, который отправил сообщение с запросом, первое быстро, а остальные после проверки данных (где-то между 10 и 60 секундами позже, из нескольких параллельных потоков).

У меня возникли проблемы с получением последующих ответов, чтобы остановить трансляцию по всем открытым веб-сокетам. Как заставить их отправлять только исходный веб-сокет? Или я должен использовать что-то помимо Spring STOMP (потому что, честно говоря, все, что я хочу, это маршрутизация сообщений к различным функциям, мне не нужна или не требуется возможность трансляции в другие веб-сокеты, поэтому я подозреваю, что могу написать сам распространитель сообщений, хотя он изобретает колесо).

Я не использую Spring Аутентификация (это переоснащается в устаревший код).

В исходном обратном сообщении я могу использовать @SendToUser, и даже если у нас нет пользователя, Spring отправляет только возвращаемое значение в веб-ячейку, отправившую сообщение. (см. этот вопрос).

С более медленными ответами, я думаю, мне нужно использовать SimpMessagingTemplate.convertAndSendToUser(пользователь, адрес, сообщение), но я не могу, потому что мне нужно передать его пользователю, и я не могу понять, что используется пользователь @SendToUser. Я попытался выполнить шаги в этом вопросе, но не смог заставить его работать, когда не аутентифицирован (в этом случае имя_имя() возвращает null).

Я значительно упростил это для прототипа, поэтому не беспокойтесь о синхронизации потоков или чего-то еще. Я просто хочу, чтобы веб-сокеты работали правильно.

Вот мой контроллер:

@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)
        {
        }
    }
  }
}

Для записи здесь есть сторона браузера:

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();
    });
});

И вот конфигурация:

@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();
    }
}

ОБНОВЛЕНИЕ. Из-за проблем безопасности, связанных с возможностью отправки информации по другим веб-сайтам, чем исходный сокет, я решил рекомендовать моей группе, что мы не используем реализацию STOMP Spring 4.0 через веб-сокеты. Я понимаю, почему команда Spring сделала это так, как они это делали, и это больше, чем нам нужно, но ограничения безопасности в нашем проекте были достаточно серьезными, и фактические требования были достаточно простыми, и мы решили пойти другой путь. Это не приводит к недействительности ответов ниже, поэтому сделайте свое собственное решение в соответствии с вашими проектами. По крайней мере, мы надеемся, что все узнали об ограничениях технологии, хорошо или плохо.

Ответ 1

Почему бы вам не использовать отдельный раздел для каждого клиента?

  • Клиент генерирует идентификатор сеанса.

    var sessionId = Math.random().toString(36).substring(7);

  • Клиент подписывается на /topic/testwsresponse/ {sessionId}, а затем отправляет сообщение в '/app/testws/{sessionId}'.

  • В вашем контроллере вы используете @MessageMapping (value = "/testws/{sessionId}" ) и удалите @SendToUser. Вы можете использовать @DestinationVariable для доступа к sessionId в вашем методе.
  • Контроллер отправляет дальнейшие ответы на /topic/testwsresponse/ {sessionId}.

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

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
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)
            {
            }
        }
    }
}

Просто протестируйте его, работайте, как ожидалось.

Ответ 2

Это не полный ответ. Просто общее соображение и предложение. Вы не можете делать разные вещи или тип соединения через один и тот же сокет. Почему бы не иметь разные сокеты для другой работы? Некоторые с аутентификацией, а некоторые без нее. Некоторые для быстрой задачи, а некоторые для длительного исполнения.