[hornetq-commits] JBoss hornetq SVN: r9181 - in trunk: docs/user-manual/en and 15 other directories.
do-not-reply at jboss.org
do-not-reply at jboss.org
Wed Apr 28 11:57:39 EDT 2010
Author: jmesnil
Date: 2010-04-28 11:57:32 -0400 (Wed, 28 Apr 2010)
New Revision: 9181
Added:
trunk/examples/jms/stomp-websockets/
trunk/examples/jms/stomp-websockets/build.bat
trunk/examples/jms/stomp-websockets/build.sh
trunk/examples/jms/stomp-websockets/build.xml
trunk/examples/jms/stomp-websockets/chat/
trunk/examples/jms/stomp-websockets/chat/chat.css
trunk/examples/jms/stomp-websockets/chat/chat.js
trunk/examples/jms/stomp-websockets/chat/index.html
trunk/examples/jms/stomp-websockets/chat/stomp.js
trunk/examples/jms/stomp-websockets/readme.html
trunk/examples/jms/stomp-websockets/server0/
trunk/examples/jms/stomp-websockets/server0/client-jndi.properties
trunk/examples/jms/stomp-websockets/server0/hornetq-beans.xml
trunk/examples/jms/stomp-websockets/server0/hornetq-configuration.xml
trunk/examples/jms/stomp-websockets/server0/hornetq-jms.xml
trunk/examples/jms/stomp-websockets/server0/hornetq-users.xml
trunk/examples/jms/stomp-websockets/server0/jndi.properties
trunk/examples/jms/stomp-websockets/server0/logging.properties
trunk/examples/jms/stomp-websockets/src/
trunk/examples/jms/stomp-websockets/src/org/
trunk/examples/jms/stomp-websockets/src/org/hornetq/
trunk/examples/jms/stomp-websockets/src/org/hornetq/jms/
trunk/examples/jms/stomp-websockets/src/org/hornetq/jms/example/
trunk/examples/jms/stomp-websockets/src/org/hornetq/jms/example/StompWebSocketExample.java
trunk/src/main/org/hornetq/core/protocol/stomp/WebSocketServerHandler.java
trunk/src/main/org/hornetq/core/protocol/stomp/WebSocketStompFrameEncoder.java
trunk/tests/src/org/hornetq/tests/integration/stomp/StompWebSocketTest.java
Removed:
trunk/src/main/org/hornetq/integration/transports/
Modified:
trunk/.classpath
trunk/docs/user-manual/en/examples.xml
trunk/docs/user-manual/en/interoperability.xml
trunk/src/main/org/hornetq/core/protocol/stomp/StompFrame.java
trunk/src/main/org/hornetq/core/remoting/impl/netty/NettyAcceptor.java
trunk/src/main/org/hornetq/core/remoting/server/impl/RemotingServiceImpl.java
trunk/src/main/org/hornetq/spi/core/protocol/ProtocolType.java
Log:
https://jira.jboss.org/jira/browse/HORNETQ-279: Support Stomp over Web Sockets
* add stomp_ws protocol to support sending/receiving Stomp messages over HTML5 Web Sockets
* add stomp-websockets example
* documentation
Modified: trunk/.classpath
===================================================================
--- trunk/.classpath 2010-04-28 08:54:19 UTC (rev 9180)
+++ trunk/.classpath 2010-04-28 15:57:32 UTC (rev 9181)
@@ -67,6 +67,7 @@
<classpathentry kind="src" path="examples/jms/static-selector/src"/>
<classpathentry kind="src" path="examples/jms/static-selector-jms/src"/>
<classpathentry kind="src" path="examples/jms/stomp/src"/>
+ <classpathentry kind="src" path="examples/jms/stomp-websockets/src"/>
<classpathentry kind="src" path="examples/jms/symmetric-cluster/src"/>
<classpathentry kind="src" path="examples/jms/temp-queue/src"/>
<classpathentry kind="src" path="examples/jms/topic/src"/>
@@ -101,7 +102,7 @@
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER"/>
<classpathentry kind="lib" path="tests/tmpfiles"/>
<classpathentry kind="lib" path="thirdparty/net/java/dev/javacc/lib/javacc.jar"/>
- <classpathentry kind="lib" path="thirdparty/org/jboss/netty/lib/netty.jar"/>
+ <classpathentry kind="lib" path="thirdparty/org/jboss/netty/lib/netty.jar" sourcepath="/Users/jmesnil/Downloads/netty-3.2.0.BETA1-sources.jar"/>
<classpathentry kind="lib" path="thirdparty/log4j/lib/log4j.jar"/>
<classpathentry kind="lib" path="thirdparty/org/jboss/naming/lib/jnpserver.jar"/>
<classpathentry kind="lib" path="thirdparty/org/jboss/security/lib/jbosssx.jar"/>
Modified: trunk/docs/user-manual/en/examples.xml
===================================================================
--- trunk/docs/user-manual/en/examples.xml 2010-04-28 08:54:19 UTC (rev 9180)
+++ trunk/docs/user-manual/en/examples.xml 2010-04-28 15:57:32 UTC (rev 9181)
@@ -421,6 +421,12 @@
HornetQ server to send and receive Stomp messages.</para>
</section>
<section>
+ <title>Stomp Over Web Sockets</title>
+ <para>The <literal>stomp-websockets</literal> example shows you how to configure a
+ HornetQ server to send and receive Stomp messages directly from Web browsers (provided
+ they support Web Sockets).</para>
+ </section>
+ <section>
<title>Symmetric Cluster</title>
<para>The <literal>symmetric-cluster</literal> example demonstrates a symmetric cluster
set-up with HornetQ.</para>
Modified: trunk/docs/user-manual/en/interoperability.xml
===================================================================
--- trunk/docs/user-manual/en/interoperability.xml 2010-04-28 08:54:19 UTC (rev 9180)
+++ trunk/docs/user-manual/en/interoperability.xml 2010-04-28 15:57:32 UTC (rev 9181)
@@ -79,10 +79,10 @@
<para>send or subscribe to a JMS <emphasis>Topic</emphasis> by prepending the topic name by <literal>jms.topic.</literal>.</para>
<para>For example to subscribe to the <literal>stocks</literal> JMS Topic, the Stomp client must send the frame:</para>
<programlisting>
- SUBSCRIBE
- destination:jms.topic.stocks
-
- ^@
+SUBSCRIBE
+destination:jms.topic.stocks
+
+^@
</programlisting>
</listitem>
</itemizedlist>
@@ -108,6 +108,30 @@
</programlisting>
</section>
</section>
+
+ <section id="stomp.websockets">
+ <title>Stomp Over Web Sockets</title>
+ <para>HornetQ also support Stomp over <ulink url="http://dev.w3.org/html5/websockets/">Web Sockets</ulink>. Modern web browser which support Web Sockets can send and receive
+ Stomp messages from HornetQ.</para>
+ <para>To enable Stomp over Web Sockets, you must configure a <literal>NettyAcceptor</literal> with a <literal>protocol</literal>
+ parameter set to <literal>stomp_ws</literal>:</para>
+ <programlisting>
+<acceptor name="stomp-ws-acceptor">
+ <factory-class>org.hornetq.core.remoting.impl.netty.NettyAcceptorFactory</factory-class>
+ <param key="protocol" value="stomp_ws"/>
+ <param key="port" value="61614"/>
+</acceptor>
+ </programlisting>
+ <para>With this configuration, HornetQ will accept Stomp connections over Web Sockets on
+ the port <literal>61614</literal> with the URL path <literal>/stomp</literal>.
+ Web browser can then connect to <literal>ws://<server>:61614/stomp</literal> usin a Web Socket to send and receive Stomp
+ messages.</para>
+ <para>A companion JavaScript library to ease client-side development is available from
+ <ulink url="http://github.com/jmesnil/stomp-websocket">GitHub</ulink> (please see
+ its <ulink url="http://jmesnil.net/stomp-websocket/doc/">documentation</ulink> for a complete description).</para>
+ <para>The <literal>stomp-websockets</literal> example shows how to configure HornetQ server to have web browsers and Java
+ applications exchanges messages on a JMS topic.</para>
+ </section>
<section id="stompconnect">
<title>StompConnect</title>
Added: trunk/examples/jms/stomp-websockets/build.bat
===================================================================
--- trunk/examples/jms/stomp-websockets/build.bat (rev 0)
+++ trunk/examples/jms/stomp-websockets/build.bat 2010-04-28 15:57:32 UTC (rev 9181)
@@ -0,0 +1,13 @@
+ at echo off
+
+set "OVERRIDE_ANT_HOME=..\..\..\tools\ant"
+
+if exist "..\..\..\src\bin\build.bat" (
+ rem running from TRUNK
+ call ..\..\..\src\bin\build.bat %*
+) else (
+ rem running from the distro
+ call ..\..\..\bin\build.bat %*
+)
+
+set "OVERRIDE_ANT_HOME="
Added: trunk/examples/jms/stomp-websockets/build.sh
===================================================================
--- trunk/examples/jms/stomp-websockets/build.sh (rev 0)
+++ trunk/examples/jms/stomp-websockets/build.sh 2010-04-28 15:57:32 UTC (rev 9181)
@@ -0,0 +1,15 @@
+#!/bin/sh
+
+OVERRIDE_ANT_HOME=../../../tools/ant
+export OVERRIDE_ANT_HOME
+
+if [ -f "../../../src/bin/build.sh" ]; then
+ # running from TRUNK
+ ../../../src/bin/build.sh "$@"
+else
+ # running from the distro
+ ../../../bin/build.sh "$@"
+fi
+
+
+
Property changes on: trunk/examples/jms/stomp-websockets/build.sh
___________________________________________________________________
Name: svn:executable
+ *
Added: trunk/examples/jms/stomp-websockets/build.xml
===================================================================
--- trunk/examples/jms/stomp-websockets/build.xml (rev 0)
+++ trunk/examples/jms/stomp-websockets/build.xml 2010-04-28 15:57:32 UTC (rev 9181)
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE project [
+ <!ENTITY libraries SYSTEM "../../../thirdparty/libraries.ent">
+ ]>
+<!--
+ ~ Copyright 2009 Red Hat, Inc.
+ ~ Red Hat licenses this file to you under the Apache License, version
+ ~ 2.0 (the "License"); you may not use this file except in compliance
+ ~ with the License. You may obtain a copy of the License at
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ ~ implied. See the License for the specific language governing
+ ~ permissions and limitations under the License.
+ -->
+<project default="runRemote" name="HornetQ Stomp Over WebSockets Example">
+
+ <import file="../../common/build.xml"/>
+
+ <target name="runRemote">
+ <antcall target="runExample">
+ <param name="example.classname" value="org.hornetq.jms.example.StompWebSocketExample"/>
+ <param name="hornetq.example.runServer" value="false"/>
+ </antcall>
+ </target>
+
+</project>
\ No newline at end of file
Added: trunk/examples/jms/stomp-websockets/chat/chat.css
===================================================================
--- trunk/examples/jms/stomp-websockets/chat/chat.css (rev 0)
+++ trunk/examples/jms/stomp-websockets/chat/chat.css 2010-04-28 15:57:32 UTC (rev 9181)
@@ -0,0 +1,99 @@
+* {
+ margin: 0;
+ padding: 0;
+}
+
+body {
+ font-family: 'Helvetica Neue', Helvetica, Verdana, Arial, sans-serif;
+ padding: 10px;
+}
+
+#disconnect {
+ display: none;
+}
+#subscribe {
+ display: none;
+}
+
+#debug {
+ background-color: #F0F0F0;
+ font-size: 12px;
+ height: 75%;
+ overflow: auto;
+ padding: 10px;
+ position: absolute;
+ right: 10px;
+ top: 10px;
+ width: 250px;
+ z-index: 100;
+}
+
+#send_form {
+ bottom: 5px;
+ position: absolute;
+ width: 99%;
+}
+
+#send_form #send_form_input {
+ border: 1px solid #CCC;
+ font-size: 16px;
+ height: 20px;
+ padding: 5px;
+ width: 98%;
+}
+
+#send_form input[disabled] {
+ background-color: #EEE;
+}
+
+#messages {
+ bottom: 25px;
+ left: 0;
+ overflow: auto;
+ padding: 5px;
+ right: 0;
+ top: 2em;
+ z-index: -1;
+}
+
+.message {
+ width: 95%;
+}
+
+.timestamp {
+ font-size: 12px;
+}
+
+.me, .nick {
+ float: left;
+ width: 100px;
+}
+
+.me {
+ color: #F99;
+}
+
+.nick {
+ color: #99F;
+}
+
+.status {
+ background-color: #DDD;
+}
+
+form dt {
+ clear:both;
+ width:19%;
+ float:left;
+ text-align:right;
+}
+
+form dd {
+ float:left;
+ width:80%;
+ margin:0 0 0.5em 0.25em;
+}
+
+input {
+ width: 320px;
+}
\ No newline at end of file
Added: trunk/examples/jms/stomp-websockets/chat/chat.js
===================================================================
--- trunk/examples/jms/stomp-websockets/chat/chat.js (rev 0)
+++ trunk/examples/jms/stomp-websockets/chat/chat.js 2010-04-28 15:57:32 UTC (rev 9181)
@@ -0,0 +1,51 @@
+$(document).ready(function(){
+
+ var client, destination;
+
+ $('#connect_form').submit(function() {
+ var url = $("#connect_url").val();
+ var login = $("#connect_login").val();
+ var passcode = $("#connect_passcode").val();
+ destination = $("#destination").val();
+
+ client = Stomp.client(url);
+
+ // this allows to display debug logs directly on the web page
+ client.debug = function(str) {
+ $("#debug").append(str + "\n");
+ };
+ // the client is notified when it is connected to the server.
+ var onconnect = function(frame) {
+ debug("connected to Stomp");
+ $('#connect').fadeOut({ duration: 'fast' });
+ $('#disconnect').fadeIn();
+ $('#send_form_input').removeAttr('disabled');
+
+ client.subscribe(destination, function(message) {
+ $("#messages").append("<p>" + message.body + "</p>\n");
+ });
+ };
+ client.connect(login, passcode, onconnect);
+
+ return false;
+ });
+
+ $('#disconnect_form').submit(function() {
+ client.disconnect(function() {
+ $('#disconnect').fadeOut({ duration: 'fast' });
+ $('#connect').fadeIn();
+ $('#send_form_input').addAttr('disabled');
+ });
+ return false;
+ });
+
+ $('#send_form').submit(function() {
+ var text = $('#send_form_input').val();
+ if (text) {
+ client.send(destination, {foo: 1}, text);
+ $('#send_form_input').val("");
+ }
+ return false;
+ });
+
+});
\ No newline at end of file
Added: trunk/examples/jms/stomp-websockets/chat/index.html
===================================================================
--- trunk/examples/jms/stomp-websockets/chat/index.html (rev 0)
+++ trunk/examples/jms/stomp-websockets/chat/index.html 2010-04-28 15:57:32 UTC (rev 9181)
@@ -0,0 +1,54 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <title>Chat Example Using Stomp Over Web Sockets</title>
+ <link rel="stylesheet" href="chat.css" />
+ <script src='http://ajax.googleapis.com/ajax/libs/jquery/1.4/jquery.min.js'></script>
+ <script src='stomp.js'></script>
+ <script src='chat.js'></script>
+ <script>
+ $(document).ready(function() {
+ var supported = ("WebSocket" in window);
+ if(!supported) {
+ var msg = "Your browser does not support Web Sockets. This example will not work properly.<br>";
+ msg += "Please use a Web Browser with Web Sockets support (WebKit or Google Chrome).";
+ $("#connect").html(msg);
+ }
+ });
+ </script>
+ </head>
+ <body>
+
+ <div id='connect'>
+ <form id='connect_form'>
+ <dl>
+ <dt><label for=connect_url>Server URL</label></dt>
+ <dd><input name=url id='connect_url' value='ws://localhost:61614/stomp'></dd>
+ <dt><label for=connect_login>Login</label></dt>
+ <dd><input id='connect_login' placeholder="User Login" value="guest"></dd>
+ <dt><label for=connect_passcode>Password</label></dt>
+ <dd><input id='connect_passcode' type=password placeholder="User Password" value="guest"></dd>
+ <dt><label for=destination>Destination</label></dt>
+ <dd><input id='destination' placeholder="Destination" value="jms.topic.chat"></dd>
+ <dt> </dt>
+ <dd><input type=submit id='connect_submit' value="Connect"></dd>
+ </dl>
+ </form>
+
+ <p>Use the form above to connect to the Stomp server and subscribe to the destination.</p>
+ <p>Once connected, you can send messages to the destination with the text field at the bottom of this page</p>
+ </div>
+ <div id="disconnect">
+ <form id='disconnect_form'>
+ <input type=submit id='disconnect_submit' value="Disconnect">
+ </form>
+ </div>
+ <pre id="debug"></pre>
+ <div id="messages">
+ </div>
+ <form id='send_form'>
+ <input id='send_form_input' placeholder="Type your message here" disabled />
+ </form>
+ </body>
+</html>
+
Added: trunk/examples/jms/stomp-websockets/chat/stomp.js
===================================================================
--- trunk/examples/jms/stomp-websockets/chat/stomp.js (rev 0)
+++ trunk/examples/jms/stomp-websockets/chat/stomp.js 2010-04-28 15:57:32 UTC (rev 9181)
@@ -0,0 +1,185 @@
+// (c) 2010 Jeff Mesnil -- http://jmesnil.net/
+
+(function(window) {
+
+ var Stomp = {};
+
+ Stomp.frame = function(command, headers, body) {
+ return {
+ command: command,
+ headers: headers,
+ body: body,
+ toString: function() {
+ var out = command + '\n';
+ if (headers) {
+ for (header in headers) {
+ if(headers.hasOwnProperty(header)) {
+ out = out + header + ': ' + headers[header] + '\n';
+ }
+ }
+ }
+ out = out + '\n';
+ if (body) {
+ out = out + body;
+ }
+ return out;
+ }
+ }
+ };
+
+ trim = function(str) {
+ return str.replace(/^\s+/g,'').replace(/\s+$/g,'');
+ };
+
+ Stomp.unmarshal = function(data) {
+ var divider = data.search(/\n\n/),
+ headerLines = data.substring(0, divider).split('\n'),
+ command = headerLines.shift(),
+ headers = {},
+ body = '';
+
+ // Parse headers
+ var line = idx = null;
+ for (var i = 0; i < headerLines.length; i++) {
+ line = headerLines[i];
+ idx = line.indexOf(':');
+ headers[trim(line.substring(0, idx))] = trim(line.substring(idx + 1));
+ }
+
+ // Parse body, stopping at the first \0 found.
+ // TODO: Add support for content-length header.
+ var chr = null;
+ for (var i = divider + 2; i < data.length; i++) {
+ chr = data.charAt(i);
+ if (chr === '\0') {
+ break;
+ }
+ body += chr;
+ }
+
+ return Stomp.frame(command, headers, body);
+ };
+
+ Stomp.marshal = function(command, headers, body) {
+ return Stomp.frame(command, headers, body).toString() + '\0';
+ };
+
+ Stomp.client = function (url){
+
+ var that, ws, login, passcode;
+ var counter = 0; // used to index subscribers
+ // subscription callbacks indexed by subscriber's ID
+ var subscriptions = {};
+
+ debug = function(str) {
+ if (that.debug) {
+ that.debug(str);
+ }
+ };
+
+ onmessage = function(evt) {
+ debug('<<< ' + evt.data);
+ var frame = Stomp.unmarshal(evt.data);
+ if (frame.command === "CONNECTED" && that.connectCallback) {
+ that.connectCallback(frame);
+ } else if (frame.command === "MESSAGE") {
+ var onreceive = subscriptions[frame.headers.subscription];
+ if (onreceive) {
+ onreceive(frame);
+ }
+ } else if (frame.command === "RECEIPT" && that.onreceipt) {
+ that.onreceipt(frame);
+ } else if (frame.command === "ERROR" && that.onerror) {
+ that.onerror(frame);
+ }
+ };
+
+ transmit = function(command, headers, body) {
+ var out = Stomp.marshal(command, headers, body);
+ debug(">>> " + out);
+ ws.send(out);
+ };
+
+ that = {};
+
+ that.connect = function(login_, passcode_, connectCallback, errorCallback) {
+ debug("Opening Web Socket...");
+ ws = new WebSocket(url);
+ ws.onmessage = onmessage;
+ ws.onclose = function() {
+ var msg = "Whoops! Lost connection to " + url;
+ debug(msg);
+ if (errorCallback) {
+ errorCallback(msg);
+ }
+ };
+ ws.onopen = function() {
+ debug('Web Socket Opened...');
+ transmit("CONNECT", {login: login, passcode: passcode});
+ // connectCallback handler will be called from onmessage when a CONNECTED frame is received
+ };
+ login = login_;
+ passcode = passcode_;
+ that.connectCallback = connectCallback;
+ };
+
+ that.disconnect = function(disconnectCallback) {
+ transmit("DISCONNECT");
+ ws.close();
+ if (disconnectCallback) {
+ disconnectCallback();
+ }
+ };
+
+ that.send = function(destination, headers, body) {
+ var headers = headers || {};
+ headers.destination = destination;
+ transmit("SEND", headers, body);
+ };
+
+ that.subscribe = function(destination, callback, headers) {
+ var headers = headers || {};
+ var id = "sub-" + counter++;
+ headers.destination = destination;
+ headers.id = id;
+ subscriptions[id] = callback;
+ transmit("SUBSCRIBE", headers);
+ return id;
+ };
+
+ that.unsubscribe = function(id, headers) {
+ var headers = headers || {};
+ headers.id = id;
+ delete subscriptions[id];
+ transmit("UNSUBSCRIBE", headers);
+ };
+
+ that.begin = function(transaction, headers) {
+ var headers = headers || {};
+ headers.transaction = transaction;
+ transmit("BEGIN", headers);
+ };
+
+ that.commit = function(transaction, headers) {
+ var headers = headers || {};
+ headers.transaction = transaction;
+ transmit("COMMIT", headers);
+ };
+
+ that.abort = function(transaction, headers) {
+ var headers = headers || {};
+ headers.transaction = transaction;
+ transmit("ABORT", headers);
+ };
+
+ that.ack = function(message_id, headers) {
+ var headers = headers || {};
+ headers["message-id"] = message_id;
+ transmit("ACK", headers);
+ };
+ return that;
+ };
+
+ window.Stomp = Stomp;
+
+})(window);
Added: trunk/examples/jms/stomp-websockets/readme.html
===================================================================
--- trunk/examples/jms/stomp-websockets/readme.html (rev 0)
+++ trunk/examples/jms/stomp-websockets/readme.html 2010-04-28 15:57:32 UTC (rev 9181)
@@ -0,0 +1,73 @@
+<html>
+ <head>
+ <title>HornetQ Stomp WebSockets Example</title>
+ <link rel="stylesheet" type="text/css" href="../../common/common.css" />
+ <link rel="stylesheet" type="text/css" href="../../common/prettify.css" />
+ <script src="../../common/prettify.js"></script>
+ <script src='http://ajax.googleapis.com/ajax/libs/jquery/1.4/jquery.min.js'></script>
+ <script>
+ $(document).ready(function() {
+ $("#live-web-sockets").html(supports("WebSocket" in window, "Web Sockets"));
+
+ function supports(bool, suffix) {
+ var s = "Your browser ";
+ if (bool) {
+ s += "supports " + suffix + ".";
+ } else {
+ s += "does not support " + suffix + ". :(";
+ }
+ return s;
+ }
+ });
+ </script>
+ </head>
+ <body onload="prettyPrint()">
+ <h1>Stomp WebSockets Example</h1>
+
+ <p>This example shows you how to configure HornetQ to send and receive Stomp messages from modern web browser using Web Sockets.</p>
+
+ <p>At the moment, WebKit and Google Chrome are the only web browsers with Web Sockets support.</p>
+ <p><span id=live-web-sockets></span></p>
+
+ <h2>Example Setup</h2>
+ <p>The example will start a HornetQ server configured with Stomp over Web Sockets and JMS. Web browsers clients and
+ Java application will exchange message using a JMS Topic.</p>
+
+ <h2>Example step-by-step</h2>
+ <p>To run the example, you need to start HornetQ server from the <code>bin</code> directory and specify this example's
+ server configuration:</p>
+ <pre class="prettyprint">
+$ ./run.sh ../examples/jms/stomp-websockets/server0
+...
+[main] 17:45:03,498 INFO [org.hornetq.core.remoting.impl.netty.NettyAcceptor] Started Netty Acceptor version 3.2.0.BETA1-r2215 localhost:61614 for STOMP_WS protocol
+[main] 17:45:03,505 INFO [org.hornetq.core.server.impl.HornetQServerImpl] HornetQ Server version 2.1.0.BETA3 (Hungry Hornet, 117) started
+ </pre>
+
+ <p>To publish a message to a JMS topic from a Java application, simply type <code>./build.sh</code>
+ (or <code>build.bat</code> on windows) from this directory:</p>
+ <pre class="prettyprint">
+$ ./build.sh
+...
+[java] Sent message: message sent from a Java application at Wed Apr 28 17:45:53 CEST 2010
+[java] Received message: message sent from a Java application at Wed Apr 28 17:45:53 CEST 2010
+[java] example complete
+[java]
+[java] #####################
+[java] ### SUCCESS! ###
+[java] #####################
+ </pre>
+
+ <p>To subscribe to the topic from your web browser, open the <a href="chat/index.html">Chat Example</a> from another tab.
+ The chat example is preconfigured to connect to the HornetQ server with the URL <code>ws://localhost:61614/stomp</code> and subscribe to the JMS Topic (through its core address
+ <code>jms.topic.chat</code>).
+ </p>
+ <p>You can open as many Web clients as you want and they will all exchange messages through the topic</p>
+ <p>If you run again the Java application (with <code>./build.sh</code>), the web clients will also receive its message</p>
+
+ <h2>Documentation</h2>
+
+ <p>A JavaScript library is used on the browser side to be able to use Stomp Over Web Sockets (please see its <a href="http://jmesnil.net/stomp-websocket/doc/">documentation</a>
+ for a complete description).
+
+ </body>
+</html>
\ No newline at end of file
Added: trunk/examples/jms/stomp-websockets/server0/client-jndi.properties
===================================================================
--- trunk/examples/jms/stomp-websockets/server0/client-jndi.properties (rev 0)
+++ trunk/examples/jms/stomp-websockets/server0/client-jndi.properties 2010-04-28 15:57:32 UTC (rev 9181)
@@ -0,0 +1,3 @@
+java.naming.factory.initial=org.jnp.interfaces.NamingContextFactory
+java.naming.provider.url=jnp://localhost:1099
+java.naming.factory.url.pkgs=org.jboss.naming:org.jnp.interfaces
Added: trunk/examples/jms/stomp-websockets/server0/hornetq-beans.xml
===================================================================
--- trunk/examples/jms/stomp-websockets/server0/hornetq-beans.xml (rev 0)
+++ trunk/examples/jms/stomp-websockets/server0/hornetq-beans.xml 2010-04-28 15:57:32 UTC (rev 9181)
@@ -0,0 +1,59 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<deployment xmlns="urn:jboss:bean-deployer:2.0">
+
+ <bean name="Naming" class="org.jnp.server.NamingBeanImpl"/>
+
+ <!-- JNDI server. Disable this if you don't want JNDI -->
+ <bean name="JNDIServer" class="org.jnp.server.Main">
+ <property name="namingInfo">
+ <inject bean="Naming"/>
+ </property>
+ <property name="port">1099</property>
+ <property name="bindAddress">localhost</property>
+ <property name="rmiPort">1098</property>
+ <property name="rmiBindAddress">localhost</property>
+ </bean>
+
+ <!-- MBean server -->
+ <bean name="MBeanServer" class="javax.management.MBeanServer">
+ <constructor factoryClass="java.lang.management.ManagementFactory"
+ factoryMethod="getPlatformMBeanServer"/>
+ </bean>
+
+ <!-- The core configuration -->
+ <bean name="Configuration" class="org.hornetq.core.config.impl.FileConfiguration"/>
+
+ <!-- The security manager -->
+ <bean name="HornetQSecurityManager" class="org.hornetq.spi.core.security.HornetQSecurityManagerImpl">
+ <start ignored="true"/>
+ <stop ignored="true"/>
+ </bean>
+
+ <!-- The core server -->
+ <bean name="HornetQServer" class="org.hornetq.core.server.impl.HornetQServerImpl">
+ <constructor>
+ <parameter>
+ <inject bean="Configuration"/>
+ </parameter>
+ <parameter>
+ <inject bean="MBeanServer"/>
+ </parameter>
+ <parameter>
+ <inject bean="HornetQSecurityManager"/>
+ </parameter>
+ </constructor>
+ <start ignored="true"/>
+ <stop ignored="true"/>
+ </bean>
+
+ <!-- The JMS server -->
+ <bean name="JMSServerManager" class="org.hornetq.jms.server.impl.JMSServerManagerImpl">
+ <constructor>
+ <parameter>
+ <inject bean="HornetQServer"/>
+ </parameter>
+ </constructor>
+ </bean>
+
+</deployment>
Added: trunk/examples/jms/stomp-websockets/server0/hornetq-configuration.xml
===================================================================
--- trunk/examples/jms/stomp-websockets/server0/hornetq-configuration.xml (rev 0)
+++ trunk/examples/jms/stomp-websockets/server0/hornetq-configuration.xml 2010-04-28 15:57:32 UTC (rev 9181)
@@ -0,0 +1,42 @@
+<configuration xmlns="urn:hornetq"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="urn:hornetq /schema/hornetq-configuration.xsd">
+
+ <!-- Connectors -->
+
+ <connectors>
+ <connector name="netty-connector">
+ <factory-class>org.hornetq.core.remoting.impl.netty.NettyConnectorFactory</factory-class>
+ </connector>
+ </connectors>
+
+ <!-- Acceptors -->
+ <acceptors>
+ <!-- a regular Netty acceptor used by the JMS client -->
+ <acceptor name="netty-acceptor">
+ <factory-class>org.hornetq.core.remoting.impl.netty.NettyAcceptorFactory</factory-class>
+ </acceptor>
+ <!-- the stomp-acceptor is configured for the Stomp over Web Sockets and -->
+ <!-- will listen on port 61614) -->
+ <acceptor name="stomp-websocket">
+ <factory-class>org.hornetq.core.remoting.impl.netty.NettyAcceptorFactory</factory-class>
+ <param key="protocol" value="stomp_ws" />
+ <param key="port" value="61614" />
+ </acceptor>
+ </acceptors>
+
+ <!-- Other config -->
+
+ <security-settings>
+ <!--security for example queue-->
+ <security-setting match="jms.topic.chat">
+ <permission type="createDurableQueue" roles="guest"/>
+ <permission type="deleteDurableQueue" roles="guest"/>
+ <permission type="createNonDurableQueue" roles="guest"/>
+ <permission type="deleteNonDurableQueue" roles="guest"/>
+ <permission type="consume" roles="guest"/>
+ <permission type="send" roles="guest"/>
+ </security-setting>
+ </security-settings>
+
+</configuration>
Added: trunk/examples/jms/stomp-websockets/server0/hornetq-jms.xml
===================================================================
--- trunk/examples/jms/stomp-websockets/server0/hornetq-jms.xml (rev 0)
+++ trunk/examples/jms/stomp-websockets/server0/hornetq-jms.xml 2010-04-28 15:57:32 UTC (rev 9181)
@@ -0,0 +1,19 @@
+<configuration xmlns="urn:hornetq"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="urn:hornetq /schema/hornetq-jms.xsd">
+ <!--the connection factory used by the example-->
+ <connection-factory name="ConnectionFactory">
+ <connectors>
+ <connector-ref connector-name="netty-connector"/>
+ </connectors>
+ <entries>
+ <entry name="ConnectionFactory"/>
+ </entries>
+ </connection-factory>
+
+ <!--the topic used by the example-->
+ <topic name="chat">
+ <entry name="/topic/chat"/>
+ </topic>
+
+</configuration>
Added: trunk/examples/jms/stomp-websockets/server0/hornetq-users.xml
===================================================================
--- trunk/examples/jms/stomp-websockets/server0/hornetq-users.xml (rev 0)
+++ trunk/examples/jms/stomp-websockets/server0/hornetq-users.xml 2010-04-28 15:57:32 UTC (rev 9181)
@@ -0,0 +1,7 @@
+<configuration xmlns="urn:hornetq" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="urn:hornetq /schema/hornetq-users.xsd">
+ <!-- the default user. this is used where username is null-->
+ <defaultuser name="guest" password="guest">
+ <role name="guest"/>
+ </defaultuser>
+</configuration>
\ No newline at end of file
Added: trunk/examples/jms/stomp-websockets/server0/jndi.properties
===================================================================
--- trunk/examples/jms/stomp-websockets/server0/jndi.properties (rev 0)
+++ trunk/examples/jms/stomp-websockets/server0/jndi.properties 2010-04-28 15:57:32 UTC (rev 9181)
@@ -0,0 +1,2 @@
+java.naming.factory.initial=org.jnp.interfaces.NamingContextFactory
+java.naming.factory.url.pkgs=org.jboss.naming:org.jnp.interfaces
Added: trunk/examples/jms/stomp-websockets/server0/logging.properties
===================================================================
--- trunk/examples/jms/stomp-websockets/server0/logging.properties (rev 0)
+++ trunk/examples/jms/stomp-websockets/server0/logging.properties 2010-04-28 15:57:32 UTC (rev 9181)
@@ -0,0 +1,34 @@
+############################################################
+# Default Logging Configuration File
+#
+# You can use a different file by specifying a filename
+# with the java.util.logging.config.file system property.
+# For example java -Djava.util.logging.config.file=myfile
+############################################################
+
+############################################################
+# Global properties
+############################################################
+
+# "handlers" specifies a comma separated list of log Handler
+# classes. These handlers will be installed during VM startup.
+# Note that these classes must be on the system classpath.
+# By default we only configure a ConsoleHandler, which will only
+# show messages at the INFO and above levels.
+handlers=java.util.logging.ConsoleHandler,java.util.logging.FileHandler
+java.util.logging.ConsoleHandler.formatter=org.hornetq.integration.logging.HornetQLoggerFormatter
+java.util.logging.FileHandler.level=INFO
+java.util.logging.FileHandler.formatter=org.hornetq.integration.logging.HornetQLoggerFormatter
+java.util.logging.FileHandler.pattern=../logs/hornetq.log
+# Default global logging level.
+# This specifies which kinds of events are logged across
+# all loggers. For any given facility this global level
+# can be overriden by a facility specific level
+# Note that the ConsoleHandler also has a separate level
+# setting to limit messages printed to the console.
+.level= INFO
+
+############################################################
+# Handler specific properties.
+# Describes specific configuration info for Handlers.
+############################################################
Added: trunk/examples/jms/stomp-websockets/src/org/hornetq/jms/example/StompWebSocketExample.java
===================================================================
--- trunk/examples/jms/stomp-websockets/src/org/hornetq/jms/example/StompWebSocketExample.java (rev 0)
+++ trunk/examples/jms/stomp-websockets/src/org/hornetq/jms/example/StompWebSocketExample.java 2010-04-28 15:57:32 UTC (rev 9181)
@@ -0,0 +1,89 @@
+/*
+ * Copyright 2010 Red Hat, Inc.
+ * Red Hat licenses this file to you under the Apache License, version
+ * 2.0 (the "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ * implied. See the License for the specific language governing
+ * permissions and limitations under the License.
+ */
+package org.hornetq.jms.example;
+
+import java.util.Date;
+
+import javax.jms.BytesMessage;
+import javax.jms.Connection;
+import javax.jms.ConnectionFactory;
+import javax.jms.MessageConsumer;
+import javax.jms.MessageProducer;
+import javax.jms.Session;
+import javax.jms.TextMessage;
+import javax.jms.Topic;
+import javax.naming.InitialContext;
+
+import org.hornetq.common.example.HornetQExample;
+
+/**
+ * An example where a client will send a JMS message to a Topic.
+ * Browser clients connected using Web Sockets will be able to receive the message.
+ *
+ * @author <a href="mailto:jmesnil at redhat.com">Jeff Mesnil</a>
+ */
+public class StompWebSocketExample extends HornetQExample
+{
+ public static void main(final String[] args)
+ {
+ new StompWebSocketExample().run(args);
+ }
+
+ @Override
+ public boolean runExample() throws Exception
+ {
+ Connection connection = null;
+ InitialContext initialContext = null;
+ try
+ {
+ initialContext = getContext(0);
+ Topic topic = (Topic)initialContext.lookup("/topic/chat");
+ ConnectionFactory cf = (ConnectionFactory)initialContext.lookup("/ConnectionFactory");
+ connection = cf.createConnection();
+ Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
+
+ MessageProducer producer = session.createProducer(topic);
+ MessageConsumer consumer = session.createConsumer(topic);
+
+ // use JMS bytes message with UTF-8 String to send a text to Stomp clients
+ String text = "message sent from a Java application at " + new Date();
+ BytesMessage message = session.createBytesMessage();
+ message.writeBytes(text.getBytes("UTF-8"));
+ System.out.println("Sent message: " + text);
+
+ producer.send(message);
+
+ connection.start();
+
+ message = (BytesMessage)consumer.receive();
+ byte[] data = new byte[1024];
+ int size = message.readBytes(data);
+ text = new String(data, 0, size, "UTF-8");
+ System.out.println("Received message: " + text);
+
+ return true;
+ }
+ finally
+ {
+ if (connection != null)
+ {
+ connection.close();
+ }
+
+ if (initialContext != null)
+ {
+ initialContext.close();
+ }
+ }
+ }
+}
\ No newline at end of file
Modified: trunk/src/main/org/hornetq/core/protocol/stomp/StompFrame.java
===================================================================
--- trunk/src/main/org/hornetq/core/protocol/stomp/StompFrame.java 2010-04-28 08:54:19 UTC (rev 9180)
+++ trunk/src/main/org/hornetq/core/protocol/stomp/StompFrame.java 2010-04-28 15:57:32 UTC (rev 9181)
@@ -18,6 +18,7 @@
package org.hornetq.core.protocol.stomp;
import java.util.Map;
+import java.util.Map.Entry;
import org.hornetq.api.core.HornetQBuffer;
import org.hornetq.api.core.HornetQBuffers;
@@ -82,6 +83,18 @@
{
return "StompFrame[command=" + command + ", headers=" + headers + ", content-length=" + content.length + "]";
}
+
+ public String asString()
+ {
+ String out = command + '\n';
+ for (Entry<String, Object> header : headers.entrySet())
+ {
+ out += header.getKey() + ": " + header.getValue() + '\n';
+ }
+ out += '\n';
+ out += new String(content);
+ return out;
+ }
public HornetQBuffer toHornetQBuffer() throws Exception
{
Added: trunk/src/main/org/hornetq/core/protocol/stomp/WebSocketServerHandler.java
===================================================================
--- trunk/src/main/org/hornetq/core/protocol/stomp/WebSocketServerHandler.java (rev 0)
+++ trunk/src/main/org/hornetq/core/protocol/stomp/WebSocketServerHandler.java 2010-04-28 15:57:32 UTC (rev 9181)
@@ -0,0 +1,135 @@
+/*
+ * Copyright 2010 Red Hat, Inc.
+ *
+ * Red Hat licenses this file to you under the Apache License, version 2.0
+ * (the "License"); you may not use this file except in compliance with the
+ * License. You may obtain a copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+package org.hornetq.core.protocol.stomp;
+
+import org.jboss.netty.buffer.ChannelBuffers;
+import org.jboss.netty.channel.ChannelFuture;
+import org.jboss.netty.channel.ChannelFutureListener;
+import org.jboss.netty.channel.ChannelHandlerContext;
+import org.jboss.netty.channel.ChannelPipeline;
+import org.jboss.netty.channel.Channels;
+import org.jboss.netty.channel.ExceptionEvent;
+import org.jboss.netty.channel.MessageEvent;
+import org.jboss.netty.channel.SimpleChannelUpstreamHandler;
+import org.jboss.netty.handler.codec.http.DefaultHttpResponse;
+import org.jboss.netty.handler.codec.http.HttpHeaders;
+import org.jboss.netty.handler.codec.http.HttpMethod;
+import org.jboss.netty.handler.codec.http.HttpRequest;
+import org.jboss.netty.handler.codec.http.HttpResponse;
+import org.jboss.netty.handler.codec.http.HttpResponseStatus;
+import org.jboss.netty.handler.codec.http.HttpVersion;
+import org.jboss.netty.handler.codec.http.websocket.WebSocketFrame;
+import org.jboss.netty.handler.codec.http.websocket.WebSocketFrameDecoder;
+import org.jboss.netty.util.CharsetUtil;
+
+/**
+ * @author The Netty Project (netty-dev at lists.jboss.org)
+ * @author Trustin Lee (trustin at gmail.com)
+ *
+ * @version $Rev$, $Date$
+ */
+public class WebSocketServerHandler extends SimpleChannelUpstreamHandler {
+
+ private static final String WEBSOCKET_PATH = "/stomp";
+
+ @Override
+ public void messageReceived(ChannelHandlerContext ctx, MessageEvent e) throws Exception {
+ Object msg = e.getMessage();
+ if (msg instanceof HttpRequest) {
+ handleHttpRequest(ctx, (HttpRequest) msg);
+ } else if (msg instanceof WebSocketFrame) {
+ handleWebSocketFrame(ctx, (WebSocketFrame) msg);
+ }
+ }
+
+ private void handleHttpRequest(ChannelHandlerContext ctx, HttpRequest req) {
+ // Allow only GET methods.
+ if (req.getMethod() != HttpMethod.GET) {
+ sendHttpResponse(
+ ctx, req, new DefaultHttpResponse(
+ HttpVersion.HTTP_1_1, HttpResponseStatus.FORBIDDEN));
+ return;
+ }
+
+ // Serve the WebSocket handshake request.
+ if (req.getUri().equals(WEBSOCKET_PATH) &&
+ HttpHeaders.Values.UPGRADE.equalsIgnoreCase(req.getHeader(HttpHeaders.Names.CONNECTION)) &&
+ HttpHeaders.Values.WEBSOCKET.equalsIgnoreCase(req.getHeader(HttpHeaders.Names.UPGRADE))) {
+
+ // Create the WebSocket handshake response.
+ HttpResponse res = new DefaultHttpResponse(
+ HttpVersion.HTTP_1_1,
+ new HttpResponseStatus(101, "Web Socket Protocol Handshake"));
+ res.addHeader(HttpHeaders.Names.UPGRADE, HttpHeaders.Values.WEBSOCKET);
+ res.addHeader(HttpHeaders.Names.CONNECTION, HttpHeaders.Values.UPGRADE);
+ res.addHeader(HttpHeaders.Names.WEBSOCKET_ORIGIN, req.getHeader(HttpHeaders.Names.ORIGIN));
+ res.addHeader(HttpHeaders.Names.WEBSOCKET_LOCATION, getWebSocketLocation(req));
+ String protocol = req.getHeader(HttpHeaders.Names.WEBSOCKET_PROTOCOL);
+ if (protocol != null) {
+ res.addHeader(HttpHeaders.Names.WEBSOCKET_PROTOCOL, protocol);
+ }
+
+ // Upgrade the connection and send the handshake response.
+ ChannelPipeline p = ctx.getChannel().getPipeline();
+ p.remove("http-aggregator");
+ p.replace("http-decoder", "ws-decoder", new WebSocketFrameDecoder());
+
+ ctx.getChannel().write(res);
+
+ p.replace("http-encoder", "ws-encoder", new WebSocketStompFrameEncoder());
+ return;
+ }
+
+ // Send an error page otherwise.
+ sendHttpResponse(
+ ctx, req, new DefaultHttpResponse(
+ HttpVersion.HTTP_1_1, HttpResponseStatus.FORBIDDEN));
+ }
+
+ private void handleWebSocketFrame(ChannelHandlerContext ctx, WebSocketFrame frame) {
+ // Send the uppercased string back.
+ Channels.fireMessageReceived(ctx, frame.getBinaryData());
+ }
+
+ private void sendHttpResponse(ChannelHandlerContext ctx, HttpRequest req, HttpResponse res) {
+ // Generate an error page if response status code is not OK (200).
+ if (res.getStatus().getCode() != 200) {
+ res.setContent(
+ ChannelBuffers.copiedBuffer(
+ res.getStatus().toString(), CharsetUtil.UTF_8));
+ res.setHeader(
+ HttpHeaders.Names.CONTENT_LENGTH,
+ Integer.toString(res.getContent().readableBytes()));
+ }
+
+ // Send the response and close the connection if necessary.
+ ChannelFuture f = ctx.getChannel().write(res);
+ if (!HttpHeaders.isKeepAlive(req) || res.getStatus().getCode() != 200) {
+ f.addListener(ChannelFutureListener.CLOSE);
+ }
+ }
+
+ @Override
+ public void exceptionCaught(ChannelHandlerContext ctx, ExceptionEvent e)
+ throws Exception {
+ e.getCause().printStackTrace();
+ e.getChannel().close();
+ }
+
+ private String getWebSocketLocation(HttpRequest req) {
+ return "ws://" + req.getHeader(HttpHeaders.Names.HOST) + WEBSOCKET_PATH;
+ }
+}
\ No newline at end of file
Added: trunk/src/main/org/hornetq/core/protocol/stomp/WebSocketStompFrameEncoder.java
===================================================================
--- trunk/src/main/org/hornetq/core/protocol/stomp/WebSocketStompFrameEncoder.java (rev 0)
+++ trunk/src/main/org/hornetq/core/protocol/stomp/WebSocketStompFrameEncoder.java 2010-04-28 15:57:32 UTC (rev 9181)
@@ -0,0 +1,72 @@
+/*
+ * Copyright 2010 Red Hat, Inc.
+ *
+ * Red Hat licenses this file to you under the Apache License, version 2.0
+ * (the "License"); you may not use this file except in compliance with the
+ * License. You may obtain a copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+package org.hornetq.core.protocol.stomp;
+
+import org.hornetq.api.core.HornetQBuffer;
+import org.hornetq.core.buffers.impl.ChannelBufferWrapper;
+import org.jboss.netty.buffer.ChannelBuffer;
+import org.jboss.netty.channel.Channel;
+import org.jboss.netty.channel.ChannelHandlerContext;
+import org.jboss.netty.handler.codec.http.websocket.DefaultWebSocketFrame;
+import org.jboss.netty.handler.codec.http.websocket.WebSocketFrame;
+import org.jboss.netty.handler.codec.oneone.OneToOneEncoder;
+
+/**
+ * Encodes a {@link WebSocketFrame} into a {@link ChannelBuffer}.
+ * <p>
+ * For the detailed instruction on adding add Web Socket support to your HTTP
+* server, take a look into the <tt>WebSocketServer</tt> example located in the
+ * {@code org.jboss.netty.example.http.websocket} package.
+ *
+ * @author The Netty Project (netty-dev at lists.jboss.org)
+ * @author Mike Heath (mheath at apache.org)
+ * @author Trustin Lee (trustin at gmail.com)
+ * @version $Rev: 2019 $, $Date: 2010-01-09 21:00:24 +0900 (Sat, 09 Jan 2010) $
+ */
+public class WebSocketStompFrameEncoder extends OneToOneEncoder
+{
+
+ private final StompFrameDecoder decoder = new StompFrameDecoder();
+
+ @Override
+ protected Object encode(ChannelHandlerContext ctx, Channel channel, Object msg) throws Exception
+ {
+
+ if (msg instanceof ChannelBuffer)
+ {
+ // this is ugly and slow!
+ // we have to go ChannelBuffer -> HornetQBuffer -> StompFrame -> String -> WebSocketFrame
+ // since HornetQ protocol SPI requires to return HornetQBuffer to the transport
+ HornetQBuffer buffer = new ChannelBufferWrapper((ChannelBuffer)msg);
+ StompFrame frame = decoder.decode(buffer);
+ if (frame != null)
+ {
+ WebSocketFrame wsFrame = new DefaultWebSocketFrame(frame.asString());
+
+ // Text frame
+ ChannelBuffer data = wsFrame.getBinaryData();
+ ChannelBuffer encoded = channel.getConfig().getBufferFactory().getBuffer(data.order(),
+ data.readableBytes() + 2);
+ encoded.writeByte((byte)wsFrame.getType());
+ encoded.writeBytes(data, data.readableBytes());
+ encoded.writeByte((byte)0xFF);
+ return encoded;
+
+ }
+ }
+ return msg;
+ }
+}
\ No newline at end of file
Modified: trunk/src/main/org/hornetq/core/remoting/impl/netty/NettyAcceptor.java
===================================================================
--- trunk/src/main/org/hornetq/core/remoting/impl/netty/NettyAcceptor.java 2010-04-28 08:54:19 UTC (rev 9180)
+++ trunk/src/main/org/hornetq/core/remoting/impl/netty/NettyAcceptor.java 2010-04-28 15:57:32 UTC (rev 9181)
@@ -35,6 +35,7 @@
import org.hornetq.api.core.TransportConfiguration;
import org.hornetq.api.core.management.NotificationType;
import org.hornetq.core.logging.Logger;
+import org.hornetq.core.protocol.stomp.WebSocketServerHandler;
import org.hornetq.core.remoting.impl.ssl.SSLSupport;
import org.hornetq.core.server.management.Notification;
import org.hornetq.core.server.management.NotificationService;
@@ -57,6 +58,7 @@
import org.jboss.netty.channel.ChannelPipeline;
import org.jboss.netty.channel.ChannelPipelineFactory;
import org.jboss.netty.channel.ChannelStateEvent;
+import org.jboss.netty.channel.DefaultChannelPipeline;
import org.jboss.netty.channel.StaticChannelPipeline;
import org.jboss.netty.channel.group.ChannelGroup;
import org.jboss.netty.channel.group.ChannelGroupFuture;
@@ -65,6 +67,7 @@
import org.jboss.netty.channel.local.LocalAddress;
import org.jboss.netty.channel.socket.nio.NioServerSocketChannelFactory;
import org.jboss.netty.channel.socket.oio.OioServerSocketChannelFactory;
+import org.jboss.netty.handler.codec.http.HttpChunkAggregator;
import org.jboss.netty.handler.codec.http.HttpRequestDecoder;
import org.jboss.netty.handler.codec.http.HttpResponseEncoder;
import org.jboss.netty.handler.ssl.SslHandler;
@@ -320,9 +323,15 @@
ChannelPipelineFactory factory = new ChannelPipelineFactory()
{
+ /**
+ * we use named handlers so that the web socket server handler can
+ * replace the http encode/decoder after the http handshake.
+ *
+ * @see WebSocketServerHandler#handleHttpRequest(ChannelHandlerContext, org.jboss.netty.handler.codec.http.HttpRequest)
+ */
public ChannelPipeline getPipeline() throws Exception
{
- List<ChannelHandler> handlers = new ArrayList<ChannelHandler>();
+ ChannelPipeline pipeline = new DefaultChannelPipeline();
if (sslEnabled)
{
@@ -332,32 +341,38 @@
SslHandler handler = new SslHandler(engine);
- handlers.add(handler);
+ pipeline.addLast("ssl", handler);
}
if (httpEnabled)
{
- handlers.add(new HttpRequestDecoder());
+ pipeline.addLast("http-decoder", new HttpRequestDecoder());
- handlers.add(new HttpResponseEncoder());
+ pipeline.addLast("http-encoder", new HttpResponseEncoder());
- handlers.add(new HttpAcceptorHandler(httpKeepAliveRunnable, httpResponseTime));
+ pipeline.addLast("http-handler", new HttpAcceptorHandler(httpKeepAliveRunnable, httpResponseTime));
}
if (protocol == ProtocolType.CORE)
{
// Core protocol uses it's own optimised decoder
- handlers.add(new HornetQFrameDecoder2());
+ pipeline.addLast("hornetq-decoder", new HornetQFrameDecoder2());
}
+ else if (protocol == ProtocolType.STOMP_WS)
+ {
+ pipeline.addLast("http-decoder", new HttpRequestDecoder());
+ pipeline.addLast("http-aggregator", new HttpChunkAggregator(65536));
+ pipeline.addLast("http-encoder", new HttpResponseEncoder());
+ pipeline.addLast("hornetq-decoder", new HornetQFrameDecoder(decoder));
+ pipeline.addLast("websocket-handler", new WebSocketServerHandler());
+ }
else
{
- handlers.add(new HornetQFrameDecoder(decoder));
+ pipeline.addLast("hornetq-decoder", new HornetQFrameDecoder(decoder));
}
- handlers.add(new HornetQServerChannelHandler(channelGroup, handler, new Listener()));
+ pipeline.addLast("handler", new HornetQServerChannelHandler(channelGroup, handler, new Listener()));
- ChannelPipeline pipeline = new StaticChannelPipeline(handlers.toArray(new ChannelHandler[handlers.size()]));
-
return pipeline;
}
};
@@ -411,7 +426,7 @@
batchFlusherFuture = scheduledThreadPool.scheduleWithFixedDelay(flusher, batchDelay, batchDelay, TimeUnit.MILLISECONDS);
}
- NettyAcceptor.log.info("Started Netty Acceptor version " + Version.ID + " " + host + ":" + port);
+ NettyAcceptor.log.info("Started Netty Acceptor version " + Version.ID + " " + host + ":" + port + " for " + protocol + " protocol");
}
private void startServerChannels()
Modified: trunk/src/main/org/hornetq/core/remoting/server/impl/RemotingServiceImpl.java
===================================================================
--- trunk/src/main/org/hornetq/core/remoting/server/impl/RemotingServiceImpl.java 2010-04-28 08:54:19 UTC (rev 9180)
+++ trunk/src/main/org/hornetq/core/remoting/server/impl/RemotingServiceImpl.java 2010-04-28 15:57:32 UTC (rev 9181)
@@ -130,8 +130,10 @@
this.protocolMap.put(ProtocolType.CORE, new CoreProtocolManagerFactory().createProtocolManager(server,
interceptors));
+ // difference between Stomp and Stomp over Web Sockets is handled in NettyAcceptor.getPipeline()
this.protocolMap.put(ProtocolType.STOMP, new StompProtocolManagerFactory().createProtocolManager(server,
interceptors));
+ this.protocolMap.put(ProtocolType.STOMP_WS, new StompProtocolManagerFactory().createProtocolManager(server, interceptors));
}
// RemotingService implementation -------------------------------
Modified: trunk/src/main/org/hornetq/spi/core/protocol/ProtocolType.java
===================================================================
--- trunk/src/main/org/hornetq/spi/core/protocol/ProtocolType.java 2010-04-28 08:54:19 UTC (rev 9180)
+++ trunk/src/main/org/hornetq/spi/core/protocol/ProtocolType.java 2010-04-28 15:57:32 UTC (rev 9181)
@@ -22,5 +22,5 @@
*/
public enum ProtocolType
{
- CORE, STOMP, AMQP, AARDVARK;
+ CORE, STOMP, STOMP_WS, AMQP, AARDVARK;
}
Added: trunk/tests/src/org/hornetq/tests/integration/stomp/StompWebSocketTest.java
===================================================================
--- trunk/tests/src/org/hornetq/tests/integration/stomp/StompWebSocketTest.java (rev 0)
+++ trunk/tests/src/org/hornetq/tests/integration/stomp/StompWebSocketTest.java 2010-04-28 15:57:32 UTC (rev 9181)
@@ -0,0 +1,93 @@
+/**
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.hornetq.tests.integration.stomp;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import junit.framework.TestCase;
+
+import org.hornetq.api.core.TransportConfiguration;
+import org.hornetq.core.config.Configuration;
+import org.hornetq.core.config.CoreQueueConfiguration;
+import org.hornetq.core.config.impl.ConfigurationImpl;
+import org.hornetq.core.logging.Logger;
+import org.hornetq.core.remoting.impl.invm.InVMAcceptorFactory;
+import org.hornetq.core.remoting.impl.netty.NettyAcceptorFactory;
+import org.hornetq.core.remoting.impl.netty.TransportConstants;
+import org.hornetq.core.server.HornetQServer;
+import org.hornetq.core.server.HornetQServers;
+import org.hornetq.jms.server.JMSServerManager;
+import org.hornetq.jms.server.config.JMSConfiguration;
+import org.hornetq.jms.server.config.impl.JMSConfigurationImpl;
+import org.hornetq.jms.server.impl.JMSServerManagerImpl;
+import org.hornetq.spi.core.protocol.ProtocolType;
+
+public class StompWebSocketTest extends TestCase {
+ private static final transient Logger log = Logger.getLogger(StompWebSocketTest.class);
+ private JMSServerManager server;
+
+ /**
+ * to test the Stomp over Web Sockets protocol,
+ * uncomment the sleep call and run the stomp-websockets Javascript test suite
+ * from http://github.com/jmesnil/stomp-websocket
+ */
+ public void testConnect() throws Exception {
+ //Thread.sleep(10000000);
+ }
+
+ // Implementation methods
+ //-------------------------------------------------------------------------
+ protected void setUp() throws Exception {
+ server = createServer();
+ server.start();
+ }
+
+ /**
+ * @return
+ * @throws Exception
+ */
+ private JMSServerManager createServer() throws Exception
+ {
+ Configuration config = new ConfigurationImpl();
+ config.setSecurityEnabled(false);
+ config.setPersistenceEnabled(false);
+
+ Map<String, Object> params = new HashMap<String, Object>();
+ params.put(TransportConstants.PROTOCOL_PROP_NAME, ProtocolType.STOMP_WS.toString());
+ params.put(TransportConstants.PORT_PROP_NAME, TransportConstants.DEFAULT_STOMP_PORT + 1);
+ TransportConfiguration stompTransport = new TransportConfiguration(NettyAcceptorFactory.class.getName(), params);
+ config.getAcceptorConfigurations().add(stompTransport);
+ config.getAcceptorConfigurations().add(new TransportConfiguration(InVMAcceptorFactory.class.getName()));
+ config.getQueueConfigurations().add(new CoreQueueConfiguration(getQueueName(), getQueueName(), null, false));
+ HornetQServer hornetQServer = HornetQServers.newHornetQServer(config);
+
+ JMSConfiguration jmsConfig = new JMSConfigurationImpl();
+ server = new JMSServerManagerImpl(hornetQServer, jmsConfig);
+ server.setContext(null);
+ return server;
+ }
+
+ protected void tearDown() throws Exception {
+ server.stop();
+ }
+
+ protected String getQueueName() {
+ return "/queue/test";
+ }
+}
More information about the hornetq-commits
mailing list