пятница, 11 июля 2014 г.

Передача сообщений между WebSocket и JMS (Spring Framework)

После вчерашнего поста о реализации передачи сообщений между JMS и WebSocket силами Java EE я решил сообразить аналогичную реализацию при помощи Spring Framework. Для этого потребуется сервер приложений, реализующий JSR 356 (Apache Tomcat версии 7.0.47 и выше, GlassFish 4, WildFly 8 или любой другой), JMS-брокер, в качестве которого я выбрал Apache ActiveMQ, и браузер, реализующий RFC 6455.

pom.xml

Для нашего проекта потребуются следующие зависимости:
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-webmvc</artifactId>
            <version>4.0.6.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-jms</artifactId>
            <version>4.0.6.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>org.apache.activemq</groupId>
            <artifactId>activemq-client</artifactId>
            <version>5.10.0</version>
        </dependency>
        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-log4j12</artifactId>
            <version>1.7.7</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-websocket</artifactId>
            <version>4.0.6.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>javax</groupId>
            <artifactId>javaee-web-api</artifactId>
            <version>7.0</version>
            <scope>provided</scope>
        </dependency>
Если используется Tomcat, то понадобится реализация Java API for JSON Processing:
        <dependency>
            <groupId>org.glassfish</groupId>
            <artifactId>javax.json</artifactId>
            <version>1.0.4</version>
        </dependency> 

Реализация серверной стороны

Как я уже упомянул в предыдущем посте, реализовать передачу сообщений между JMS и WebSocket при помощи Spring очень просто. Для этого понадобится класс, обрабатывающий события WebSocket (хэндлер), а так же JMS-листнер, в который хэндлер будет внедрён. 

WebSocket-хэндлер (ChatHandler.java):
public class ChatHandler extends TextWebSocketHandler {

    private final static Logger logger = LoggerFactory.getLogger(ChatHandler.class);
    private final static Set<WebSocketSession> sessions = Collections.synchronizedSet(new HashSet<WebSocketSession>());

    @Override
    public void afterConnectionClosed(WebSocketSession session, CloseStatus status) throws Exception {
        logger.info("User has left the chat");

        super.afterConnectionClosed(session, status);
        sessions.remove(session);
    }

    @Override
    public void afterConnectionEstablished(WebSocketSession session) throws Exception {
        logger.info("New user has joined the chat");

        super.afterConnectionEstablished(session);
        sessions.add(session);
    }

    public void sendMessage(final String message) throws IOException {
        logger.info("Sending message to all participants");

        for (WebSocketSession session : sessions) {
            if (session.isOpen()) {
                session.sendMessage(new TextMessage(message));
            } else {
                sessions.remove(session);
            }
        }
    }
}

JMS-листнер (WebSocketOutMessageListener.java):
public class WebSocketOutMessageListener implements MessageListener {

    private static final Logger logger = LoggerFactory.getLogger(WebSocketOutMessageListener.class);

    private ChatHandler webSocketHandler;

    public void setWebSocketHandler(ChatHandler webSocketHandler) {
        this.webSocketHandler = webSocketHandler;
    }

    @Override
    public void onMessage(Message message) {
        if (message instanceof TextMessage) {
            TextMessage textMessage = (TextMessage) message;
            try {
                webSocketHandler.sendMessage(textMessage.getText());
            } catch (JMSException | IOException ex) {
                logger.error(ex.getMessage(), ex);
            }
        }
    }
}

