SocketStream & Node.js – for interactive single page apps – SocketStream



SocketStream & Node.js – for interactive single page apps – SocketStream

0 0


socketstream-presentation

Introduction to SocketStream, initially held at cologne.js user meeting 11.09.2012 at CoWoCo cologne

On Github ruhmesmeile / socketstream-presentation

SocketStream & Node.js

for interactive single page apps

About me

  • Lead developer at @ruhmesmeile
  • HTML / CSS / JavaScript enthusiast
  • Began using Node.js & Server-sideJavaScript about 1 1/2 years ago
  • Currently ~2-3 apps in production
  • DJ & and music nerd in general

- Jonas Ulrich / @tsnmp

WebSockets

  • Bi-directional, full-duplex communicationover single TCP connection
  • Best suited for live content and real-time games
  • Initial handshake through HTTP Upgrade request
  • Connection kept open
  • No further HTTP-overhead per message
  • Unsolicited server-to-client communication

Further information at the Mozilla Developer Network or the official RFCs.

SocketStream

Current version 0.31, MIT licensed
SocketStream is a modular Node.js web framework for building fast, responsive single-page realtime apps.

It provides the structure and basic building blocks you need to create rich social/chat apps, multiplayer games, trading platforms, sales dashboards, or any other kind of web app that needs to display realtime streaming data.

Twitter: @socketstream Google Group: http://groups.google.com/group/socketstream IRC channel: #socketstream on freenode Created by Owen Barnes (GitHub)

Motivation

Why use it?

  • Sane structure for client & server
  • Sensible defaults (Think Rails conventionover configuration)
  • Live Reload while developing
  • Asset packing and CDN support
  • Modular client-side JavaScript
  • Integrates well with popular client-sideframeworks (e.g. Backbone, Ember, Angular)

Features

Server

  • Modular WebSocket transports (Socket.IO / SockJS)
  • Shared code between client and server
  • Request middleware support
  • Scalable pub/sub built-in
  • Authentication support (using Everyauth)
  • Integrates with Connect 2.0 and Redis (optional)

Optional modules (officially maintained and supported):ss-sockjs, ss-console, ss-coffee, ss-jade, ss-stylus, ss-less, ss-hogan, ss-coffeekup

Features

Client

Demo

How to use it

code examples based on our app

Install SocketStream (prerequesites: npm & Node 0.8.x):
[sudo] npm install -g socketstream
Create a new project:
socketstream new <name_of_your_project>
Install dependencies:
cd <name_of_your_project>
[sudo] npm install
Start the app:
node app.js
Point your browser at:
http://localhost:3000

Server structure

  • client/
  • server/
    • assets/ - general assets needed on the server
      • images/
      • fonts/
      • data/
      • ...
    • middleware/ - custom / additional middleware
      • facebookAuth.js
      • zipStream.js
    • rpc/ - RPC endpoints
      • board.js
  • app.js

Client structure

  • client/
    • code/ - client-side JavaScript code
      • app/ - main client & entry point
      • libs/ - legacy (read: non common.js) libraries
      • system/ - system modules
    • css/ - CSS files
    • static/ - static assets
    • templates/ - templates in configured templating language
      • fileupload.html
      • userdetails.html
    • views/ - main views / layouts
      • app.html
  • server/
  • app.js

Adding your modules

before starting the server in app.js

Session storage:
ss.session.store.use('redis', config.db);
Server-side transport:
ss.publish.transport.use('redis');
Templating engine:
ss.client.templateEngine.use(require('ss-hogan'));
WebSocket transport:
ss.ws.transport.use(require('ss-sockjs'));
Additional middleware (e.g. bodyParser()):
ss.http.middleware.prepend(ss.http.connect.bodyParser());

Defining Clients

Register views, libraries, and files:
// Define a single-page client called 'main'
ss.client.define('main', {
  view: 'app.html',
  css:  ['app.css'],
  code: ['libs/jquery.min.js', 'app', 'system'],
  tmpl: '*'
});
and add the routing:
// Serve main client on the root URL
ss.http.route('/', function (req, res) {
  res.serveClient('main');
});

Starting the server

in app.js

// Start web server
var server = https.createServer(options, ss.http.middleware);
server.listen(443);

// Start SocketStream
ss.start(server);

Bootstrap the client

in client/code/app/entry.js

// Make 'ss' available to all modules and the browser console
window.ss = require('socketstream');
// Add handling for disconnect-events
ss.server.on('disconnect', function () {
  console.log('connection is down');
});
// Add handling for reconnect-events
ss.server.on('reconnect', function () {
  console.log('connection is back up');
});
// Initialize app when server signals ready
ss.server.on('ready', function () {
  // Wait for DOM to load
  jQuery(function () {
    require('/app').init();
  });
};

Making RPC calls

from the client

Client-side RPC-call initiation (client/code/app/app.js)
// Called from entry.js, single initialization point
exports.init = function () {
  // Get current board from server
  ss.rpc('board.current', initializeBoard);
};
Server-side RPC-call interception (server/rpc/board.js)
// Actions accessible through client/rpc (e.g. 'board.current'
// where current is the action & board the controller)
exports.actions = function(req, res, socketstream) {
  return {
    current: function () {
      res(null, calculatedResult);
    },
    // Calls can take arbitrary number of arguments
    get: function (boardId) { ... }
  }
}

Broadcasting to Clients

from the server

Server-side broadcast (server/rpc/board.js)
var notifyUsers = function (user, callback) {
  // Publish a message to all connected clients
  ss.publish.all('newUser', user);
};
Client-side RPC-call initiation (client/code/app/app.js)
ss.event.on('newUser', function (user) {
  // Handle the new user
});
Alternatively you can use channels, too
// Register for channel boardUpdates
req.session.channel.subscribe('boardUpdates')
// Publish a message to that channel
ss.publish.channel('boardUpdates', 'update', user);
// Handle that message on the client
ss.event.on('update', function(user, channelName){ ... });

Exposing modules to SS

To share modules / state over multiple open Socket connections, just add them to the SocketStream-API. This is done in app.js, before starting the server
// Add database connection to the SS.api
ss.api.add('db', client);
// Add enviroment const to the SS.api
ss.api.add('enviroment', env);
The API is accessible in every RPC-call (server/rpc/board.js)
// Access the database
ss.db.get('boards:'+boardId+':positions:'+position,
  function (err, userId) { ... }
);

// Or access the environment
if (ss.environment === "production") { ... }

Session Management

Sessions can be shared between the initial HTTP-request and the WebSocket-connection. For every RPC-call that should load session-data the following has to be added:

// Add ss-session middleware, so we have access to the
// shared session (especially session.facebook,
// set during initial http-request)
exports.actions = function(req, res, socketstream) {
  req.use('session');

  return {
    ...
  }
}

Production gotchas

  • ulimit should be increased to a reasonable value on the server, to allow a high number of concurrent connections
  • For Internet Explorer support, a P3P-header has to be included in the first response in order for sessions to work
    ss.http.route('/', function (req, res) {
      res.setHeader('P3P', 'CP="IDC DSP ..."');
      res.serveClient('main');
    });
  • Heroku doesn't currently support WebSockets

SocketStream 0.4

Lessons learnt from 0.3 & built upon:
  • New Stream API to pipe() news feeds / etcover the websocket
  • Improved Request Responder API
  • Bundled with Engine.IO by default(replaces Socket.IO)
  • Option to Gzip assets
  • Server-side event bus
  • Improved architecture, faster than 0.3
  • Complete switch from CoffeeScript to JavaScript

Thanks

Any questions?

Slides were written using reveal.js