Spring Boot реализует WebSocket протокола STOMP

Spring Boot

WebSocketПротоколы — это один из способов, с помощью которых приложение обрабатывает сообщения в реальном времени. Наиболее распространенными альтернативами являются длительные опросы и события, отправленные сервером. У каждого из этих решений есть свои плюсы и минусы. В этой статье я покажу вам, как использоватьSpring BootвыполнитьWebSocket. Я расскажу о настройке на стороне сервера и на стороне клиента, используяWebSocketпо соглашениюSTOMPобщаться друг с другом.

Серверная часть будет полностью написана на Java. Однако, что касается клиента, я покажуJavaиJavaScript(SockJS)письменные фрагменты, потому что обычно,WebSocketКлиент встроен в интерфейсное приложение. Примеры кода продемонстрируют, как использоватьpub-subМоделирует, как рассылать сообщения нескольким пользователям и как отправлять сообщения только одному пользователю. В другой части этой статьи я кратко расскажу о вопросах безопасности WebSocket и о том, как гарантировать, что даже если среда не поддерживаетWebSocketсоглашение, основанное наWebSocketрешение тоже работает.

Уведомление,WebSocketТема безопасности здесь затронута лишь вкратце, так как это очень сложный вопрос, который можно было бы написать в отдельной статье. По этой причине, а также в последнем разделе моей статьиWebSocket in production?Учитывая факторы, упомянутые в разделе , я рекомендую изменять параметры безопасности в рабочей среде до тех пор, пока она не будет готова и не будут приняты меры безопасности.

1. WebSocket и протокол STOMP

WebSocketПротоколы обеспечивают двустороннюю связь между приложениями. Важно знатьHTTPИспользуется только для первоначального рукопожатия. После первого рукопожатияHTTPСоединение будет обновлено доWebSocketновый подержанныйTCP/IPсоединять.

WebSocketПротокол — это довольно низкоуровневый протокол. Он определяет, как преобразовать поток байтов в кадры. Фреймы могут содержать текстовые или бинарные сообщения. Поскольку само сообщение не предоставляет никакой другой информации о том, как его маршрутизировать или обрабатывать, трудно реализовать более сложные приложения без написания дополнительного кода. К счастью,WebSocketСпецификация позволяет использовать подпротоколы на более высоком уровне приложений.STOMPявляется одним из них, поSpring Frameworkслужба поддержки.

STOMPэто простой протокол обмена текстовыми сообщениями, первоначально предназначенный дляRuby,PythonиPerlи другие языки сценариев для подключения к брокерам сообщений корпоративного уровня. так какSTOMP, чтобы клиенты и агенты, разработанные на разных языках, могли отправлять и получать сообщения друг от друга.WebSocketСоглашение иногда называютWeb TCP. В этом виде толчка,STOMPназываетсяWeb HTTP. Он определяет некоторое отображение наWebSocketтип кадра кадра, например.CONNECT,SUBSCRIBE,UNSUBSCRIBE,ACKилиSEND. С одной стороны, эти команды очень удобны для управления коммуникацией, а с другой — позволяют реализовать решения с более сложными функциями, такими как подтверждение сообщений.

2. Сервер: Spring Boot и WebSocket

чтобы построитьWebSocketСерверная часть, мы будем использоватьSpring BootFramework, который ускоряет разработку автономных программ и веб-приложений на Java.Spring BootВключаютspring-WebSocketмодуль, который связан сJava WebSocket APIСоответствует стандартам (JSR-356).

использоватьSpring BootвыполнитьWebSocketСерверная часть не очень сложная задача и состоит всего из нескольких шагов, которые мы пройдем один за другим.

*Шаг 1:* Сначала добавьте зависимости библиотеки WebSocket.

<dependency>
  <groupId>org.springframework.boot</groupId>            
  <artifactId>spring-boot-starter-websocket</artifactId>
</dependency>

Если вы планируете использоватьJSONсообщение о передаче формата, вам также может понадобиться включитьGSONилиJacksonзависимости. Вам также может понадобиться структура безопасности, такая какSpring Security.

*Шаг 2:* Затем вы можете настроитьSpringвключитьWebSocketиSTOMPобмен сообщениями.

Configuration
@EnableWebSocketMessageBroker
public class WebSocketConfig implements WebSocketMessageBrokerConfigurer {

  @Override
  public void registerStompEndpoints(StompEndpointRegistry
   registry) {
    registry.addEndpoint("/mywebsockets")
        .setAllowedOrigins("mydomain.com").withSockJS();
  }

  @Override
  public void configureMessageBroker(MessageBrokerRegistry config){ 
    config.enableSimpleBroker("/topic/", "/queue/");
    config.setApplicationDestinationPrefixes("/app");
  }
}

