Как отправить сообщение ERROR клиентам 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 после того, как он успешно подключен, но до сих пор мне не удалось получить сообщение об ошибке об исключении подписки с сервера, доставленного на клиент. Это, очевидно, меньше, чем идеальный, поскольку клиенты никогда не уведомляются о том, что им отказали в доступе.

Ответ 1

Вы не можете просто исключить исключение из MySubscriptionInterceptor в clientInboundChannel, потому что последний имеет значение ExecutorSubscribableChannel, поэтому он равен async, и все исключения из этих потоков попадают в журналы с любыми re -для вызывающего абонента - 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();
    }