Как отправить сообщение об ошибке клиентам STOMP с помощью Spring WebSocket?

Я использую реализацию Spring STOMP через WebSocket с полнофункциональным брокером ActiveMQ. Когда пользователи SUBSCRIBE входят в тему, существует некоторая логика разрешений, которую они должны пройти, прежде чем будут успешно подписаны. Я использую ChannelInterceptor для применения логики разрешений, как указано ниже:

WebSocketConfig.java:

@EnableWebSocketMessageBroker
public class WebSocketConfig extends AbstractWebSocketMessageBrokerConfigurer {

  @Override
  public void registerStompEndpoints(StompEndpointRegistry registry) {
    registry.addEndpoint("/stomp")
      .setAllowedOrigins("*")
      .withSockJS();
  }

  @Override
  public void configureMessageBroker(MessageBrokerRegistry registry) {
    registry.enableStompBrokerRelay("/topic", "/queue")
      .setRelayHost("relayhost.mydomain.com")
      .setRelayPort(61613);
  }

  @Override
  public void configureClientInboundChannel(ChannelRegistration registration) {
    registration.setInterceptors(new MySubscriptionInterceptor());
  }


}

WebSocketSecurityConfig.java:

public class WebSocketSecurityConfig extends AbstractSecurityWebSocketMessageBrokerConfigurer {

  @Override
  protected void configureInbound(MessageSecurityMetadataSourceRegistry messages) {
    messages
      .simpSubscribeDestMatchers("/stomp/**").authenticated()
      .simpSubscribeDestMatchers("/user/queue/errors").authenticated()
      .anyMessage().denyAll();
  }

}

MySubscriptionInterceptor.java:

public class MySubscriptionInterceptor extends ChannelInterceptorAdapter {

  @Override
  public Message<?> preSend(Message<?> message, MessageChannel channel) {

    StompHeaderAccessor headerAccessor= StompHeaderAccessor.wrap(message);
    Principal principal = headerAccessor.getUser();

    if (StompCommand.SUBSCRIBE.equals(headerAccessor.getCommand())) {
      checkPermissions(principal);
    }

    return message;
  }

  private void checkPermissions(Principal principal) {
    // apply permissions logic
    // throw Exception permissions not sufficient
  }
}

Когда клиенты, у которых нет соответствующих разрешений, пытаются подписаться на ограниченную тему, они фактически никогда не получают никаких сообщений из этой темы, НО также не уведомляются об исключении, которое было сгенерировано, которое отклонило их подписку. Вместо этого клиенту возвращается мертвая подписка, о которой брокер ActiveMQ ничего не знает. (Нормальные клиентские взаимодействия с адекватными разрешениями с конечной точкой STOMP и темами работают так, как ожидалось.)

Я пробовал подписаться на users/{subscribingUsername}/queue/errors и просто users/queue/errors с моим тестовым клиентом Java после того, как он был успешно подключен, но до сих пор мне не удалось получить сообщение об ошибке подписки с сервера, доставленного клиенту. Это явно далеко не идеально, поскольку клиентов никогда не уведомляют об отказе в доступе.


person hartz89    schedule 16.11.2015    source источник


Ответы (1)


Вы не можете просто выбросить исключение из MySubscriptionInterceptor на clientInboundChannel, потому что последнее - это ExecutorSubscribableChannel, следовательно, это async, и любые исключения из этих потоков попадают в журналы с любым повторным вызовом вызывающей стороне - StompSubProtocolHandler.handleMessageFromClient.

Но вы можете сделать что-то вроде clientOutboundChannel и использовать его так:

StompHeaderAccessor headerAccessor = StompHeaderAccessor.create(StompCommand.ERROR);
headerAccessor.setMessage(error.getMessage());

clientOutboundChannel.send(MessageBuilder.createMessage(new byte[0], headerAccessor.getMessageHeaders()));

Другой вариант - сопоставление аннотаций:

    @SubscribeMapping("/foo")
    public void handleWithError() {
        throw new IllegalArgumentException("Bad input");
    }

    @MessageExceptionHandler
    @SendToUser("/queue/error")
    public String handleException(IllegalArgumentException ex) {
        return "Got error: " + ex.getMessage();
    }
person Artem Bilan    schedule 16.11.2015
comment
Спасибо за это @Artem, я попробовал. К сожалению, когда исключение создается в методе @SubscribeMapping и обрабатывается методом @MessageExceptionHandler, сообщение IS отправляется в очередь ошибок клиента, НО их подписка по-прежнему пересылается брокеру, и клиент по-прежнему получает последующие сообщения из темы. - person hartz89; 17.11.2015
comment
Итак, попробуйте использовать прямой send на clientOutboundChannel из этого @MessageExceptionHandler. - person Artem Bilan; 17.11.2015
comment
@ArtemBilan, не могли бы вы объяснить, как получить OutboundChannel? Задал вопрос: stackoverflow.com / questions / 39641477 / - person Askar Ibragimov; 22.09.2016