configureMessageBrokerДелайте в основном две вещи:

  • Создает брокер сообщений в памяти, который содержит одно или несколько мест назначения для отправки и получения сообщений. В приведенном выше примере определены два префикса адреса назначения:topicиqueue. Они следуют соглашению о том, что в рамках модели pub-subtopicАдрес назначения для сообщений с префиксом, которые должны быть доставлены всем подписавшимся клиентам. С другой стороны, адрес назначения личных сообщений обычно начинается сqueueкак префикс.
  • определить префиксapp, который используется для фильтрации целевых адресов, находящихся вControllerв одеяле@MessageMappingМодифицированный метод обработки.

- Рис. Как обрабатывать сервер сообщений

Возвращаясь к приведенному выше фрагменту — вероятно, вы заметили, что методwithSockJS()вызов - это позволяетSockJSРезервный вариант. Короче говоря, даже если интернет-браузер не поддерживаетWebSocketпротокола, это также позволит нашимWebSocketsРабота. Я рассмотрю эту тему более подробно.

Еще одно уточнение - почему мы звоним на конечную точкуsetAllowedOrigins()метод.一般是必需的,因为WebSocketиSockJSПоведение по умолчанию — принимать только запросы от одного и того же источника. Поэтому, если клиент и сервер находятся в разных доменах, необходимо вызвать этот метод, чтобы разрешить связь между ними.

*Шаг 3:* Внедрите контроллер, который обрабатывает запросы пользователей Он будет транслировать полученное сообщение всем пользователям, подписавшимся на определенную тему.

Это отправить сообщение на целевой адрес/topic/newsметод примера.

@MessageMapping("/news")
@SendTo("/topic/news")
public void broadcastNews(@Payload String message) {
  return message;
}

также можно использоватьSimpMessagingTemplateвместо аннотаций@SendTo, вы можете Autowired внутри контроллера.

@MessageMapping("/news")
public void broadcastNews(@Payload String message) {
  this.simpMessagingTemplate.convertAndSend("/topic/news", message)
}

На более поздних этапах может потребоваться добавить некоторые дополнительные классы для защиты конечной точки, например.Spring Securityв рамкеResourceServerConfigurerAdapterилиWebSecurityConfigurerAdapter. Кроме того, часто бывает полезно реализовать модель сообщений таким образом, чтобыJSONмогут быть сопоставлены с объектами.

3. Создание клиента WebSocket

Реализация клиента — более простая задача.

*Шаг 1: *СборкаSpring STOMPклиент

@Autowired
private WebSocketStompClient stompClient;

Шаг 2:открытое соединение

StompSessionHandler sessionHandler = new CustmStompSessionHandler();

StompSession stompSession = stompClient.connect(loggerServerQueueUrl, 
sessionHandler).get();

После завершения этой операции сообщение может быть отправлено получателю. Сообщение будет отправлено всем подписавшимся на тему пользователям.

stompSession.send("topic/greetings", "Hello new user");

Следующие методы также могут подписаться на сообщения

session.subscribe("topic/greetings", this);

@Override
public void handleFrame(StompHeaders headers, Object payload) {
    Message msg = (Message) payload;
    logger.info("Received : " + msg.getText()+ " from : " + 
    msg.getFrom());
}

Иногда необходимо отправить сообщение конкретному пользователю (например, при реализации чата). Затем клиентская и серверная стороны должны использовать отдельные адреса назначения, предназначенные для этого частного сеанса. Имя адреса назначения может быть создано путем добавления уникального идентификатора к общему адресу, например./queue/chat-user123.HTTPсеанс илиSTOMPИдентификатор сеанса, используемый для этой цели.

SpringОблегчает отправку личных сообщений. нам просто нужно использовать@SendToUserПримечанияControllerМетоды. Затем адрес назначения будет указанUserDestinationMessageHandlerобработка, основанная на идентификаторе сеанса. На стороне клиента, когда клиент подписывается на/userПри наличии префикса адреса назначения этот адрес назначения будет преобразован в адрес назначения, уникальный для этого пользователя. На стороне сервера, по желанию пользователяPrincipalРазрешить адрес назначения пользователя.

использование на стороне сервера@SendToUserАннотированный пример кода:

@MessageMapping("/greetings")
@SendToUser("/queue/greetings")
public String reply(@Payload String message,
   Principal user) {
 return  "Hello " + message;
}

или вы можете использоватьSimpMessagingTemplate:

String username = ...
this.simpMessagingTemplate.convertAndSendToUser();
   username, "/queue/greetings", "Hello " + username);

Теперь давайте посмотрим, как реализовать метод, который может получать личные сообщения.JavaScript(SockJS)Клиент, клиент может получить сообщение в приведенном выше примере отправки кода Java. Стоит отметить, чтоWebSocketsдаHTML5часть спецификации и поддерживается большинством современных браузеров (начиная с версии 10,Internet Explorerподдерживать их).