Spring-beans XML
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:p="http://www.springframework.org/schema/p"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:websocket="http://www.springframework.org/schema/websocket"
       xmlns:jms="http://www.springframework.org/schema/jms"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
                           http://www.springframework.org/schema/beans/spring-beans.xsd
                           http://www.springframework.org/schema/websocket
                           http://www.springframework.org/schema/websocket/spring-websocket.xsd
                           http://www.springframework.org/schema/jms
                           http://www.springframework.org/schema/jms/spring-jms.xsd">

    <bean class="org.apache.activemq.ActiveMQConnectionFactory"
          id="activeMQConnectionFactory"
          p:brokerURL="tcp://localhost:61616"/>
    
    <websocket:handlers>
        <websocket:mapping path="/chat" handler="chatHandler"/>
    </websocket:handlers>
    
    <bean class="org.acruxsource.sandbox.spring.jmstows.websocket.ChatHandler" 
          id="chatHandler"/>
    
    <bean class="org.acruxsource.sandbox.spring.jmstows.jms.WebSocketOutMessageListener"
          id="webSocketOutMessageListener"
          p:webSocketHandler-ref="chatHandler"
          lazy-init="false"/>
    
    <jms:listener-container connection-factory="activeMQConnectionFactory" container-type="default">
        <jms:listener destination="websocket.out" ref="webSocketOutMessageListener" method="onMessage"/>
    </jms:listener-container>

</beans>

Этого достаточно, что бы отправлять сообщения из JMS клиентам WebSocket.

Реализация клиентской стороны

Для клиентской стороны достаточно небольшой странички:
index.jsp
<%@page contentType="text/html" pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
    <head>
        <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
        <title>WebSocket test</title>
    </head>
    <body>
        <script type="text/javascript">
            var ws;

            if ('WebSocket' in window) {
                console.log('This browser supports websocket');
                ws = new WebSocket('ws://${pageContext.request.serverName}:${pageContext.request.serverPort}${pageContext.request.contextPath}/sandbox/chat');
                ws.onmessage = function(message) {
                    console.log(message);
                };
            }
        </script>
    </body>
</html> 

Сообщения из очереди websocket.out будут отображаться в консоли веб-браузера.

Для отправки сообщений в обратном направлении, из WebSocket в JMS понадобится ещё один класс.

JmsMessageSender.java
public class JmsMessageSender {

    private JmsTemplate jmsTemplate;

    public void setJmsTemplate(JmsTemplate jmsTemplate) {
        this.jmsTemplate = jmsTemplate;
    }

    public void sendMessage(String destination, final String message) {
        jmsTemplate.send(destination, new MessageCreator() {

            @Override
            public Message createMessage(Session session) throws JMSException {
                TextMessage textMessage = session.createTextMessage();
                textMessage.setText(message);
                return textMessage;
            }
        });
    }
}
Объект этого класса нужно внедрить в WebSocket-хэндлер и использовать его для отправки сообщений в очередь

ChatHandler.java
    private JmsMessageSender jmsMessageSender;

    public void setJmsMessageSender(JmsMessageSender jmsMessageSender) {
        this.jmsMessageSender = jmsMessageSender;
    }

    @Override
    protected void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception {
        logger.info("New incoming message: " + message.getPayload());
        jmsMessageSender.sendMessage("websocket.in", message.getPayload());
    }

Остаётся изменить spring-beans:
    <bean class="org.springframework.jms.connection.SingleConnectionFactory"
          id="jmsProducerConnectionFactory"
          p:targetConnectionFactory-ref="activeMQConnectionFactory"/>

    <bean id="jmsTemplate" class="org.springframework.jms.core.JmsTemplate"
          p:connectionFactory-ref="jmsProducerConnectionFactory"/>
    
    <bean class="org.acruxsource.sandbox.spring.jmstows.websocket.ChatHandler" 
          id="chatHandler"
          p:jmsMessageSender-ref="jmsMessageSender"/>
    
    <bean class="org.acruxsource.sandbox.spring.jmstows.jms.JmsMessageSender"
          id="jmsMessageSender"
          p:jmsTemplate-ref="jmsTemplate"/>

Теперь для теста из консоли браузера можно отправить сообщение: ws.send("Some test message").
В следующих постах я более основательно опишу работу с WebSocket.

Полезные ссылки

Комментариев нет:

Отправить комментарий