JBoss hornetq SVN: r9181 - in trunk: docs/user-manual/en and 15 other directories.
by do-not-reply@jboss.org
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 @@
+@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@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(a)lists.jboss.org)
+ * @author Trustin Lee (trustin(a)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(a)lists.jboss.org)
+ * @author Mike Heath (mheath(a)apache.org)
+ * @author Trustin Lee (trustin(a)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";
+ }
+}
14 years
JBoss hornetq SVN: r9180 - trunk/docs/quickstart-guide/en.
by do-not-reply@jboss.org
Author: jmesnil
Date: 2010-04-28 04:54:19 -0400 (Wed, 28 Apr 2010)
New Revision: 9180
Modified:
trunk/docs/quickstart-guide/en/installation.xml
Log:
https://jira.jboss.org/jira/browse/HORNETQ-375: Update quick start guide with new AS configuration directory layout
Modified: trunk/docs/quickstart-guide/en/installation.xml
===================================================================
--- trunk/docs/quickstart-guide/en/installation.xml 2010-04-27 23:21:28 UTC (rev 9179)
+++ trunk/docs/quickstart-guide/en/installation.xml 2010-04-28 08:54:19 UTC (rev 9180)
@@ -46,8 +46,9 @@
|___ bin
|
|___ config
- | |___ jboss-as
- | ` |___ stand-alone
+ | |___ jboss-as-4
+ | |___ jboss-as-5
+ | |___ stand-alone
|
|___ docs
| |___ api
@@ -71,7 +72,7 @@
</listitem>
<listitem>
<para><literal>config</literal> -- configuration files needed to configure HornetQ. This
- contains configurations to run HornetQ either in stand-alone or inside JBoss AS 5.
+ contains configurations to run HornetQ either in stand-alone or inside JBoss AS 4 and 5.
Please refer to the reference guide for details on configuration. </para>
</listitem>
<listitem>
@@ -109,8 +110,8 @@
directory where you installed JBoss AS 5</para>
</listitem>
<listitem>
- <para>run <literal>./build.sh as5</literal> (or <literal>build.bat as5</literal> if you are on
- Windows) in HornetQ's <literal>config/jboss-as</literal> directory</para>
+ <para>run <literal>./build.sh</literal> (or <literal>build.bat</literal> if you are on
+ Windows) in HornetQ <literal>config/jboss-as-5</literal> directory</para>
</listitem>
</orderedlist>
<para>This will create 2 new profiles in <literal>$JBOSS_HOME/server</literal>:</para>
@@ -143,8 +144,8 @@
directory where you installed JBoss AS 4</para>
</listitem>
<listitem>
- <para>run <literal><literal>./build.sh as4</literal> (or <literal>build.bat as4</literal> if you
- are on Windows)</literal> in HornetQ's <literal>config/jboss-as</literal>
+ <para>run <literal>./build.sh</literal> (or <literal>build.bat as4</literal> if you
+ are on Windows) in HornetQ <literal>config/jboss-as-4</literal>
directory</para>
</listitem>
</orderedlist>
14 years
JBoss hornetq SVN: r9179 - tags.
by do-not-reply@jboss.org
Author: clebert.suconic(a)jboss.com
Date: 2010-04-27 19:21:28 -0400 (Tue, 27 Apr 2010)
New Revision: 9179
Added:
tags/HornetQ_2_1_0_Beta3/
Log:
Beta3 release
Copied: tags/HornetQ_2_1_0_Beta3 (from rev 9178, trunk)
14 years
JBoss hornetq SVN: r9178 - trunk.
by do-not-reply@jboss.org
Author: clebert.suconic(a)jboss.com
Date: 2010-04-27 19:08:12 -0400 (Tue, 27 Apr 2010)
New Revision: 9178
Modified:
trunk/build-maven.xml
Log:
adding new repo to build-maven.xml
Modified: trunk/build-maven.xml
===================================================================
--- trunk/build-maven.xml 2010-04-27 21:26:22 UTC (rev 9177)
+++ trunk/build-maven.xml 2010-04-27 23:08:12 UTC (rev 9178)
@@ -195,9 +195,7 @@
</exec>
</target>
- <target name="updateMavenRepos">
- <fail unless="hornetq.repos" message="*** Please set the absolute path to hornetq.repos property i.e. -Dhornetq.repos=foo ***"/>
- <property name="dest.dir" value="${hornetq.repos}"/>
+ <target name="deploy">
<antcall target="deploy-jar">
@@ -257,8 +255,8 @@
<arg value="-Dversion=${hornetq.version}"/>
<arg value="-Dpackaging=jar"/>
<arg value="-Dfile=./build/jars/${artifact.id}.jar"/>
- <arg value="-DrepositoryId=jboss-releases"/>
- <arg value="-Durl=file:///${dest.dir}"/>
+ <arg value="-DrepositoryId=jboss-releases-repository"/>
+ <arg value="-Durl=https://repository.jboss.org/nexus/service/local/staging/deploy/maven2/"/>
</exec>
<!-- deploy the sources jar -->
<exec executable="mvn">
@@ -271,10 +269,12 @@
<arg value="-Dpackaging=jar"/>
<arg value="-Dclassifier=sources"/>
<arg value="-Dfile=./build/jars/${artifact.id}-sources.jar"/>
- <arg value="-DrepositoryId=jboss-releases"/>
- <arg value="-Durl=file:///${dest.dir}"/>
+ <arg value="-DrepositoryId=jboss-releases-repository"/>
+ <arg value="-Durl=https://repository.jboss.org/nexus/service/local/staging/deploy/maven2/"/>
</exec>
- <delete file="${temporary.pom}"/>
+
+ <delete file="${temporary.pom}"/>
+
</target>
</project>
14 years
JBoss hornetq SVN: r9177 - trunk/docs.
by do-not-reply@jboss.org
Author: clebert.suconic(a)jboss.com
Date: 2010-04-27 17:26:22 -0400 (Tue, 27 Apr 2010)
New Revision: 9177
Modified:
trunk/docs/README.html
Log:
Changing release
Modified: trunk/docs/README.html
===================================================================
--- trunk/docs/README.html 2010-04-27 18:29:50 UTC (rev 9176)
+++ trunk/docs/README.html 2010-04-27 21:26:22 UTC (rev 9177)
@@ -3,7 +3,7 @@
<head>
<meta content="text/html; charset=ISO-8859-1"
http-equiv="content-type">
- <title>HornetQ 2.1.0 BETA2 Release Notes</title>
+ <title>HornetQ 2.1.0 BETA3 Release Notes</title>
</head>
<body>
@@ -11,12 +11,12 @@
<br>
-<h2>16th April 2010</h2>
+<h2>27th April 2010</h2>
-These are the release notes for HornetQ 2.1.0 BETA2<br><br>
+These are the release notes for HornetQ 2.1.0 BETA3<br><br>
For full description of the contents please see the
-<a href="https://jira.jboss.org/jira/secure/ReleaseNote.jspa?version=12314776&styl...">HornetQ project JIRA</a>.<br><br>
+<a href="https://jira.jboss.org/jira/secure/ReleaseNote.jspa?version=12314823&styl...">HornetQ project JIRA</a>.<br><br>
This release is a feature complete release for forthcoming HornetQ 2.1.0<br>
14 years
JBoss hornetq SVN: r9176 - trunk/src/config/common.
by do-not-reply@jboss.org
Author: clebert.suconic(a)jboss.com
Date: 2010-04-27 14:29:50 -0400 (Tue, 27 Apr 2010)
New Revision: 9176
Modified:
trunk/src/config/common/hornetq-version.properties
Log:
Changing version.properties
Modified: trunk/src/config/common/hornetq-version.properties
===================================================================
--- trunk/src/config/common/hornetq-version.properties 2010-04-27 13:17:41 UTC (rev 9175)
+++ trunk/src/config/common/hornetq-version.properties 2010-04-27 18:29:50 UTC (rev 9176)
@@ -3,6 +3,6 @@
hornetq.version.minorVersion=1
hornetq.version.microVersion=0
hornetq.version.incrementingVersion=117
-hornetq.version.versionSuffix=CR1
-hornetq.version.versionTag=CR1
+hornetq.version.versionSuffix=BETA3
+hornetq.version.versionTag=BETA3
hornetq.netty.version=(a)NETTY.VERSION@
14 years
JBoss hornetq SVN: r9175 - trunk/docs/user-manual/en.
by do-not-reply@jboss.org
Author: jmesnil
Date: 2010-04-27 09:17:41 -0400 (Tue, 27 Apr 2010)
New Revision: 9175
Modified:
trunk/docs/user-manual/en/using-server.xml
Log:
doc typo
Modified: trunk/docs/user-manual/en/using-server.xml
===================================================================
--- trunk/docs/user-manual/en/using-server.xml 2010-04-27 12:05:17 UTC (rev 9174)
+++ trunk/docs/user-manual/en/using-server.xml 2010-04-27 13:17:41 UTC (rev 9175)
@@ -141,7 +141,7 @@
<para><literal>hornetq-users.xml</literal> HornetQ ships with a basic security
manager implementation which obtains user credentials from the <literal
>hornetq-users.xml</literal> file. This file contains user, password and
- role information. For more information on security ,please see <xref
+ role information. For more information on security, please see <xref
linkend="security"/>.</para>
</listitem>
<listitem>
14 years
JBoss hornetq SVN: r9174 - branches/HnetQ_323_cn/docs/user-manual/zh.
by do-not-reply@jboss.org
Author: gaohoward
Date: 2010-04-27 08:05:17 -0400 (Tue, 27 Apr 2010)
New Revision: 9174
Modified:
branches/HnetQ_323_cn/docs/user-manual/zh/client-reconnection.xml
branches/HnetQ_323_cn/docs/user-manual/zh/core-bridges.xml
Log:
done
Modified: branches/HnetQ_323_cn/docs/user-manual/zh/client-reconnection.xml
===================================================================
--- branches/HnetQ_323_cn/docs/user-manual/zh/client-reconnection.xml 2010-04-27 08:12:07 UTC (rev 9173)
+++ branches/HnetQ_323_cn/docs/user-manual/zh/client-reconnection.xml 2010-04-27 12:05:17 UTC (rev 9174)
@@ -78,7 +78,7 @@
值是<literal>2000</literal>毫秒。</para>
</listitem>
<listitem>
- <para><literal>reconnect-attempts</literal>。可先参数。它表示要进行多少重试后才放弃
+ <para><literal>reconnect-attempts</literal>。可选参数。它表示要进行多少重试后才放弃
并退出。<literal>-1</literal>表示进行无限次重试。默认值是<literal>0</literal>。</para>
</listitem>
</itemizedlist>
Modified: branches/HnetQ_323_cn/docs/user-manual/zh/core-bridges.xml
===================================================================
--- branches/HnetQ_323_cn/docs/user-manual/zh/core-bridges.xml 2010-04-27 08:12:07 UTC (rev 9173)
+++ branches/HnetQ_323_cn/docs/user-manual/zh/core-bridges.xml 2010-04-27 12:05:17 UTC (rev 9174)
@@ -17,35 +17,26 @@
<!-- permitted by applicable law. -->
<!-- ============================================================================= -->
<chapter id="core-bridges">
- <title>Core Bridges</title>
- <para>The function of a bridge is to consume messages from a source queue, and forward them to a
- target address, typically on a different HornetQ server.</para>
- <para>The source and target servers do not have to be in the same cluster which makes bridging
- suitable for reliably sending messages from one cluster to another, for instance across a
- WAN, or internet and where the connection may be unreliable.</para>
- <para>The bridge has built in resilience to failure so if the target server connection is lost,
- e.g. due to network failure, the bridge will retry connecting to the target until it comes
- back online. When it comes back online it will resume operation as normal.</para>
- <para>In summary, bridges are a way to reliably connect two separate HornetQ servers together.
- With a core bridge both source and target servers must be HornetQ servers.</para>
- <para>Bridges can be configured to provide <emphasis>once and only once</emphasis> delivery
- guarantees even in the event of the failure of the source or the target server. They do this
- by using duplicate detection (described in <xref linkend="duplicate-detection"/>).</para>
+ <title>核心桥</title>
+ <para>桥的功能是从一个源队列中接收消息,再将消息转发到目的地址。通常这个目的地址在另外一个HornetQ服务器中。</para>
+ <para>源与目的不需要在同一个集群中。所以桥很适合将消息从一个集群中可靠地转发到另一个集群。比如通过一个WAN,或
+ internet,等连接不稳定的网络。</para>
+ <para>桥有处理故障的能力。如果目的服务器的连接失败(像网络故障),桥会重试与目的服务器的连接,直接连接成功
+ 为止。当连接成功后,桥则继续进行工作。</para>
+ <para>总之,桥是可靠连接两个HornetQ服务器的一种手段。使用核心桥时源和目的服务器必须都是HornetQ服务器。</para>
+ <para>桥可以通过配置提供<emphasis>一次且只有一次</emphasis>的传递保证。其采用的方法是重复检测(详细
+ 描述在<xref linkend="duplicate-detection"/>)。</para>
<note>
- <para>Although they have similar function, don't confuse core bridges with JMS
- bridges!</para>
- <para>Core bridges are for linking a HornetQ node with another HornetQ node and do not use
- the JMS API. A JMS Bridge is used for linking any two JMS 1.1 compliant JMS providers.
- So, a JMS Bridge could be used for bridging to or from different JMS compliant messaging
- system. It's always preferable to use a core bridge if you can. Core bridges use
- duplicate detection to provide <emphasis>once and only once</emphasis> guarantees. To
- provide the same guarantee using a JMS bridge you would have to use XA which has a
- higher overhead and is more complex to configure.</para>
+ <para>核心桥的功能与JMS桥的功能相似,但是不能将它们混淆!</para>
+ <para>核心桥用来连接两个HornetQ节点,它不使用JMS接口。JMS桥使用的是JMS接口,它连接的是任何两个符合
+ JMS 1.1规范的服务器。因此,JMS桥可以将两个不同的JMS服务器连接起来。从性能角度考虑,核心桥由于采用
+ 重复检测来实现<emphasis>一次且只一次</emphasis>的传递保证,可以提供更高的性能。
+ JMS桥则需要使用XA这种复杂的机制来提供同样的传递保证,因些性能要比核心桥低。</para>
</note>
<section>
- <title>Configuring Bridges</title>
- <para>Bridges are configured in <literal>hornetq-configuration.xml</literal>. Let's kick off
- with an example (this is actually from the bridge example):</para>
+ <title>桥的配置</title>
+ <para>桥的配置在<literal>hornetq-configuration.xml</literal>文件中。让我们先看一个配置的例子
+ (它实际上出自bridge例子):</para>
<programlisting>
<bridge name="my-bridge">
<queue-name>jms.queue.sausage-factory</queue-name>
@@ -66,163 +57,122 @@
<password>foopassword</password>
</bridge>
</programlisting>
- <para>In the above example we have shown all the parameters its possible to configure for a
- bridge. In practice you might use many of the defaults so it won't be necessary to
- specify them all explicitly.</para>
- <para>Let's take a look at all the parameters in turn:</para>
+ <para>在上面的配置中包括了桥的所有参数。在实际应用中可能其中很多的参数可以使用默认值,不需要在配置中
+ 指定。</para>
+ <para>下面我们对每个参数分别说明:</para>
<itemizedlist>
<listitem>
- <para><literal>name</literal> attribute. All bridges must have a unique name in the
- server.</para>
+ <para><literal>name</literal>参数。所有桥都必须有一个唯一的名字。</para>
</listitem>
<listitem>
- <para><literal>queue-name</literal>. This is the unique name of the local queue that
- the bridge consumes from, it's a mandatory parameter.</para>
- <para>The queue must already exist by the time the bridge is instantiated at
- start-up.</para>
+ <para><literal>queue-name</literal>。本地队列的名字。桥从本地队列中接收消息。
+ 这是一个必要的参数。</para>
+ <para>这个队列在桥的启动之前必须已经存在。</para>
<note>
- <para>If you're using JMS then normally the JMS configuration <literal
- >hornetq-jms.xml</literal> is loaded after the core configuration file
- <literal>hornetq-configuration.xml</literal> is loaded. If your bridge
- is consuming from a JMS queue then you'll need to make sure the JMS queue is
- also deployed as a core queue in the core configuration. Take a look at the
- bridge example for an example of how this is done.</para>
+ <para>如果使用JMS,JMS的配置文件<literal
+ >hornetq-jms.xml</literal>在核心配置文件<literal>
+ hornetq-configuration.xml</literal>之后装载。所以如果你的桥要从JMS
+ 队列接收消息,就需要保证JMS队列同时要作为核心队列部署。具体方法可以参见
+ bridge例子。</para>
</note>
</listitem>
<listitem>
- <para><literal>forwarding-address</literal>. This is the address on the target
- server that the message will be forwarded to. If a forwarding address is not
- specified, then the original address of the message will be retained.</para>
+ <para><literal>forwarding-address</literal>。目的服务器中的地址。消息将被转发到这个地址。
+ 如果没有指定这个转发地址,消息的原始地址将会保留。</para>
</listitem>
<listitem>
- <para><literal>filter-string</literal>. An optional filter string can be supplied.
- If specified then only messages which match the filter expression specified in
- the filter string will be forwarded. The filter string follows the HornetQ
- filter expression syntax described in <xref linkend="filter-expressions"
- />.</para>
+ <para><literal>filter-string</literal>。一个可选的过滤器表达式。它表示只有过滤器表达式选择
+ 的消息才被转发。过滤器表达式的语法参见 <xref linkend="filter-expressions"
+ />。</para>
</listitem>
<listitem>
- <para><literal>transformer-class-name</literal>. An optional transformer-class-name
- can be specified. This is the name of a user-defined class which implements the
- <literal>org.hornetq.core.server.cluster.Transformer</literal>
- interface.</para>
- <para>If this is specified then the transformer's <literal>transform()</literal>
- method will be invoked with the message before it is forwarded. This gives you
- the opportunity to transform the message's header or body before forwarding
- it.</para>
+ <para><literal>transformer-class-name</literal>。可选的转换器类名。这是一个用户定义的
+ 类,它需要实现接口<literal>org.hornetq.core.server.cluster.Transformer</literal>
+ 。</para>
+ <para>如果指定了这个类,每当一个消息被转发之前,它的<literal>transform()</literal>方法
+ 就会被调用。用户利用这个机会可以对消息本身或消息头信息进行修改。</para>
</listitem>
<listitem>
- <para><literal>retry-interval</literal>. This optional parameter determines the
- period in milliseconds between subsequent reconnection attempts, if the
- connection to the target server has failed. The default value is <literal
- >2000</literal>milliseconds.</para>
+ <para><literal>retry-interval</literal>。这个可选参数决定了在进行连接重试时,两次重试
+ 之间的时间间隔。默认值是<literal>2000</literal>毫秒。</para>
</listitem>
<listitem>
- <para><literal>retry-interval-multiplier</literal>. This optional parameter
- determines determines a multiplier to apply to the time since the last retry to
- compute the time to the next retry.</para>
- <para>This allows you to implement an <emphasis>exponential backoff</emphasis>
- between retry attempts.</para>
- <para>Let's take an example:</para>
- <para>If we set <literal>retry-interval</literal>to <literal>1000</literal> ms and
- we set <literal>retry-interval-multiplier</literal> to <literal>2.0</literal>,
- then, if the first reconnect attempt fails, we will wait <literal>1000</literal>
- ms then <literal>2000</literal> ms then <literal>4000</literal> ms between
- subsequent reconnection attempts.</para>
- <para>The default value is <literal>1.0</literal> meaning each reconnect attempt is
- spaced at equal intervals.</para>
+ <para><literal>retry-interval-multiplier</literal>。这个可选参数基于前一次重试连接
+ 的时间间隔来计算下一次重试的间隔,即前一次的间隔乘以该参数。</para>
+ <para>这样可以实现重试间隔的<emphasis>指数延迟(exponential backoff)</emphasis>。</para>
+ <para>让我们看一个例子:</para>
+ <para>假设<literal>retry-interval</literal>为<literal>1000</literal> ms,并且我们
+ 将<literal>retry-interval-multiplier</literal>设为<literal>2.0</literal>,如果
+ 第一次尝试失败,则等待<literal>1000</literal>毫秒后进行第二次重试,如果再失败,则每三次重
+ 试要在<literal>2000</literal>毫秒后进行,第四次要等待<literal>4000</literal>毫秒,
+ 以此类推。</para>
+ <para>默认值是<literal>1.0</literal>,表示每次重试间隔相同的时间。</para>
</listitem>
<listitem>
- <para><literal>reconnect-attempts</literal>. This optional parameter determines the
- total number of reconnect attempts the bridge will make before giving up and
- shutting down. A value of <literal>-1</literal> signifies an unlimited number of
- attempts. The default value is <literal>-1</literal>.</para>
+ <para><literal>reconnect-attempts</literal>。可选参数。它表示要进行多少重试后才放弃
+ 并退出。<literal>-1</literal>表示进行无限次重试。默认值是<literal>-1</literal>。</para>
</listitem>
<listitem>
- <para><literal>failover-on-server-shutdown</literal>. This optional parameter
- determines whether the bridge will attempt to failover onto a backup server (if
- specified) when the target server is cleanly shutdown rather than
- crashed.</para>
- <para>The bridge connector can specify both a live and a backup server, if it
- specifies a backup server and this parameter is set to <literal>true</literal>
- then if the target server is <emphasis>cleanly</emphasis> shutdown the bridge
- connection will attempt to failover onto its backup. If the bridge connector has
- no backup server configured then this parameter has no effect. </para>
- <para>Sometimes you want a bridge configured with a live and a backup target server,
- but you don't want to failover to the backup if the live server is simply taken
- down temporarily for maintenance, this is when this parameter comes in
- handy.</para>
- <para>The default value for this parameter is <literal>false</literal>.</para>
+ <para><literal>failover-on-server-shutdown</literal>。可选参数。它指定了当目的服务器正常
+ 退出时桥是否尝试失效备援(failover)到备份服务器(如果配置了的话)上。</para>
+ <para>桥的连接器可以配置一个主服务器和一个备份服务器。如果配置了备份服务器,并且这个参数是
+ <literal>true</literal>时,在主服务器正常退出时,桥会自动地连接到备份服务器上继续工作。
+ 如果桥的连接器没有配置备份服务器,则这个参数不起作用。</para>
+ <para>你的桥配置了备份服务器后,有时你需要临时将主服务器关闭进行一些维护,此时并不希望桥连接到备份服务
+ 器中。使用该参数就可以达到这个目的。</para>
+ <para>这个参数的默认值是<literal>false</literal>。</para>
</listitem>
<listitem>
- <para><literal>use-duplicate-detection</literal>. This optional parameter determines
- whether the bridge will automatically insert a duplicate id property into each
- message that it forwards.</para>
- <para>Doing so, allows the target server to perform duplicate detection on messages
- it receives from the source server. If the connection fails or server crashes,
- then, when the bridge resumes it will resend unacknowledged messages. This might
- result in duplicate messages being sent to the target server. By enabling
- duplicate detection allows these duplicates to be screened out and
- ignored.</para>
- <para>This allows the bridge to provide a <emphasis>once and only once</emphasis>
- delivery guarantee without using heavyweight methods such as XA (see <xref
- linkend="duplicate-detection"/> for more information).</para>
- <para>The default value for this parameter is <literal>true</literal>.</para>
+ <para><literal>use-duplicate-detection</literal>。可选参数。它控制桥是否在转发的消息中自动
+ 添加一个重复ID的属性。</para>
+ <para>添加这样一个属性可以使目的服务器对来自源服务器的消息进行重复检测。当出现连接故障或服务器崩溃时,
+ 桥在恢复时将重新转发那些没有被通知的消息。这在目的服务器端有可能造成重复发送。使用重复检测功能,可
+ 以将重复发送的消息过滤掉。</para>
+ <para>使用这个功能,服务器就可以保证 <emphasis>一次并且只有一次</emphasis>的传递,而不需要使用
+ 重量级的方法,如XA(参见 <xref
+ linkend="duplicate-detection"/>)。</para>
+ <para>默认的值是<literal>true</literal>.</para>
</listitem>
<listitem>
- <para><literal>confirmation-window-size</literal>. This optional parameter
- determines the <literal>confirmation-window-size</literal> to use for the
- connection used to forward messages to the target node. This attribute is
- described in section <xref linkend="client-reconnection"/></para>
+ <para><literal>confirmation-window-size</literal>。这个可选参数决定了向目的服务器转发消息时
+ 所使用的确认窗口的大小。详细的描述在<xref linkend="client-reconnection"/>。</para>
<para>
- <warning>When using the bridge to forward messages from a queue which has a
- max-size-bytes set it's important that confirmation-window-size is less than
- or equal to <literal>max-size-bytes</literal> to prevent the flow of
- messages from ceasing. </warning>
+ <warning>当桥从一个设置了max-size-bytes参数的队列接收并转发消息时,一个重要的事情就是要将
+ confirmation-window-size的值设置为小于等于
+ <literal>max-size-bytes</literal>的值,以避免造成消息流的停止。</warning>
</para>
</listitem>
<listitem>
- <para><literal>connector-ref</literal>. This mandatory parameter determines which
- <emphasis>connector</emphasis> pair the bridge will use to actually make the
- connection to the target server.</para>
- <para>A <emphasis>connector</emphasis> encapsulates knowledge of what transport to
- use (TCP, SSL, HTTP etc) as well as the server connection parameters (host, port
- etc). For more information about what connectors are and how to configure them,
- please see <xref linkend="configuring-transports"/>.</para>
- <para>The <literal>connector-ref</literal> element can be configured with two
- attributes:</para>
+ <para><literal>connector-ref</literal>。这是一个必需的参数。它指定了桥用来连接目的服务器的
+ <emphasis>连接器</emphasis>。</para>
+ <para><emphasis>connector</emphasis>包含了所用的传输(TCP, SSL, HTTP等),以及服务器连接参数
+ (如主机名,端口等)。关于连接器的详细信息请参见(<xref linkend="configuring-transports"/>)。</para>
+ <para><literal>connector-ref</literal>有两个参数:</para>
<itemizedlist>
<listitem>
- <para><literal>connector-name</literal>. This references the name of a
- connector defined in the core configuration file <literal
- >hornetq-configuration.xml</literal>. The bridge will use this
- connector to make its connection to the target server. This attribute is
- mandatory.</para>
+ <para><literal>connector-name</literal>。这个指的是核心配置文件<literal
+ >hornetq-configuration.xml</literal>中定义的连接器的名字。桥使用
+ 这个连接器创建与目的服务器的连接。这个参数是必需指定的。</para>
</listitem>
<listitem>
- <para><literal>backup-connector-name</literal>. This optional parameter also
- references the name of a connector defined in the core configuration
- file <literal>hornetq-configuration.xml</literal>. It represents the
- connector that the bridge will fail-over onto if it detects the live
- server connection has failed. If this is specified and <literal
- >failover-on-server-shutdown</literal> is set to <literal
- >true</literal> then it will also attempt failover onto this
- connector if the live target server is cleanly shut-down.</para>
+ <para><literal>backup-connector-name</literal>。这个可选参数同样指定一个在核心
+ 配置文件<literal>hornetq-configuration.xml</literal>中定义的连接器名字。
+ 当目的服务器出现故障时,或者正常退出但是参数<literal
+ >failover-on-server-shutdown</literal>的值设为<literal
+ >true</literal>时,桥使用这个参数指定的连接器通过失效备援(failover)连接
+ 到备用的服务器。</para>
</listitem>
</itemizedlist>
</listitem>
<listitem>
- <para><literal>user</literal>. This optional parameter determines the user name to
- use when creating the bridge connection to the remote server. If it is not
- specified the default cluster user specified by <literal>cluster-user</literal>
- in <literal>hornetq-configuration.xml</literal> will be used. </para>
+ <para><literal>user</literal>。这个可选参数指定了桥在创建与远程服务器连接时所用的用户名。如果
+ 没有指定用户名,在配置文件<literal>hornetq-configuration.xml</literal>中
+ <literal>cluster-user</literal>所定义的默认集群用户名将被使用。 </para>
</listitem>
<listitem>
- <para><literal>password</literal>. This optional parameter determines the password
- to use when creating the bridge connection to the remote server. If it is not
- specified the default cluster password specified by <literal
- >cluster-password</literal> in <literal>hornetq-configuration.xml</literal>
- will be used. </para>
+ <para><literal>password</literal>。这个可选的参数给出的是桥创建与远程服务器连接所使用的密码。
+ 如果没有指定密码,在配置文件<literal>hornetq-configuration.xml</literal>中
+ <literal>cluster-password</literal>所定义的默认集群密码将被使用。</para>
</listitem>
</itemizedlist>
</section>
14 years
JBoss hornetq SVN: r9173 - trunk/tests/src/org/hornetq/tests/integration/client.
by do-not-reply@jboss.org
Author: jmesnil
Date: 2010-04-27 04:12:07 -0400 (Tue, 27 Apr 2010)
New Revision: 9173
Modified:
trunk/tests/src/org/hornetq/tests/integration/client/DeadLetterAddressTest.java
Log:
fixed testReceiveWithListeners() test
* use receive(5000) instead of receiveImmediate() otherwise the consumer may look at the DLQ too early
before the server has routed the message to the DLA after the redelivery attempts
Modified: trunk/tests/src/org/hornetq/tests/integration/client/DeadLetterAddressTest.java
===================================================================
--- trunk/tests/src/org/hornetq/tests/integration/client/DeadLetterAddressTest.java 2010-04-27 05:38:01 UTC (rev 9172)
+++ trunk/tests/src/org/hornetq/tests/integration/client/DeadLetterAddressTest.java 2010-04-27 08:12:07 UTC (rev 9173)
@@ -132,7 +132,7 @@
assertTrue(latch.await(5, TimeUnit.SECONDS));
assertEquals(handler.count, 2);
clientConsumer = clientSession.createConsumer(dlq);
- Message m = clientConsumer.receiveImmediate();
+ Message m = clientConsumer.receive(5000);
Assert.assertNotNull(m);
Assert.assertEquals(m.getBodyBuffer().readString(), "heyho!");
}
14 years
JBoss hornetq SVN: r9172 - branches/HnetQ_323_cn/docs/user-manual/zh.
by do-not-reply@jboss.org
Author: gaohoward
Date: 2010-04-27 01:38:01 -0400 (Tue, 27 Apr 2010)
New Revision: 9172
Modified:
branches/HnetQ_323_cn/docs/user-manual/zh/diverts.xml
Log:
done
Modified: branches/HnetQ_323_cn/docs/user-manual/zh/diverts.xml
===================================================================
--- branches/HnetQ_323_cn/docs/user-manual/zh/diverts.xml 2010-04-27 00:41:55 UTC (rev 9171)
+++ branches/HnetQ_323_cn/docs/user-manual/zh/diverts.xml 2010-04-27 05:38:01 UTC (rev 9172)
@@ -19,43 +19,28 @@
<!-- ============================================================================= -->
<chapter id="diverts">
- <title>Diverting and Splitting Message Flows</title>
- <para>HornetQ allows you to configure objects called <emphasis>diverts</emphasis> with
- some simple server configuration.</para>
- <para>Diverts allow you to transparently divert messages routed to one address to some other
- address, without making any changes to any client application logic.</para>
- <para>Diverts can be <emphasis>exclusive</emphasis>, meaning that that the message is diverted
- to the new address, and does not go to the old address at all, or they can be
- <emphasis>non-exclusive</emphasis> which means the message continues to go the old
- address, and a <emphasis>copy</emphasis> of it is also sent to the new address.
- Non-exclusive diverts can therefore be used for <emphasis>splitting</emphasis> message
- flows, e.g. there may be a requirement to monitor every order sent to an order queue.</para>
- <para>Diverts can also be configured to have an optional message filter. If specified then only
- messages that match the filter will be diverted.</para>
- <para>Diverts can also be configured to apply a <literal>Transformer</literal>. If specified,
- all diverted messages will have the opportunity of being transformed by the <literal
- >Transformer</literal>.</para>
- <para>A divert will only divert a message to an address on the <emphasis>same server</emphasis>,
- however, if you want to divert to an address on a different server, a common pattern would
- be to divert to a local store-and-forward queue, then set up a bridge which consumes from
- that queue and forwards to an address on a different server.</para>
- <para>Diverts are therefore a very sophisticated concept, which when combined with bridges can
- be used to create interesting and complex routings. The set of diverts on a server can be
- thought of as a type of routing table for messages. Combining diverts with bridges allows
- you to create a distributed network of reliable routing connections between multiple
- geographically distributed servers, creating your global messaging mesh.</para>
- <para>Diverts are defined as xml in the <literal>hornetq-configuration.xml</literal> file. There can
- be zero or more diverts in the file.</para>
- <para>Please see <xref linkend="divert-example" /> for a full working
- example showing you how to configure and use diverts.</para>
- <para>Let's take a look at some divert examples:</para>
+ <title>消息的转发(divert)与分流</title>
+ <para>在HornetQ中可以配置一些称为转发器(<emphasis>diverts</emphasis>)的对象。</para>
+ <para>转发器可以将路由到一个地址的消息透明地转发到其它的地址去,不需要客户端的参与。</para>
+ <para>转发器可以是<emphasis>唯一(exclusive)</emphasis>的,即消息只转发到新的地址,不发到原
+ 来的地址;也可以是<emphasis>不唯一(non-exclusive)</emphasis>的,即消息在发往原有地址的
+ 同时,它的一个<emphasis>拷贝</emphasis>被发往新的地址。不唯一的转发器可以在应用中将消息进行
+ <emphasis>分流(splitting)</emphasis>。比如在一个订单系统中它可以用于监视发往订单队列中
+ 的每个订单消息。</para>
+ <para>转发器还可以带一个可选的消息选择器。只有被选择器选择的消息才会被转发。</para>
+ <para>转发器还可以带有一个<literal>转换器(Transformer)</literal>。它可以将消息进行转换。</para>
+ <para>转发器只在同一个服务器中的地址间进行转发。如果要向另外服务器中的地址进行转发,可以采用转发器与桥配合
+ 来实现。先将消息通过转发器转发到一个存储与转发的队列中,再由一个桥将这个队列的消息转发到远程服务器的目的
+ 地址中。</para>
+ <para>由转发器与桥进行配合可以组成复杂的路由系统。在服务器中由一组转发器可以形成一个消息路由表。如果加上桥,就
+ 可以进一步形成一个分布式的可靠的消息路由网。</para>
+ <para>转发器的配置在<literal>hornetq-configuration.xml</literal>中定义。可以配置零个或多个转发器。</para>
+ <para>参见转发器的例子<xref linkend="divert-example" />,它展示了如何配置与使用转发器。</para>
+ <para>让我们看一些转发器的配置例子:</para>
<section>
- <title>Exclusive Divert</title>
- <para>Let's take a look at an exclusive divert. An exclusive divert diverts all matching
- messages that are routed to the old address to the new address. Matching messages do not
- get routed to the old address.</para>
- <para>Here's some example xml configuration for an exclusive divert, it's taken from the
- divert example:</para>
+ <title>唯一式转发器</title>
+ <para>下面是一个唯一式转发器的例子。它将所有符合条件的消息转发到新的地址,而旧的地址将不能得到这些消息。</para>
+ <para>以下配置来自于divert例子:</para>
<programlisting>
<divert name="prices-divert">
<address>jms.topic.priceUpdates</address>
@@ -67,34 +52,24 @@
<exclusive>true</exclusive>
</divert>
</programlisting>
- <para>We define a divert called '<literal>prices-divert</literal>' that will divert any
- messages sent to the address '<literal>jms.topic.priceUpdates</literal>' (this
- corresponds to any messages sent to a JMS Topic called '<literal
- >priceUpdates</literal>') to another local address '<literal
- >jms.queue.priceForwarding</literal>' (this corresponds to a local JMS queue called
- '<literal>priceForwarding</literal>'</para>
- <para>We also specify a message filter string so only messages with the message property
- <literal>office</literal> with value <literal>New York</literal> will get diverted,
- all other messages will continue to be routed to the normal address. The filter string
- is optional, if not specified then all messages will be considered matched.</para>
- <para>In this example a transformer class is specified. Again this is optional, and if
- specified the transformer will be executed for each matching message. This allows you to
- change the messages body or properties before it is diverted. In this example the
- transformer simply adds a header that records the time the divert happened.</para>
- <para>This example is actually diverting messages to a local store and forward queue, which
- is configured with a bridge which forwards the message to an address on another HornetQ
- server. Please see the example for more details.</para>
+ <para>在这里我们定义了一相名为“<literal>prices-divert</literal>”的转发器,它将发往
+ “<literal>jms.topic.priceUpdates</literal>”(对应JMS话题<literal
+ >priceUpdates</literal>)的消息转向另一个本地地址“<literal
+ >jms.queue.priceForwarding</literal>”(对应JMS队列
+ <literal>priceForwarding</literal>)。</para>
+ <para>我们还配置了一相消息过滤器。只有<literal>office</literal>属性值为<literal>New York</literal>
+ 的消息才被转发到新地址,其它消息则继续发往原地址。如果没有定义过滤器,所有消息将被转发。</para>
+ <para>本例中还配置了一个转换器的类。当每转发一个消息时,该转换器就被执行一次。转换器可以对消息在转发前进行
+ 更改。这里的转换器只是在消息中加入了一个记录转发时间的消息头。</para>
+ <para>本例中消息被转发到一个’存贮与转发是‘队列,然后通过一个桥将消息转发到另一个HornetQ服务器中。</para>
</section>
<section>
- <title>Non-exclusive Divert</title>
- <para>Now we'll take a look at a non-exclusive divert. Non exclusive diverts are the same as
- exclusive diverts, but they only forward a <emphasis>copy</emphasis> of the message to
- the new address. The original message continues to the old address</para>
- <para>You can therefore think of non-exclusive diverts as <emphasis>splitting</emphasis> a
- message flow.</para>
- <para>Non exclusive diverts can be configured in the same was as exclusive diverts with an
- optional filter and transformer, here's an example non-exclusive divert, again from the
- divert example:</para>
+ <title>不唯一转发器</title>
+ <para>下面我们来看一个不唯一的转发器。不唯一转发器将消息的<emphasis>拷贝</emphasis>转发到新的地址中,
+ 原消息则继续发往原有地址。</para>
+ <para>因此不唯一转发器可以将消息进行分流(splitting)。</para>
+ <para>不唯一转发器的配置与唯一转发器的配置中一样的,也可以带一个可选的过滤器和转换器。下面的配置也是出自
+ divert例子:</para>
<programlisting>
<divert name="order-divert">
<address>jms.queue.orders</address>
14 years