function connect() {
 var socket = new SockJS('/greetings');
 stompClient = Stomp.over(socket);
 stompClient.connect({}, function (frame) {
   stompClient.subscribe('/user/queue/greetings', function (greeting) {
     showGreeting(JSON.parse(greeting.body).name);
   });
 });
}

function sendName() {
 stompClient.send("/app/greetings", {}, $("#name").val());
}

Вы могли заметить, что для получения личных сообщений клиентам необходимо подписаться с префиксом/userадрес назначения/queue/greetings. Ему не нужно беспокоиться о каких-либо уникальных идентификаторах. Однако, прежде чем клиент сможет войти в приложение, серверная сторона должна инициализироватьPrincipalобъект.

4. Безопасность веб-сокетов

многиеWebИспользование приложения основано наcookieаутентификацию, например, мы можем использоватьSpring SecurityОграничьте вошедшим в систему пользователям доступ к определенным страницам или ограничениям контроллера. Затем безопасность пользовательского контекста поддерживается с помощью сеанса HTTP на основе файлов cookie, который позже связывается сWebSocketилиSockJSСвязанная сессия.WebSocketКонечные точки могут быть защищены, как и любой другой запрос, например, вSpring WebSecurityConfigurerAdapterреализация в.

в настоящее время,Webприложения обычно используютREST APIВ качестве бэкенда используйтеOAuth/JWTТокен для аутентификации и авторизации пользователя.WebSocketПротокол не описывает сервер вHTTPКак клиент аутентифицируется во время рукопожатия. На самом деле стандартHTTPзаголовки (например, Authorization) используются для этой цели. К сожалению, не всеSTOMPВсе клиенты это поддерживают.SpringизSTOMPКлиент позволяет установить заголовок, чтобы пожать руку:

WebSocketHttpHeaders handshakeHeaders = new WebSocketHttpHeaders();
handshakeHeaders.add(principalRequestHeader, principalRequestValue);

ноSockJSКлиент JavaScript не поддерживает использованиеSockJSЗапрос отправляет заголовок Authorization (Авторизация). Однако он позволяет отправлять параметры запроса, которые можно использовать для передачи токенов. Этот подход требует написания пользовательского кода на стороне сервера, который будет считывать токен из параметров запроса и проверять его. Особенно важно убедиться, что токен не регистрируется вместе с запросом (или что журналы хорошо защищены), так как это может привести к серьезным нарушениям безопасности.

5. Резервные варианты SockJS

иWebSocketИнтеграция не всегда может быть так хороша, как должна быть. Некоторые браузеры (например, IE 9) не поддерживаютWebSocket. Более того, ограничительные прокси-серверы могут сделать невозможным обновление HTTP или обрезать соединения, которые были открыты слишком долго. В этом случае на помощь приходит SockJS.

SockJSСуществует три основных категории трансферов:WebSocket,HTTP StreamingиHTTP Long Polling. сообщение отSockJSОтправитьGET /infoНачните с получения базовой информации с сервера.SockJSКакой транспорт использовать, зависит от ответа. Первый вариантWebSocket. Если не поддерживается, используйте, если возможноStreaming. еслиStreamingнедоступен, выберите в качестве метода передачи опрос.

6. Используйте веб-сокеты в производстве

Хотя эта установка работает, она не «оптимальна».Spring Bootпозволяет использовать любойSTOMPПолная система обмена сообщениями для протокола (например, ActiveMQ, RabbitMQ) и многое другое может поддерживаться внешними брокерами.STOMPДействия (например, подтверждение, аренда) вместо простого прокси, который мы используем.STOMP Over WebSocketпредоставить оWebSocketиSTOMPинформация о договоре. он перечисляет обработкуSTOMPСистема обмена сообщениями протокола может быть лучшим решением, используемым в производстве. Особенно из-за большого количества запросов агент сообщений нуждается в кластере (простой агент сообщений Spring не подходит для кластера). Тогда не надоWebSocketConfigВключите Simple Proxy в , вместо этого вам нужно включитьStompРетранслятор брокера, который пересылает сообщения внешним брокерам сообщений и обратно. В целом, внешние брокеры сообщений могут помочь вам создать более масштабируемые и надежные решения.

Оригинальная ссылка:woohoo.topthem.com/java/stomp-…

Автор: Томаш Домбровский

Переводчик: Эмма

Рекомендуется обратить внимание на паблик аккаунт: большой парень вне банка

Ежедневно публикуйте отличные зарубежные статьи с техническими переводами, которые вдохновляют на то, чтобы помочь отечественным разработчикам стать лучше!