Bayeux compatible server written in Elixir.


License
MIT

Documentation

Pixie

Codeship Hex.pm

Pixie is a Faye compatible Bayeux implementation

Pixie is inspired by Faye and was originally planned as a port, but has diverged significantly as I've learned the Erlang way of modelling these sorts of problems.

Heroku Add-on

If you're planning on running Faye on Heroku you're probably going to have a bad time. Take a look at MessageRocket as an alternative, and help support the author to maintain more great open source projects.

License

Pixie is Copyright (c) 2015 James Harton and licensed under the terms of the MIT Public License (see the LICENSE file included with this distribution for more details).

Installation

Add pixie to your dependencies in the mix.exs file:

def deps do
  # ...
  {:pixie, "~> 0.1.3"}
  # ...
end

Also to the application section of your mix.exs file:

def application do
  [
    applications: [
      # ...
      :pixie,
      # ...
    ]
  ]
end

Then use mix deps.get to download Pixie from hex.

Status

Pixie is still pre 1.0, however it works and is compatible with the popular Faye JavaScript and Ruby clients.

Pixie is used in production at MessageRocket to handle relatively large message loads. Development is mostly guided by the needs of MessageRocket, however pull requests and issues are gratefully received.

Features

  • Compatible with Faye JavaScript and Ruby clients.
  • Supports both in-memory (ETS) and clustered (Redis) backends.
  • Handles all Bayeux message types.
  • Handles all Bayeux features except service channels.
  • Handles the following connection types:
    • long-polling
    • cross-origin-long-polling
    • callback-polling
    • websocket

Usage

Once you have pixie installed in your project you can run a stand alone-server with mix pixie.server.

Configuration

The configuration options and their defaults are shown here:

# This is the default backend configuration, you don't need to set it.
config :pixie, :backend,
  name: :ETS

# If you want to use Redis for clustering. The Redis backend defaults to
# localhost, unless you specify it here.
config :pixie, :backend,
  name: :Redis,
  redis_url: "redis://localhost:6379"

# When clients subscribe to channels we don't have to respond immediately, and
# can instead wait until there is a message to be sent on that channel or a
# heartbeat timeout expires, whichever happens first.
# Setting this option to true means that subscriptions are responded to
# which *may* increase time to first message for those not using websockets.
config :pixie, :subscribe_immediately, false

# Add extensions to be loaded at startup:
config :pixie, :extensions, [My.Extension.Module.Name]

# Add monitors to be loaded at startup:
config :pixie, :monitors, [
  My.Monitor.Module.Name,
  # ... or ...
  {My.Monitor.Module.Name, [some_arg]}
]

# Explicitly configure transports available to clients:
config :pixie, :enabled_transports, ~w| long-polling cross-origin-long-polling callback-polling websocket |

Using with Phoenix

You can add Pixie as a custom dispatcher rule for Phoenix with Cowboy by adding the following to your application configuration:

config :myapp, MyApp.Endpoint,
  http: [
    dispatch: [
      {:_, [
        {"/pixie", Pixie.Adapter.Cowboy.HttpHandler, {Pixie.Adapter.Plug, []}},
        {:_,       Plug.Adapters.Cowboy.Handler,     {MyApp.Endpoint, []}}
      ]}
    ]
  ]

Obviously, you can change "/pixie" to any path you wish.

Sending messages from the server

You can publish messages from within the server using Pixie.publish.

Pixie.publish "/my/channel", %{message: "Pixie is awesome!"}

Receiving messages from the server

You can subscribe to a channel and receive messages on that channel using Pixie.subscribe.

{:ok, pid} = Pixie.subscribe "/my/channel", fn (message, _pid)->
  IO.puts "Received message: #{inspect message}"
end

A separate worker process is created for each subscription, and it's pid is both returned from the subscribe call, but also passed as the second argument into the callback function, which means that you can do things like receive a single message, then unsubscribe:

Pixie.subscribe "/only_one_message", fn(message, pid)->
  IO.puts "Received message: #{inspect message}"
  Pixie.unsubscribe pid
end

Either way, you can use Pixie.unsubscribe pid to unsubscribe and terminate the subscription process.

Writing extensions

Pixie supports extensions which allow you to modify messages as they come into the server. You can write your own module and use the Pixie.Extension behaviour. Your extension needs only implement two functions:

  • incoming %Pixie.Event{} which returns a (possibly) modified event.
  • outgoing %Pixie.Message.Publish{} which returns a (possibly) modified message.

The Pixie.Event struct contains the following fields:

  • client_id: The ID of the Client. You can use this to find Pixie.Client and Pixie.Transport processes should you need to.
  • message: The incoming message from the client. Messages are represented as:
    • %Pixie.Message.Handshake{}: A client handshake request.
    • %Pixie.Message.Connect{}: A client connection request.
    • %Pixie.Message.Subscribe{}: A subscription request.
    • %Pixie.Message.Publish{}: A message to be published by the user.
    • %Pixie.Message.Unsubscribe{}: An unsubscription request.
    • %Pixie.Message.Disconnect{}: A client disconnection request.
  • response: The response to be sent back to the client. You can use the functions in Pixie.Protocol.Error (automatically imported for you) or you can modify the response directly.

The details of all these structs should be available on hexdocs.pm.

You can configure Pixie to load your extensions at start-up (as per the configuration section above) or you can add and remove them at runtime.

Pixie.ExtensionRegistry.register MyExtension

# ... and ...

Pixie.ExtensionRegistry.unregister MyExtension

Writing Monitors

Pixie provides monitoring functionality which allows you to subscribe to events which are happennings in the system.

Provided events are:

  • Client created.
  • Client destroyed.
  • Channel created.
  • Channel destroyed.
  • Client subscribed to channel.
  • Client unsubscribed from channel.
  • Message received for publication.
  • Message delivered to receiving client.

All events also receive a Timex timestamp recording the time at which they were generated - as there are potentially a lot of them and they may sit in a process mailbox for some time.

You can use the Pixie.Monitor behaviour to define your event handler:

defmodule MyMonitor do
  use Pixie.Monitor

  def created_channel channel_name, at do
    Logger.info "Channel \#\{channel_name} created at \#\{format at}"
  end

  def destroyed_channel channel_name, at do
    Logger.info "Channel \#\{channel_name} destroyed at \#\{format at}"
  end

  defp format timestamp do
    timestamp
      |> Date.from(:timestamp)
      |> DateFormat.format!("{UNIX}")
  end
end

Or you can use your own GenEvent handler, if the monitor API doesn't work for you.

You can find more information in the documentation.

Running the tests

Run mix espec.

Contributing

  1. Fork it
  2. Create your feature branch (git checkout -b my-new-feature)
  3. Commit your changes (git commit -am 'Added some feature')
  4. Push to the branch (git push origin my-new-feature)
  5. Create new Pull Request