Easy Messaging with STOMP over WebSockets using ActiveMQ and HornetQ


  Andriy Redko {devmind}


Saturday, June 29, 2013

Easy Messaging with STOMP over WebSockets using ActiveMQ and HornetQ

Messaging is an extremely powerful tool for building distributed software systems of different levels. Typically, at least in Java ecosystem, the client (front-end) never interacts with message broker (or exchange) directly but does it by invoking server-side (back-end) services. Or client may not even be aware that there's messaging solution in place.

With Websockets gaining more and more adoption, and wide support of the text-oriented protocols likeSTOMP (used to communicate with message broker or exchange) are going to make a difference. Today's post will try to explain how simple it is to expose two very popularJMS implementations, ApacheActiveMQ and JBossHornetQ, to be available to web front-end (JavaScript) usingSTOMP overWebsockets.

Before digging into the code, one might argue that it's not a good idea to do that. So what's the purpose? The answer really depends:

  • you are developing prototype / proof of concept and need easy way to integrate publish/subscribe or peer-to-peer messaging
  • you don't want / need to build sophisticated architecture and the simplest solution which works is just enough
The scalability, fail-over and a lot of other very important decisions are not taken into consideration here but definitely should be if you are developing robust and resilient architecture.

So let's get started. As always, it's better to start with problem we're trying to solve: we would like to develop simple publish/subscribe solution where web client written in JavaScript will be able to send messages and listen for a specific topic. Whenever any message has been received, client just shows simple alert window. Please note that we need to use modern browser which supportsWebsockets, such asGoogle Chrome orMozilla Firefox.

For both our examples client's code remains the same and so let's start with that. The great starting point isSTOMP Over WebSocket article which introduces thestomp.js module and here is ourindex.html:

01<script src="stomp.js"></script>
02 
03<script type="text/javascript">
04 varclient = Stomp.client("ws://localhost:61614/stomp","v11.stomp");
05 
06 client.connect("","",
07  function() {
08      client.subscribe("jms.topic.test",
09       function( message ) {
10           alert( message );
11          },
12    { priority: 9 }
13      );
14    
15   client.send("jms.topic.test", { priority: 9 },"Pub/Sub over STOMP!");
16  }
17 );
18  
19</script>

Extremely simple code but few details are worth to explain. First, we are looking forWebsockets endpoint atws://localhost:61614/stomp. It's sufficient for local deployment but better to replacelocalhost with real IP address or host name. Secondly, once connected, client subscribes to the topic (only interested in messages withpriority: 9) and publishes the message to this topic immediately after. From client prospective, we are done.

Let's move on to message broker and our first one in list is Apache ActiveMQ. To make the example simple, we will embed ApacheActiveMQ broker into simpleSpring application without using configuration XML files. As source code is available onGitHub, I will skip the POM file snippet and just show the code:

01package com.example.messaging;
02 
03import java.util.Collections;
04 
05import org.apache.activemq.broker.BrokerService;
06import org.apache.activemq.broker.jmx.ManagementContext;
07import org.apache.activemq.command.ActiveMQDestination;
08import org.apache.activemq.command.ActiveMQTopic;
09import org.apache.activemq.hooks.SpringContextHook;
10import org.springframework.context.annotation.Bean;
11import org.springframework.context.annotation.Configuration;
12 
13@Configuration
14public class AppConfig {
15    @Bean( initMethod ="start", destroyMethod ="stop")
16    publicBrokerService broker()throwsException {
17        finalBrokerService broker =newBrokerService();   
18        broker.addConnector("ws://localhost:61614");
19        broker.setPersistent(false);
20        broker.setShutdownHooks( Collections.< Runnable >singletonList(newSpringContextHook() ) );
21   
22        finalActiveMQTopic topic =newActiveMQTopic("jms.topic.test");
23        broker.setDestinations(newActiveMQDestination[] { topic }  );
24   
25        finalManagementContext managementContext =newManagementContext();
26        managementContext.setCreateConnector(true);
27        broker.setManagementContext( managementContext );
28   
29        returnbroker;
30    }
31}

As we can see, the ActiveMQ broker is configured withws://localhost:61614 connector which assumes usingSTOMP protocol. Also, we are creating JMS topic with namejms.topic.test and enabling JMX management instrumentation. And to run it, simpleStarter class:

01package com.example.messaging;
02 
03import org.springframework.context.ApplicationContext;
04import org.springframework.context.annotation.AnnotationConfigApplicationContext;
05 
06public class Starter  {
07    publicstaticvoid main( String[] args ) {
08        ApplicationContext context =newAnnotationConfigApplicationContext( AppConfig.class);
09    }
10}

Now, having it up and running, let's open index.html file in browser, we should see something like that:

Simple! For curious readers, ActiveMQ uses Jetty 7.6.7.v20120910 forWebsockets support and won't work with latestJetty distributions.

Moving on, with respect to HornetQ the implementations looks a bit different though not very complicated as well. AsStarter class remains the same, the only change is the configuration:

