script-exchange
文件大小: unknow
源码售价: 5 个金币 积分规则     积分充值
资源说明:RabbitMQ "Script Exchange" plugin
# RabbitMQ "Script Exchange" Plugin

Extends RabbitMQ Server with support for a new experimental exchange
type, `x-script`. An exchange of type `x-script` has an attached
program in a language such as Python, Ruby or Javascript, which is run
for each message travelling through the exchange.

Each script can:

 - **Filter and Route** messages: It's up to the script which messages
   are passed on, and if so how they are routed on to their
   destinations. Scripts can choose any combination of `fanout`,
   `direct` and `topic`-style message routing, on a message-by-message
   basis. If no routing selection is made, the message is dropped.

 - **Transform** messages: Scripts can alter content properties,
   headers and message bodies as they travel past. Arbitrary
   transformation is possible.

The current implementation does not expose the actual bindings
themselves to the scripts, but this is planned for a future
revision. Once the bindings are available for individual selection by
scripts, implementing exchanges such as an "XPath exchange" becomes
possible, where bindings contain XPath expressions for selecting
messages.

## Security

Scripts must be signed (using [GnuPG](http://www.gnupg.org/)), and the
server is configured with a list of trusted GnuPG key IDs. When a
script is uploaded to the server, its signature is checked, and it is
only executed if the signature is good and is made by one of the
configured trusted keys.

The plugin **will not run** unless a list of trusted GnuPG key IDs is
configured; see below for details of the configuration variables
required.

The virtual machines for each supported scripting language run as the
same user that runs the RabbitMQ server, meaning that unless extra
steps are taken, scripts uploaded by AMQP **clients** can run code as
the AMQP **server user** on the server's operating system if their
signatures verify successfully.

For this reason, *do not enable this plugin until you have fully
analyzed the security consequences of doing so*. You may compromise a
large chunk of your network.

By default, the plugin is configured with only the Spidermonkey
(v1.8.0 or newer; see below) Javascript interpreter
enabled. Spidermonkey is a reasonably tightly sandboxed environment
for client-supplied programs to run in. While Python is also
supported, the difficulty of securing the Python virtual machine makes
it unwise to enable Python support in anything other than the most
tightly locked-down environments.

Besides the existing support for signing of uploaded scripts, another
potential (but to date untried) approach to securing script
interpreters for the plugin is to use
[Jailkit](http://olivier.sessink.nl/jailkit/) (or similar) to set up
`chroot` jails for them. Jailkit runs on Linux, Solaris and various
BSDs including Mac OS X.

## Declaring `x-script` exchanges

Use AMQP's `Exchange.Declare` operation as usual, with type `x-script`
and the following three required arguments:

 - `type` (string): the MIME-type of the language to be used to
   interpret the attached script. Currently, the following language
   MIME-types are supported:

     - `text/javascript`: Requires Spidermonkey to be installed. I've
       been using version "1.8.0 pre-release 1 2007-10-03". See below
       for a note on the unsuitability of earlier releases of
       Spidermonkey.

     - `text/x-python`: Requires the default `python` interpreter to
       have a `simplejson` module installed. Python v2.6 and newer
       include `simplejson` by default.

 - `definition` (string): the source-code for the script itself.

 - `signature` (string): a detached GPG signature, covering the bytes
   in the `definition` argument.

As an example, here's an exchange declaration containing a simple
Javascript script, written for the
[Pika](http://github.com/tonyg/pika) Python AMQP client library:

    script_source = r"""
      return function (msg) {
        // filter, transform and route the message here to your taste.
        msg.fanout();
      }
    """

    signature = r"""
    -----BEGIN PGP SIGNATURE-----
    Version: GnuPG v1.4.10 (Darwin)

    iQEcBAABAgAGBQJLzVuBAAoJEJQm9mH+Pp+mcWIH/Ru1NsMXEur1TkBqInaf7o05
    6PI/okXUNmF9cXJwpA7inzLXYJTFSi6kVxPZ0LAxm/9bUUauFR4K5tXKDHOmRN/t
    k3hFm8HjFURz6wZNNuRUXxern4BJDu/7qx/fs0JvD7kizGjwHCfPOMq4oOo//Ov7
    BXGYEm3jAZD9tCt6K3prHVOHFnhacvlkRqvLQZxDa+ukhn9EfgHZU0QIKDfsj7ki
    uS1Ax+vRgLlT2BLABuVLEu2qsa5ABsr3V7VkPK/7wCw6SDf+x6IpzX5kRCiTe1lD
    6im6TIQNOiH0H9lTWBjo1XqQM3vaZAo5Dc/Hkq/taisK0xW5D6W8wUutL0XQWmY=
    =pkGp
    -----END PGP SIGNATURE-----
    """

    ch.exchange_declare(exchange = 'my-exchange-name',
                        type = 'x-script',
                        arguments = {"type": "text/javascript",
                                     "definition": script_source,
                                     "signature": signature})

Given a script definition in a variable `script_source`, and a key ID
(e.g. `"58914A52"`) in a variable `signingkey`, you can compute the
signature by shelling out to the `gpg` command-line tool. Here's a
python example, which leaves the computed signature in a variable
`signature`:

    (po, pi) = os.popen2("gpg -u %s -a --detach-sign" % (signingkey,))
    po.write(script_source)
    po.close()
    signature = pi.read()
    pi.close()

## Writing scripts

### Javascript

Supply a sequence of statements, ending in the `return` of a function
of one argument.

    arguments = {
      "type": "text/javascript",
      "definition": "return function (msg) { ... }"
    }

The message is passed in to the function as an object with the
following fields and methods:

 - `routing_key`: the routing key given when the message was published.
 - `properties`: a dictionary containing the content properties and
   headers, as per the AMQP specification.
 - `body`: the body of the message, as a string.
 - `fanout()`: call this method to cause the message to be routed as
   if the defined exchange were a fanout exchange.
 - `direct(key)`: call this method to cause the message to be routed
   as if the defined exchange were a direct exchange and as if the
   message had routing-key `key` attached to it.
 - `topic(key)`: call this method to cause the message to be routed as
   if the defined exchange were a topic exchange and as if the message
   had routing-key `key` attached to it.

If `properties` or `body` is altered before the function returns, the
changes will be sent on to the queues to which the message is routed.

Each call to `fanout`, `direct`, or `topic` causes a single delivery
of the message. For example, calling `fanout` twice will cause each
bound queue to receive *two* copies of the message.

### Python

Defining a Python exchange script is very similar to defining a
Javascript script. The main difference is that because the script is
evaluated with Python's
[exec](http://docs.python.org/reference/simple_stmts.html#the-exec-statement)
statement, `return`ing an anonymous function is not possible: instead,
a function named `handler` should be defined.

    arguments = {
      "type": "text/x-python",
      "definition": "def handler(msg): ..."
    }

Make sure to get the indentation right in the `definition` argument to
`Exchange.Declare`.

The `msg` argument to `handler` is a message object, very similar to
that given to a Javascript function, with identically-named fields and
methods. See the Javascript section for details.

Note that the plugin ships with Python support disabled, for the
reasons given above in the section on security.

## Managing state in scripts

While handler functions *can* be made stateful, it's not a good
idea. Try to keep your handler functions stateless.

The system is free to start (and shut down!) language-specific virtual
machines any time it needs to (up to the `max_instance_count` setting;
see below), so state held in memory can be lost at any time. If the
handler function returned from the `definition` program is a
closure---if it captures any mutable state---the lifetime of that
state is not guaranteed to persist for longer than a single call to
the handler function.

Furthermore, once more than a single virtual machine has been started
for a given language, all free instances are kept in a pool, so any
one virtual machine instance will see a different stream of messages
from the others.

If a stateful script is absolutely required, augment the
language-specific virtual machine boot script with a means of sharing
state between instances, and use that. For example, CouchDB, Redis,
Riak or even memcached could be used to share state between all
instances of a given exchange.

## Configuring the plugin and defining new languages

Define the following Erlang application configuration variables to
alter the settings for the plugin from their defaults:

 - application `rabbit_script_exchange`, variable `permitted_key_ids`:
   set this to a list of strings, identifying the public-keys that
   will be accepted as valid signatures for uploaded script
   definitions. This variable defaults to the empty list. For an
   example of creating a keypair and configuring this variable, please
   see below.

 - application `rabbit_script_exchange`, variable `languages`: set
   this to a list of tuples describing the available language
   MIME-types and virtual-machine command-line invocations required to
   start instances. This variable defaults to

   
       {languages, [{<<"text/javascript">>, [{command_line, "js js_exchange_boot.js"}]}]}
Command lines given are run with their current-working-directory set to the `priv` directory of the unpacked plugin. - application `rabbit_script_exchange`, variable `max_instance_count`: set this to the maximum number of instances of each language's virtual machine to start up. If more than one concurrent instance of a language is required to honor requests sent through `x-script` exchanges, another will be created until `max_instance_count` has been reached, after which the requesting party will be entered into a queue waiting for a busy instance to be freed up. For the plugin to work, GnuPG (the `gpg` executable) needs to be on the path, and the `permitted_key_ids` need to be available in the `rabbitmq` user's GnuPG public keyring. ### Generating a keypair and configuring `permitted_key_ids` In this example, we will generate and configure a GnuPG key that can only be used for signing. $ gpg --gen-key gpg (GnuPG) 1.4.10; Copyright (C) 2008 Free Software Foundation, Inc. This is free software: you are free to change and redistribute it. There is NO WARRANTY, to the extent permitted by law. Please select what kind of key you want: (1) RSA and RSA (default) (2) DSA and Elgamal (3) DSA (sign only) (4) RSA (sign only) Your selection? 4 RSA keys may be between 1024 and 4096 bits long. What keysize do you want? (2048) Requested keysize is 2048 bits Please specify how long the key should be valid. 0 = key does not expire = key expires in n days w = key expires in n weeks m = key expires in n months y = key expires in n years Key is valid for? (0) 1w Key expires at Tue Apr 27 19:54:33 2010 NZST Is this correct? (y/N) y You need a user ID to identify your key; the software constructs the user ID from the Real Name, Comment and Email Address in this form: "Heinrich Heine (Der Dichter) " Real name: Test key for script-exchange Email address: script-exchange@example.org Comment: You selected this USER-ID: "Test key for script-exchange " Change (N)ame, (C)omment, (E)mail or (O)kay/(Q)uit? o You need a Passphrase to protect your secret key. You don't want a passphrase - this is probably a *bad* idea! I will do it anyway. You can change your passphrase at any time, using this program with the option "--edit-key". We need to generate a lot of random bytes. It is a good idea to perform some other action (type on the keyboard, move the mouse, utilize the disks) during the prime generation; this gives the random number generator a better chance to gain enough entropy. +++++ .+++++ gpg: key 58914A52 marked as ultimately trusted public and secret key created and signed. gpg: checking the trustdb gpg: public key FBBB8AB1 is 58138 seconds newer than the signature gpg: 3 marginal(s) needed, 1 complete(s) needed, classic trust model gpg: depth: 0 valid: 4 signed: 3 trust: 0-, 0q, 0n, 0m, 0f, 4u gpg: depth: 1 valid: 3 signed: 1 trust: 3-, 0q, 0n, 0m, 0f, 0u gpg: next trustdb check due at 2010-04-27 pub 2048R/58914A52 2010-04-20 [expires: 2010-04-27] Key fingerprint = 1F4E A37F 1657 2A50 DC77 E755 0D3D C0A9 5891 4A52 uid Test key for script-exchange Note that this key cannot be used for encryption. You may want to use the command "--edit-key" to generate a subkey for this purpose. Check that the key is present in the keyring of the `rabbitmq` user: $ gpg --list-keys script-exchange@example.org pub 2048R/58914A52 2010-04-20 [expires: 2010-04-27] uid Test key for script-exchange This lets us know that we should configure `permitted_key_ids` with the string `"58914A52"`. There are many ways of doing this, but a good choice is to add a stanza to your `rabbitmq.config` file: [ {rabbit_script_exchange, [{permitted_key_ids, ["58914A52"]}]} ]. If more than one key is to be authorised for uploaded scripts, the `permitted_key_ids` variable should contain more than one string: [ {rabbit_script_exchange, [{permitted_key_ids, ["58914A52", "12345678", "98765432"]}]} ]. Both the short ("58914A52") form and the long ("0D3DC0A958914A52") forms of key IDs are acceptable for use in the `permitted_key_ids` variable. ## Interactions between RabbitMQ and script language virtual machines The protocol is not documented yet, as it may still change; read `priv/js_exchange_boot.js` and `priv/py_exchange_boot.py` in conjunction with `src/script_instance.erl` for details of its operation. ## Known bugs - Unicode, UTF-8 and 8-bit-clean message processing has not yet been thoroughly tested. - The error handling in the vm-rabbit protocol is very poorly defined; error handling in the language-specific boot scripts is similarly awful. This can make debugging problems very difficult. Keep an eye on the RabbitMQ logs: it's the only place any useful information appears when problems occur. ## Licensing This plugin is licensed under the MPL. The full license text is included with the source code for the package. If you have any questions regarding licensing, please contact us at . ## Known Problems Version 1.7.0 of Spidermonkey lacks a significant `fflush()` call in its `print()` primitive which prevents it from working with the script exchange. Version 1.8.0rc1 seems to be fine, though.

本源码包内暂不包含可直接显示的源代码文件,请下载源码包。