hbpush is a python/tornado implementation of Leo Ponomarev's Basic HTTP Push Relay Protocol.
Yes, it is yet another comet server, but simpler, and speaking plain old HTTP.
- long polling / interval polling for subscriber
- pluggable message storage (included: memory or redis)
$ git clone git://github.com/clement/py-hbpush.git $ cd py-hbpush $ python setup.py install
$ hbpushd
to run it with the default configuration, or
$ hbpushd --config=path/to/configuration/file
Well, well, the best is probably to read protocol itself. For lazies out there, here is a small example using cURL:
// Run the server with the default configuration $ sudo hbpushd & // Post a message to channel `test` $ curl -i -d 'Hello world!' -H 'Content-Type: text/plain' http://localhost:80/publisher/test // Retrieve that message (and print response headers) $ curl -i http://localhost:80/subscriber/test HTTP/1.1 200 OK Content-Length: 12 Vary: If-Modified-Since, If-None-Match Server: TornadoServer/0.1 Last-Modified: Mon, 07 Jun 2010 16:21:50 GMT Etag: 0 Content-Type: text/plain Hello world! // Retrieve the next message (there's none so far, so the client // will wait) $ curl -i -H 'If-Modified-Since: Mon, 07 Jun 2010 16:21:50 GMT' -H 'If-None-Match: 0' http://localhost:90/test // Open up another terminal, and send a new message $ curl -i -d '{"msg":"Hello world!"}' -H 'Content-Type: application/json' http://localhost:90/test // Back to the first terminal, you'll see the message has arrived HTTP/1.1 200 OK Content-Length: 22 Vary: If-Modified-Since, If-None-Match Server: TornadoServer/0.1 Last-Modified: Mon, 07 Jun 2010 16:25:21 GMT Etag: 0 Content-Type: application/json {"msg":"Hello world!"}
hbpushd configuration files are either YAML or JSON files.
You can use the following options:
port
: the numeric port number to use (default to80
)address
: the IP-address to bind to (default to''
)
Example configuration (in YAML):
port: 9090 address: 127.0.0.1
Stores are modules responsible for storing/retrieving messages. hbpush comes bundled with two types
of stores, memory
and redis
. Each of these stores has specific options. For redis:
host
: the hostname for redis server, default to'localhost'
port
: the port for redis server, default to6379
database
: the database index to select when connecting to rediskey_prefix
: a string prepended to a channel identifier to make a redis key. Use this to avoid key collision when you're using your redis server for other stuff.
Memory stores haven't any specific options (yet).
Here is an example of how to specify the store (YAML):
port: 9090 store: type: redis key_prefix: hbpush_
In more complex configurations, you might need multiple stores on the same server. Here is how it looks like:
port: 9090 store: mystore: type: redis host: 127.0.0.1 port: 6380 myotherstore: type: memory default: type: redis port: 6379
Note that default
is a special name (see the Locations part). Also, if you just specify an unnamed
store, it will have a name of default
. That means that the two following configuration snippets are
equivalent:
port: 9090 store: type: redis key_prefix: hbpush_ # is exactly the same as port: 9090 store: default: type: redis key_prefix: hbpush_
Locations are URLs pattern on which the server listen for publishing/subscribing request. hbpush provides a flexible way to configure those, or you can stick with the default configuration, which should be enough for a vast majority of use-cases.
A location has a type
of either publisher
or subscriber
. It supports also setting some options:
store
: the store name to use (default todefault
)prefix
: an URL prefix for this location. For example/publisher/
. Everything coming after the prefix will be used as channel id (not set by default)url
: the complete URL pattern to use for this location, eg:/channel/(\d+)/publish/
. Not you should have only one capture group, that must represent the channel id. This settings has precedence overprefix
(not set by default)polling
(subscriber only):interval
orlong
, see the protocol for more information (default tolong
)create_on_post
(publisher only): if set tofalse
, you will need to create a channel with a PUT request first before POSTing any data to it (default totrue
)
For info, the default configuration looks like this:
port: 80 store: type: memory locations: - type: subscriber prefix: /subscriber/ - type: publisher prefix: /publisher/
Now, here's a complex configuration example, with multiple stores, and multiple pub/sub locations:
port: 9090 store: default: type: memory redis1: type: redis key_prefix: redis1_ redis2: type: redis key_prefix: redis2_ locations: - type: subscriber prefix: /sub/ - type: publisher prefix: /pub/ - type: subscriber polling: interval url: /redis/(.+)/1/sub/ store: redis1 - type: publisher url: /redis/(.+)/1/pub/ store: redis1 - type: subscriber url: /redis/(.+)/2/sub/ store: redis2 - type: publisher url: /redis/(.+)/2/pub/ store: redis2
The server will try each location pattern in order of definition.
It also won't detect if you messed up your URL scheme, so be careful designing it. A typical example:
locations: - type: subscriber url: /(.+) - type: publisher url: /pub/(.+)
with this configuration, your publisher location will be unreachable, as the server will always match the request to the subscriber location.
Make sure you have a test redis server accessible at localhost:6379
. Be careful, the tests suite will
flush your server default database, you've been warned.
Run the test suite with
$ python setup.py nosetests
- hbpushd depends on the development version of facebook's tornado.
setup.py
will install a compatible version, but if you have already installed tornado througheasy_install
orpip
, you might have some problems with Etags, or when launching hbpushd. In that case, reinstall the latest version of tornado.
- 0.1.0
- redis and memory message store
- interval and long polling
- subscriber and publisher locations
- multiplexing
- postgreSQL message store
- in-code documentation
- codebase refactoring