blob: 2dea949a1e2bfb3835a51b17f8a660e7e085e8aa [file] [log] [blame]
Qpid Proton JavaScript Language Bindings
========================================
The code contained herein provides JavaScript language bindings for working
with the Qpid Proton AMQP 1.0 protocol engine and messenger library.
Important Note - Modern Browser Needed
======================================
The JavaScript binding requires ArrayBuffer/TypedArray and WebSocket support.
Both of these are available in most "modern" browser versions. The author has
only tried running on FireFox and Chrome, though recent Safari, Opera and IE10+
*should* work too - YMMV. It might be possible to polyfill for older browsers
but the author hasn't tried this.
Important Note - WebSocket Transport!!!
=======================================
Before going any further it is really important to realise that the JavaScript
bindings to Proton are somewhat different to the bindings for other languages
because of the restrictions of the execution environment. In particular it is
very important to note that the JavaScript bindings by default use a WebSocket
transport and not a TCP transport, so whilst it's possible to create Server style
applications that clients can connect to (e.g. recv.js and send.js) note that:
JavaScript clients cannot *directly* talk to "normal" AMQP applications such as
qpidd or (by default) the Java Broker because they use a standard TCP transport.
This is a slightly irksome issue, but there's no getting away from it because
it's a security restriction imposed by the browser environment.
At the moment even for Node.js we are limited to using a WebSocket transport, but
it is on the author's "TODO" list to look at providing a means to use either a
WebSocket transport or a native TCP transport (via node's net library). It should
also be possible to use native TCP for Chrome "packaged apps", but again this is
only on the TODO list so if you want to talk to a "normal" AMQP application you
must live with the WebSocket constraints.
Option 1. proxy from WebSockets to TCP sockets. The application
<proton>/examples/messenger/javascript/proxy.js
is a simple Node.js WebSocket<->TCP Socket proxy, simply doing:
node proxy.js
will stand up a proxy listening by default on WebSocket port 5673 and forwarding
to TCP port 5672 (this is configurable, for options do: node proxy.js -h)
Rather than using a stand-alone proxy it is possible to have applications stand
up their own proxy (where lport = listen port, thost = target host and
tport = target port):
var proxy = require('./ws2tcp.js');
proxy.ws2tcp(lport, thost, tport);
For talking to the C++ broker unfortunately proxying is currently the only option
as qpidd does not have a WebSocket transport.
Option 2. The Java Broker's WebSocket transport.
Recent releases of the Qpid Java Broker provide a native WebSocket transport which
means that the JavaScript binding can talk to it with no additional requirements.
It is necessary to configure the Java Broker as the WebSocket transport is not
enabled by default. In <qpid-work>/config.json in the "ports" array you need to add:
{
"authenticationProvider" : "passwordFile",
"name" : "AMQP-WS",
"port" : "5673",
"transports" : [ "WS" ]
}
This sets the JavaBroker to listen on WebSocket port 5673 similar to the proxy.
One gotcha that still bites the author *** DO NOT FORGET *** that you will be
using ports that you are not used to!! If you are not connecting check that the
proxy is running and that you are specifying the right ports. I still mess up :-(
WebRTC Transport
================
A WebRTC Transport is supported by emscripten, though the author has not tried it.
If you wan to play with this you are very much on your own at the moment YMMV.
This is configured by adding to the LINK_FLAGS in CMakeLists.txt
-s \"SOCKET_WEBRTC=1\"
/* WebRTC sockets supports several options on the Module object.
* Module['host']: true if this peer is hosting, false otherwise
* Module['webrtc']['broker']: hostname for the p2p broker that this peer should use
* Module['webrtc']['session']: p2p session for that this peer will join, or undefined if this peer is hosting
* Module['webrtc']['hostOptions']: options to pass into p2p library if this peer is hosting
* Module['webrtc']['onpeer']: function(peer, route), invoked when this peer is ready to connect
* Module['webrtc']['onconnect']: function(peer), invoked when a new peer connection is ready
* Module['webrtc']['ondisconnect']: function(peer), invoked when an existing connection is closed
* Module['webrtc']['onerror']: function(error), invoked when an error occurs
*/
If you wanted to play with these you'd likely have to tweak the module.js code in
<proton>/proton-c/bindings/javascript
The emscripten documentation is a bit light on the WebRTC Transport too, though
emscripten/tests/test_sockets.py
emscripten/tests/sockets/webrtc_host.c
emscripten/tests/sockets/webrtc_peer.c
emscripten/tests/sockets/p2p/broker/p2p-broker.js
emscripten/tests/sockets/p2p/client/p2p-client.js
Look like they provide a starting point.
Very much TODO......
Creating The Bindings
=====================
To generate the JavaScript bindings we actually cross-compile from proton-c
You will need to have emscripten (https://github.com/kripken/emscripten) installed
to do the cross-compilation and in addition you will require a few things that
emscripten itself depends upon.
http://kripken.github.io/emscripten-site/docs/building_from_source/index.html#installing-from-source
http://kripken.github.io/emscripten-site/docs/building_from_source/toolchain_what_is_needed.html
provide instructions for installing emscripten and the "fastcomp" LLVM backend.
This approach lets users use the "bleeding edge" version of emscripten on the
"incoming" branch (pretty much analogous to building qpid/proton off svn trunk).
This is the approach that the author of the JavaScript Bindings tends to use.
http://kripken.github.io/emscripten-site/docs/getting_started/downloads.html
provides some fairly easy to follow instructions for getting started on several
platforms the main dependencies are as follows (on Windows the SDK includes these):
* The Emscripten code, from github (git clone git://github.com/kripken/emscripten.git).
* LLVM with Clang. Emscripten uses LLVM and Clang, but at the moment the JavaScript
back-end for LLVM is off on a branch so you can't use a stock LLVM/Clang.
http://kripken.github.io/emscripten-site/docs/building_from_source/LLVM-Backend.html
http://kripken.github.io/emscripten-site/docs/building_from_source/building_fastcomp_manually_from_source.html#building-fastcomp-from-source
has lots of explanation and some easy to follow instructions for downloading
and building fastcomp
* Node.js (0.8 or above; 0.10.17 or above to run websocket-using servers in node)
* Python 2.7.3
* Java is required in order to use the Closure Compiler to minify the code.
If you haven't run Emscripten before it's a good idea to have a play with the
tutorial here:
http://kripken.github.io/emscripten-site/docs/getting_started/Tutorial.html
when you are all set up with emscripten and have got the basic tests in the
tutorial running building Proton should be simple, simply go to the Proton root
directory and follow the main instructions in the README there, in precis (from
the root directory) it's:
mkdir build
cd build
cmake ..
make
and you should be all set, to test it's all working do (from the build directory):
cd proton-c/bindings/javascript/examples
node recv-async.js
in one window and
node send-async.js
in another.
These examples are actually JavaScript applications that have been directly
compiled from recv-async.c and send-async.c in <proton>/examples/messenger/c
if you'd prefer to write applications in C and compile them to JavaScript that
is a perfectly valid approach and these examples provide a reasonable starting
point for doing so.
Documentation
=============
When you've successfully got a working build do:
make docs
Which will make all of the proton documentation including the JavaScript docs.
If successful the JSDoc generated documentation should be found here:
<proton>/build/proton-c/bindings/javascript/html/index.html
Using "native" JavaScript
=========================
The examples in <proton>/examples/messenger/javascript are the best starting point.
In practice the examples follow a fairly similar pattern to the Python bindings
the most important thing to bear in mind though is that JavaScript is completely
asynchronous/non-blocking, which can catch the unwary.
An application follows the following (rough) steps:
1. (optional) Set the heap size.
It's important to realise that most of the library code is compiled C code and
the runtime uses a "virtual heap" to support the underlying malloc/free. This is
implemented internally as an ArrayBuffer with a default size of 16777216.
To allocate a larger heap an application must set the PROTON_TOTAL_MEMORY global.
In Node.js this would look like (see send.js):
PROTON_TOTAL_MEMORY = 50000000; // Note no var - it needs to be global.
In a browser it would look like (see send.html):
<script type="text/javascript">PROTON_TOTAL_MEMORY = 50000000;</script>
2. Load the library and create a message and messenger.
In Node.js this would look like (see send.js):
var proton = require("qpid-proton-messenger");
var message = new proton.Message();
var messenger = new proton.Messenger();
In a browser it would look like (see send.html):
<script type="text/javascript" src="../../../node_modules/qpid-proton-messenger/lib/proton-messenger.js"></script>
<script type="text/javascript">
var message = new proton.Message();
var messenger = new proton.Messenger();
....
3. Set up event handlers as necessary.
messenger.on('error', <error callback>);
messenger.on('work', <work callback>);
messenger.on('subscription', <subscription callback>);
The work callback is triggered on WebSocket events, so in general you would use
this to send and receive messages, for example in recv.js we have:
var pumpData = function() {
while (messenger.incoming()) {
var t = messenger.get(message);
console.log("Address: " + message.getAddress());
console.log("Subject: " + message.getSubject());
// body is the body as a native JavaScript Object, useful for most real cases.
//console.log("Content: " + message.body);
// data is the body as a proton.Data Object, used in this case because
// format() returns exactly the same representation as recv.c
console.log("Content: " + message.data.format());
messenger.accept(t);
}
};
messenger.on('work', pumpData);
The subscription callback is triggered when the address provided in a call to
messenger.subscribe(<address>);
Gets resolved. An example of its usage can be found in qpid-config.js which is
a fully functioning and complete port of the python qpid-config tool. It also
illustrates how to do asynchronous request/response based applications.
Aside from the asynchronous aspects the rest of the API is essentially the same
as the Python binding aside from minor things such as camel casing method names etc.
Serialisation/Deserialisation, Types etc.
=========================================
The JavaScript binding tries to be as simple, intuitive and natural as possible
so when sending a message all native JavaScript types including Object literals
and Arrays are transparently supported, for example.
var message = new proton.Message();
message.setAddress('amqp://localhost');
message.setSubject('UK.NEWS');
message.body = ['Rod', 'Jane', 'Freddy', {cat: true, donkey: 'hee haw'}];
The main thing to bear in mind is that (particularly for sending messages) we
may need to use "adapters" to make sure values are correctly interpreted and
encoded to the correct type in the AMQP type system. This is especially important
when interoperating with a peer written in a strongly typed language (C/C++/Java).
Some examples of available types follow:
// UUID
message.body = new proton.Data.Uuid();
// AMQP Symbol
message.body = new proton.Data.Symbol("My Symbol");
// Binary data (created from a gibberish String in this case).
message.body = new proton.Data.Binary("Monkey Bathпогромзхцвбнм");
// Binary data (Get a Uint8Array view of the data and directly access that).
message.body = new proton.Data.Binary(4);
var buffer = message.body.getBuffer();
buffer[0] = 65;
buffer[1] = 77;
buffer[2] = 81;
buffer[3] = 80;
// Binary Data (Created from an Array, you can use an ArrayBuffer/TypedArray too).
message.body = new proton.Data.Binary([65, 77, 81, 80]);
Note that the implementation of proton.Data.Binary tries to minimise copying so
it accesses the internal emscripten heap *directly* this requires memory management
which is mostly handled transparently, but users need to be aware that the
underlying memory is "owned" by the Message Object, so if Binary data needs to
be maintained after the next call to messenger.get(message); it must be
*explicitly* copied. For more detail do "make docs" and see:
<proton>/build/proton-c/bindings/javascript/html/proton.Data.Binary.html
// AMQP Described (value, descriptor)
message.body = new proton.Data.Described('persian, 'com.cheezburger.icanhas');
// AMQP Timestamp maps to native JavaScript Date.
message.body = new Date();
// Various AMQP Array examples.
message.body = new proton.Data.Array('INT', [1, 3, 5, 7], "odd numbers");
message.body = new proton.Data.Array('UINT', [1, 3, 5, 7], "odd");
message.body = new proton.Data.Array('ULONG', [1, 3, 5, 7], "odd");
message.body = new proton.Data.Array('FLOAT', [1, 3, 5, 7], "odd");
message.body = new proton.Data.Array('STRING', ["1", "3", "5", "7"], "odd");
// A JavaScript TypedArray will map directly to and from an AMQP Array of the
// appropriate type (Internally sets a descriptor of 'TypedArray').
message.body = new Uint8Array([1, 3, 5, 7]);
// UUID Array
message.body = new proton.Data.Array('UUID', [new proton.Data.Uuid(), new proton.Data.Uuid(), new proton.Data.Uuid(), new proton.Data.Uuid()], "unique");
// Null
message.body = null;
// Boolean
message.body = true;
// Native JavaScript Array maps to an AMQP List
message.body = ['Rod', 'Jane', 'Freddy'];
// Native JavaScript Object maps to an AMQP Map
message.body = ['Rod', 'Jane', 'Freddy', {cat: true, donkey: 'hee haw'}];
// JavaScript only has a "number" type so the binding provides "decorator"
// methods added to the JavaScript Number class. To access this from number
// primitives it is necessary to either use braces or use a "double dot" so that
// the interpreter can disambiguate from a simple decimal point. The binding will
// attempt to use the correct type such that message.body = 2147483647; would be
// sent as an AMQP integer, but because of the way JavaScript coerces integers
// message.body = 2147483647.0; would also be sent as an AMQP integer because
// 2147483647.0 gets transparently conveted to 2147483647 by the interpreter, so
// to explicitly send this as an AMQP float we'd need to do:
// message.body = 2147483647.0.float();
// Some more number examples:
message.body = 66..char(); // char - note double dot. (66).char(); works too.
message.body = 2147483647; // int
message.body = -2147483649; // long
message.body = 12147483649; // long
message.body = (12147483649).long(); // long
message.body = (17223372036854778000).ulong(); // ulong
message.body = (121474.836490).float(); // float
message.body = 12147483649.0.float(); // float
message.body = (4294967296).uint(); // uint
message.body = (255).ubyte(); // ubyte
Note too that floats are subject to a loss of precision
Fortunately most of these quirks only affect serialisation.when the binding
receives a message it will attempt to decode it into the most "natural" native
JavaScript type.
One additional decoding "quirk" can be caused by C++ qpid::messaging clients. It
is unfortunately quite common for C++ clients to incorrectly encode Strings as
AMQP Binary by neglecting to provide an encoding. The QMF Management Agent is one
such culprit. This is a bit of a pain, especially because of proton.Data.Binary
memory management quirks and having to remember to explicitly copy the data
on each call to messenger.get(message); In order to cater for this an overloaded
messenger.get(message, true); has been provided. Setting the second parameter to
true forces any received Binary payloads to be decoded as Strings. If you know
that producers might behave in this way and you are not expecting any "real"
Binary data from the producer this convenience mechanism results in nice clean
JavaScript Objects being received and is extremely useful for things like QMF.
JSON
====
As well as allowing native JavaScript Objects and Arrays to be transparently
serialised and deserialised via the AMQP type system it is also possible to
serialise/deserialise as JSON.
message.setAddress('amqp://localhost');
message.setContentType('application/json');
message.body = {array: [1, 2, 3, 4], object: {name: "John Smith", age: 65}};
On the wire the above will be encoded as an opaque binary in an AMQP data section
but will be deserialised into a JavaScript Object in exactly the same was as the
previous examples that use the AMQP type system.
SUPPORT
=======
To report bugs in the bindings, or to request an enhancement, please file
a tracker request:
https://issues.apache.org/jira/browse/PROTON
The main support channel is the qpid-users mailing list, see:
http://qpid.apache.org/discussion.html#mailing-lists
You can also directly interact with the development team and other users
in the #qpid channel on irc.freenode.net.