Understanding Primus
In the last article, we took a look at Socket.IO—the popular library designed to take the pain out of realtime communications between browser and server. I mentioned before that Socket.IO is built atop Engine.IO, a realtime engine that deals with transports and communication. However, there are a number of transport abstractions available besides Engine.IO.
Primus provides a single API for both server and client side, and lets you pick what backend is used. Primus supports the following engines:
- Engine.IO
- Plain WebSockets
- Faye
- BrowserChannel
- SockJS
- Socket.IO (0.9.x)
Getting Started
First of all, we need to install Primus and a realtime framework. For this tutorial, we'll stick to Engine.IO—the same framework Socket.IO uses.
$ npm install primus engine.io --save
To get started, we need to create an HTTP server to serve the routes needed for Primus to work. Using the core "http"
module, we can use Primus like so:
var http = require("http")
var Primus = require("primus")
var options = {
transformer: "engine.io"
}
var server = http.createServer()
var primus = new Primus(server, options)
To use Primus with Express, the procedure is similar:
var express = require("express")
var http = require("http")
var Primus = require("primus")
var app = express()
var server = http.createServer(app)
var primus = Primus(server, options)
If you're using Hapi, Primus will also integrate with little work:
var Hapi = require("hapi")
var Primus = require("primus")
var server = Hapi.createServer(0)
var primus = new Primus(server.listener, options)
Primus is compatible with frameworks that expose the internal http.Server
instance. For more information, consult the documentation.
A Taste of the API
Primus' API is very easy to pick up. It comes with both a server-side and client-side API that stays consistent no matter what backend engine is used.
Server-side
To listen for connections, we can set up an event listener on our Primus
instance like so:
primus.on("connection", function (spark) {
spark.write("Hello, world!")
})
You'll notice that the callback function gets called with a "spark"
. In Primus, a Spark object represents the client's socket/connection and conforms to the Node.js Stream interface.
The spark.write()
method can take any JSON-serialisable object and send it to the client. We can also pipe data to the browser without any extra modules, since spark
is a Duplex stream.
var fs = require("fs")
primus.on("connection", function (spark) {
fs.createReadStream("./package.json").pipe(spark)
})
It's also possible to broadcast data to each connection via primus.write()
.
primus.write({some: "data"})
Client-side
You can obtain the client library for Primus by using primus.library()
on the server. It's recommended to process/minify this library in production. However, for convenience, Primus exposes the library via the /primus/primus.js
route.
To get started, serve a HTML document and append the following to the body:
<script src="/primus/primus.js"></script>
<script>
// connect to current URL
var primus = Primus.connect()
primus.on("open", function () {
console.log("Connected!")
})
primus.on("data", function (data) {
console.log("data =", data)
})
</script>
Ping / Pong Example
As a very simple demo, let's create a Primus server that takes the contents of /usr/share/dict/words
, splits it every newline and sends the words to the client. The client will then send the word back to the server.
We'll use the lstream
module developed by TJ Fontaine to split the dictionary.
$ npm install lstream --save
Server-side
When we get a connection from a client, we need to add a listener for the "data"
event. This listener simply sends the data back to the client.
var fs = require("fs")
var lstream = require("lstream")
primus.on("connection", function(spark) {
fs.createReadStream("/usr/share/dict/words")
.pipe(new lstream())
.pipe(spark)
spark.on("data", function(data), {
console.log("ping:", data)
})
})
Client-side
The client-side part of the example logs each word to the console and then sends it back to the server. Since the server has a listener set up for the "data"
event, it'll be logged there, too.
primus.on("data", function(data) {
console.log("word:", data)
primus.write(data)
})
Further Reading
Primus' purpose is to create a universal abstraction for dealing with connections/sockets from the server to the browser. Rather than worrying about specific implementation details, you can focus on writing your logic first and picking a transformer later.
We've covered the very basics of Primus in this article; you can find out more about Primus at the project's GitHub repository: https://github.com/primus/primus.