01package com.example.hornetq;
02 
03import java.util.Collections;
04import java.util.HashMap;
05import java.util.Map;
06 
07import org.hornetq.api.core.TransportConfiguration;
08import org.hornetq.core.config.impl.ConfigurationImpl;
09import org.hornetq.core.remoting.impl.netty.NettyAcceptorFactory;
10import org.hornetq.core.remoting.impl.netty.TransportConstants;
11import org.hornetq.core.server.JournalType;
12import org.hornetq.jms.server.config.ConnectionFactoryConfiguration;
13import org.hornetq.jms.server.config.JMSConfiguration;
14import org.hornetq.jms.server.config.TopicConfiguration;
15import org.hornetq.jms.server.config.impl.ConnectionFactoryConfigurationImpl;
16import org.hornetq.jms.server.config.impl.JMSConfigurationImpl;
17import org.hornetq.jms.server.config.impl.TopicConfigurationImpl;
18import org.hornetq.jms.server.embedded.EmbeddedJMS;
19import org.springframework.context.annotation.Bean;
20import org.springframework.context.annotation.Configuration;
21 
22@Configuration
23public class AppConfig {
24    @Bean( initMethod ="start", destroyMethod ="stop")
25    publicEmbeddedJMS broker()throwsException {
26        finalConfigurationImpl configuration =newConfigurationImpl();
27        configuration.setPersistenceEnabled(false);
28        configuration.setJournalType( JournalType.NIO );
29        configuration.setJMXManagementEnabled(true);
30        configuration.setSecurityEnabled(false);
31   
32        finalMap< String, Object > params =newHashMap<>();
33        params.put( TransportConstants.HOST_PROP_NAME,"localhost");
34        params.put( TransportConstants.PROTOCOL_PROP_NAME,"stomp_ws");
35        params.put( TransportConstants.PORT_PROP_NAME,"61614");
36   
37        finalTransportConfiguration stomp =newTransportConfiguration( NettyAcceptorFactory.class.getName(), params );
38        configuration.getAcceptorConfigurations().add( stomp );
39        configuration.getConnectorConfigurations().put("stomp_ws", stomp );
40   
41        finalConnectionFactoryConfiguration cfConfig =newConnectionFactoryConfigurationImpl("cf",true,"/cf");
42        cfConfig.setConnectorNames( Collections.singletonList("stomp_ws") );
43   
44        finalJMSConfiguration jmsConfig =newJMSConfigurationImpl();
45        jmsConfig.getConnectionFactoryConfigurations().add( cfConfig );
46   
47        finalTopicConfiguration topicConfig =newTopicConfigurationImpl("test","/topic/test");
48        jmsConfig.getTopicConfigurations().add( topicConfig );
49   
50        finalEmbeddedJMS jmsServer =newEmbeddedJMS();
51        jmsServer.setConfiguration( configuration );
52        jmsServer.setJmsConfiguration( jmsConfig );
53   
54        returnjmsServer;
55    }
56}

The complete source code is on GitHub. After running Starter class and openningindex.html in browser, we should see very similar results:

HornetQ configuration looks a bit more verbose, however there are no additional dependencies involved except brilliantNetty framework.

For my own curiosity, I replaced the ActiveMQ broker with Apollo implementation. Though I succeeded with making it works as expected, I found the API to be very cumbersome, at least in current version1.6, so I haven't covered it in this post.

All sources are available on GitHub: Apache ActiveMQ example and JBoss HornetQ example

6 comments:
christian posta said...

Great write up.

Can you tell a little more about your config with Apollo? Let me know what you found to be cumbersome and I can help you out. Or change it to be more friendly :)

Cheers!

&lt;img src="http://1.bp.blogspot.com/_WNHv4iYKMe0/S2RnlNBCAKI/AAAAAAAAAA0/liRhama55ss/S45/photo.jpg" width="35" height="35" class="photo" alt=""&gt;
Andriy Redko said...

Hi Christian,

Thank you very much for the feedback (as your blog follower, I really appreciate that). Yes, I found Apollo API to be cumbersome and verbose. I don't mean it's bad but I think it's not expressive and intuitive enough. The example I've taken as a foundation is this one: https://github.com/apache/activemq-apollo/blob/trunk/apollo-distro/src/main/release/examples/java-embedded-broker/src/main/java/example/EmbeddedBroker.java

Though everything worked out fine, I think Apollo API could be better. I understand there are a reasons for that and I would love to make my contributions.

Thank you very much.

Best Regards,
Andriy Redko

jmesnil said...

Hi,

Great introduction article.

There is only a minor issue in it.

When you call client.subscribe(destination, callback, { priority: 9}); this does *not* mean that the callback will be called only for messages with priority = 9.
The last argument is the headers sent by the SUBSCRIBE frame and will not have any effect.

What you want is to pass a selector[1] header like this:

client.subscribe(destination, callback, { selector: "priority=9" });

Please note that this is not specified in the STOMP protocol and may or may not be supported by the messaging brokers (both STOMP and ActiveMQ does support it).

&lt;img src="http://1.bp.blogspot.com/_WNHv4iYKMe0/S2RnlNBCAKI/AAAAAAAAAA0/liRhama55ss/S45/photo.jpg" width="35" height="35" class="photo" alt=""&gt;
Andriy Redko said...

Hi Jeff,

Thank you very much for such a great clarification. That's definitely something I should have payed more attention to.

Thank you!

Best Regards,
Andriy Redko

Zaw Min Tun said...

Thanks for the great post.
Is it possible to get the topic starting from the very beginning every time I connect to the topic?

&lt;img src="http://1.bp.blogspot.com/_WNHv4iYKMe0/S2RnlNBCAKI/AAAAAAAAAA0/liRhama55ss/S45/photo.jpg" width="35" height="35" class="photo" alt=""&gt;
Andriy Redko said...

Hi Zaw,

Thanks a lot for the comments. As far as I know it is possible to do that in STOMP with durable subscriptions. I haven't done it personally but you may look at https://activemq.apache.org/how-do-durable-queues-and-topics-work.html and http://activemq.apache.org/apollo/documentation/stomp-manual.html#Topic_Durable_Subscriptions for more details.

Thanks.

Best Regards,
Andriy Redko

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值