Slide 1

Slide 1 text

WEBSOCKETS

Slide 2

Slide 2 text

• phpBB • Symfony2 • Silex igorw

Slide 3

Slide 3 text

No content

Slide 4

Slide 4 text

WEBSOCKETS

Slide 5

Slide 5 text

WEB

Slide 6

Slide 6 text

Application HTTP Presentation TCP Session TCP Transport TCP Network IP Data Link Physical

Slide 7

Slide 7 text

Application HTTP Presentation TCP Session TCP Transport TCP Network IP Data Link Physical the web stack

Slide 8

Slide 8 text

HTTP

Slide 9

Slide 9 text

client

Slide 10

Slide 10 text

request client

Slide 11

Slide 11 text

reponse client request web is built around client-server

Slide 12

Slide 12 text

THIS IS A GOOD THING

Slide 13

Slide 13 text

• Headers • Content-Types • Caching • Authentication • Transactions • Stateless this provides you with lots of useful tools

Slide 14

Slide 14 text

BUT

Slide 15

Slide 15 text

NOT FOR EVERYTHING event-driven apps games streaming

Slide 16

Slide 16 text

UNIDIRECTIONAL

Slide 17

Slide 17 text

are we there yet? polling

Slide 18

Slide 18 text

LATENCY lots of overhead and processing

Slide 19

Slide 19 text

STATELESS sessions are re- implemented with cookies

Slide 20

Slide 20 text

Application HTTP Presentation TCP Session TCP Transport TCP Network IP Data Link Physical

Slide 21

Slide 21 text

Application HTTP Presentation TCP Session TCP Transport TCP Network IP Data Link Physical tcp has sessions http throws them away

Slide 22

Slide 22 text

WEBSOCKETS

Slide 23

Slide 23 text

SOCKETS persistent connection between two machines

Slide 24

Slide 24 text

WEBSOCKETS server-push

Slide 25

Slide 25 text

PROTOCOL API

Slide 26

Slide 26 text

THE WEBSOCKET PROTOCOL

Slide 27

Slide 27 text

browser browser- server

Slide 28

Slide 28 text

No content

Slide 29

Slide 29 text

RFC 6455

Slide 30

Slide 30 text

bi-directional

Slide 31

Slide 31 text

low-latency

Slide 32

Slide 32 text

stateful

Slide 33

Slide 33 text

• Handshake / Upgrade • TCP • Proxies • Supports TLS (SSL)

Slide 34

Slide 34 text

COMET legacy push hacks websockets before they were cool

Slide 35

Slide 35 text

THE WEBSOCKET API

Slide 36

Slide 36 text

browser app-browser

Slide 37

Slide 37 text

No content

Slide 38

Slide 38 text

var ws = new WebSocket('ws://example.org:8080/updates'); ws.onopen = function () { ws.send('hello'); }; ws.onmessage = function (message) { console.log(message.data); };

Slide 39

Slide 39 text

JS INTERFACE • WebSocket • onopen • onmessage • onerror • onclose • binaryType • MessageEvent • data

Slide 40

Slide 40 text

browser API PROTOCOL

Slide 41

Slide 41 text

No content

Slide 42

Slide 42 text

• built on node.js • abstracts socket communication • supports fallback transports

Slide 43

Slide 43 text

• session storage • namespacing • acknowledgement • broadcasting • reconnection

Slide 44

Slide 44 text

• json serialization • heartbeats • connection ids • io.sockets collection

Slide 45

Slide 45 text

• browser support flash fallback supports everything!

Slide 46

Slide 46 text

Y U NO PHP? apache and php suck at long-running connections

Slide 47

Slide 47 text

zero-broker messaging

Slide 48

Slide 48 text

No content

Slide 49

Slide 49 text

$context = new ZMQContext(); $sock = $context->getSocket(ZMQ::SOCKET_PUB); $sock->connect('tcp://127.0.0.1:5555'); $msg = json_encode([ 'type' => 'debug', 'data' => ['foo', 'bar', 'baz'] ]); $sock->send($msg); basic php zmq client

Slide 50

Slide 50 text

$context = new ZMQContext(); $sock = $context->getSocket(ZMQ::SOCKET_PUB); $sock->connect('tcp://127.0.0.1:5555'); $msg = json_encode([ 'type' => 'debug', 'data' => ['foo', 'bar', 'baz'] ]); $sock->send($msg);

Slide 51

Slide 51 text

$context = new ZMQContext(); $sock = $context->getSocket(ZMQ::SOCKET_PUB); $sock->connect('tcp://127.0.0.1:5555'); $msg = json_encode([ 'type' => 'debug', 'data' => ['foo', 'bar', 'baz'] ]); $sock->send($msg);

Slide 52

Slide 52 text

$context = new ZMQContext(); $sock = $context->getSocket(ZMQ::SOCKET_PUB); $sock->connect('tcp://127.0.0.1:5555'); $msg = json_encode([ 'type' => 'debug', 'data' => ['foo', 'bar', 'baz'] ]); $sock->send($msg);

Slide 53

Slide 53 text

No content

Slide 54

Slide 54 text

var io = require('socket.io').listen(8080), zmq = require('zmq'), sock = zmq.socket('sub'); sock.subscribe(''); sock.bind('tcp://*:5555'); sock.on('message', function (msg) { var event = JSON.parse(msg); io.sockets.emit(event.type, event.data); });

Slide 55

Slide 55 text

var io = require('socket.io').listen(8080), zmq = require('zmq'), sock = zmq.socket('sub'); sock.subscribe(''); sock.bind('tcp://*:5555'); sock.on('message', function (msg) { var event = JSON.parse(msg); io.sockets.emit(event.type, event.data); }); listens for socket.io on port 8080

Slide 56

Slide 56 text

var io = require('socket.io').listen(8080), zmq = require('zmq'), sock = zmq.socket('sub'); sock.subscribe(''); sock.bind('tcp://*:5555'); sock.on('message', function (msg) { var event = JSON.parse(msg); io.sockets.emit(event.type, event.data); });

Slide 57

Slide 57 text

var io = require('socket.io').listen(8080), zmq = require('zmq'), sock = zmq.socket('sub'); sock.subscribe(''); sock.bind('tcp://*:5555'); sock.on('message', function (msg) { var event = JSON.parse(msg); io.sockets.emit(event.type, event.data); }); listens for zmq on port 5555

Slide 58

Slide 58 text

var io = require('socket.io').listen(8080), zmq = require('zmq'), sock = zmq.socket('sub'); sock.subscribe(''); sock.bind('tcp://*:5555'); sock.on('message', function (msg) { var event = JSON.parse(msg); io.sockets.emit(event.type, event.data); });

Slide 59

Slide 59 text

var io = require('socket.io').listen(8080), zmq = require('zmq'), sock = zmq.socket('sub'); sock.subscribe(''); sock.bind('tcp://*:5555'); sock.on('message', function (msg) { var event = JSON.parse(msg); io.sockets.emit(event.type, event.data); }); pass through messages from zmq to socket.io

Slide 60

Slide 60 text

var socket = io.connect('http://localhost:8080'); socket.on('debug', function (data) { console.log(data); });

Slide 61

Slide 61 text

LE CODE •blog comments •chat •canvas

Slide 62

Slide 62 text

LE CODE •blog comments •chat •canvas

Slide 63

Slide 63 text

No content

Slide 64

Slide 64 text

class CommentController extends Controller { public function createAction(Post $post) { $comment = new Comment(); $comment->setPost($post); $request = $this->getRequest(); $form = $this->createForm(new CommentType(), $comment); $form->bindRequest($request); if ($form->isValid()) { $em = $this->getDoctrine()->getEntityManager(); $em->persist($comment); $em->flush(); $renderedRow = $this->renderView( 'AcmeBlogBundle:Comment:row.html.twig', array('comment' => $comment) ); return new Response($renderedRow, 201); } return new Response('{}', 400, array('Content-Type' => 'application/json')); } }

Slide 65

Slide 65 text

class CommentController extends Controller { public function createAction(Post $post) { $comment = new Comment(); $comment->setPost($post); $request = $this->getRequest(); $form = $this->createForm(new CommentType(), $comment); $form->bindRequest($request); if ($form->isValid()) { $em = $this->getDoctrine()->getEntityManager(); $em->persist($comment); $em->flush(); $renderedRow = $this->renderView( 'AcmeBlogBundle:Comment:row.html.twig', array('comment' => $comment) ); return new Response($renderedRow, 201); } return new Response('{}', 400, array('Content-Type' => 'application/json')); } } ajax response

Slide 66

Slide 66 text

var renderComment = function (renderedRow) { $('.comments').append(renderedRow); }; $('.comment-form form').submit(function (event) { event.preventDefault(); var form = $(this), action = form.attr('action'), data = form.serialize(); $.post(action, data, function (comment) { renderComment(comment); resetForm(form); }); });

Slide 67

Slide 67 text

var renderComment = function (renderedRow) { $('.comments').append(renderedRow); }; $('.comment-form form').submit(function (event) { event.preventDefault(); var form = $(this), action = form.attr('action'), data = form.serialize(); $.post(action, data, function (comment) { renderComment(comment); resetForm(form); }); });

Slide 68

Slide 68 text

var renderComment = function (renderedRow) { $('.comments').append(renderedRow); }; $('.comment-form form').submit(function (event) { event.preventDefault(); var form = $(this), action = form.attr('action'), data = form.serialize(); $.post(action, data, function (comment) { renderComment(comment); resetForm(form); }); });

Slide 69

Slide 69 text

var renderComment = function (renderedRow) { $('.comments').append(renderedRow); }; $('.comment-form form').submit(function (event) { event.preventDefault(); var form = $(this), action = form.attr('action'), data = form.serialize(); $.post(action, data, function (comment) { renderComment(comment); resetForm(form); }); });

Slide 70

Slide 70 text

Secret Sauce

Slide 71

Slide 71 text

private function publishCommentCreate(Comment $comment) { $context = new \ZMQContext(); $sock = $context->getSocket(\ZMQ::SOCKET_PUB); $sock->connect('tcp://127.0.0.1:5555'); $renderedRow = $this->renderView( 'AcmeBlogBundle:Comment:row.html.twig', array('comment' => $comment) ); $postId = $comment->getPost()->getId(); $msg = json_encode(array( 'type' => "post.$postId.comment.create", 'data' => $renderedRow, )); $sock->send($msg); }

Slide 72

Slide 72 text

private function publishCommentCreate(Comment $comment) { $context = new \ZMQContext(); $sock = $context->getSocket(\ZMQ::SOCKET_PUB); $sock->connect('tcp://127.0.0.1:5555'); $renderedRow = $this->renderView( 'AcmeBlogBundle:Comment:row.html.twig', array('comment' => $comment) ); $postId = $comment->getPost()->getId(); $msg = json_encode(array( 'type' => "post.$postId.comment.create", 'data' => $renderedRow, )); $sock->send($msg); }

Slide 73

Slide 73 text

private function publishCommentCreate(Comment $comment) { $context = new \ZMQContext(); $sock = $context->getSocket(\ZMQ::SOCKET_PUB); $sock->connect('tcp://127.0.0.1:5555'); $renderedRow = $this->renderView( 'AcmeBlogBundle:Comment:row.html.twig', array('comment' => $comment) ); $postId = $comment->getPost()->getId(); $msg = json_encode(array( 'type' => "post.$postId.comment.create", 'data' => $renderedRow, )); $sock->send($msg); } per-post event

Slide 74

Slide 74 text

$this->publishCommentCreate($comment);

Slide 75

Slide 75 text

var socket = io.connect('http://localhost:8080'); socket.on('post.'+postId+'.comment.create', function (data) { renderComment(data); });

Slide 76

Slide 76 text

LE CODE •blog comments •chat •canvas

Slide 77

Slide 77 text

No content

Slide 78

Slide 78 text

Send

Slide 79

Slide 79 text

var socket = io.connect('http://localhost:8080'), messages = $('.messages'); socket.on('message', function (message) { var messageHtml = $('
').text(message.body); messages.append(messageHtml); messages.prop('scrollTop', 100000); });

Slide 80

Slide 80 text

var socket = io.connect('http://localhost:8080'), messages = $('.messages'); socket.on('message', function (message) { var messageHtml = $('
').text(message.body); messages.append(messageHtml); messages.prop('scrollTop', 100000); });

Slide 81

Slide 81 text

var form = $('form'); form.submit(function (event) { event.preventDefault(); var input = form.find('*[name=message]'), body = input.val(); if (!body.length) { return; } socket.emit('message', { name: name, body: body }); input.val(''); });

Slide 82

Slide 82 text

var form = $('form'); form.submit(function (event) { event.preventDefault(); var input = form.find('*[name=message]'), body = input.val(); if (!body.length) { return; } socket.emit('message', { name: name, body: body }); input.val(''); });

Slide 83

Slide 83 text

var io = require('socket.io').listen(8080); io.sockets.on('connection', function (socket) { socket.on('message', function (message) { io.sockets.emit('message', message); }); });

Slide 84

Slide 84 text

var io = require('socket.io').listen(8080), messages; messages = []; io.sockets.on('connection', function (socket) { messages.forEach(function (message) { socket.emit('message', message); }); socket.on('message', function (message) { io.sockets.emit('message', message); messages.push(message); }); });

Slide 85

Slide 85 text

var io = require('socket.io').listen(8080), messages; messages = []; io.sockets.on('connection', function (socket) { messages.forEach(function (message) { socket.emit('message', message); }); socket.on('message', function (message) { io.sockets.emit('message', message); messages.push(message); }); }); buffer messages and send them

Slide 86

Slide 86 text

var io = require('socket.io').listen(8080), bufferLimit = 15, messages; messages = []; io.sockets.on('connection', function (socket) { messages.forEach(function (message) { socket.emit('message', message); }); socket.on('message', function (message) { io.sockets.emit('message', message); messages.push(message); if (messages.length > bufferLimit) { messages = messages.splice(-bufferLimit, bufferLimit); } }); });

Slide 87

Slide 87 text

var io = require('socket.io').listen(8080), bufferLimit = 15, messages; messages = []; io.sockets.on('connection', function (socket) { messages.forEach(function (message) { socket.emit('message', message); }); socket.on('message', function (message) { io.sockets.emit('message', message); messages.push(message); if (messages.length > bufferLimit) { messages = messages.splice(-bufferLimit, bufferLimit); } }); }); limit the size of the buffer to not blow up the server

Slide 88

Slide 88 text

LE CODE •blog comments •chat •canvas

Slide 89

Slide 89 text

No content

Slide 90

Slide 90 text

Paintbrush Canvas mousedown mousemove mouseup WS mousedown mousemove mouseup Paintbrush mousedown mousemove mouseup

Slide 91

Slide 91 text

var io = require('socket.io').listen(8080); io.sockets.on('connection', function (socket) { var color = '#'+Math.floor(Math.random()*16777215).toString(16); socket.emit('color', color); socket.on('mousedown', function (event) { event.source = socket.id; event.color = color; socket.broadcast.emit('mousedown', event); }); socket.on('mousemove', function (event) { event.source = socket.id; socket.broadcast.emit('mousemove', event); }); socket.on('mouseup', function (event) { event.source = socket.id; socket.broadcast.emit('mouseup', event); }); });

Slide 92

Slide 92 text

var io = require('socket.io').listen(8080); io.sockets.on('connection', function (socket) { var color = '#'+Math.floor(Math.random()*16777215).toString(16); socket.emit('color', color); socket.on('mousedown', function (event) { event.source = socket.id; event.color = color; socket.broadcast.emit('mousedown', event); }); socket.on('mousemove', function (event) { event.source = socket.id; socket.broadcast.emit('mousemove', event); }); socket.on('mouseup', function (event) { event.source = socket.id; socket.broadcast.emit('mouseup', event); }); });

Slide 93

Slide 93 text

var io = require('socket.io').listen(8080); io.sockets.on('connection', function (socket) { var color = '#'+Math.floor(Math.random()*16777215).toString(16); socket.emit('color', color); socket.on('mousedown', function (event) { event.source = socket.id; event.color = color; socket.broadcast.emit('mousedown', event); }); socket.on('mousemove', function (event) { event.source = socket.id; socket.broadcast.emit('mousemove', event); }); socket.on('mouseup', function (event) { event.source = socket.id; socket.broadcast.emit('mouseup', event); }); });

Slide 94

Slide 94 text

var io = require('socket.io').listen(8080); io.sockets.on('connection', function (socket) { var color = '#'+Math.floor(Math.random()*16777215).toString(16); socket.emit('color', color); socket.on('mousedown', function (event) { event.source = socket.id; event.color = color; socket.broadcast.emit('mousedown', event); }); socket.on('mousemove', function (event) { event.source = socket.id; socket.broadcast.emit('mousemove', event); }); socket.on('mouseup', function (event) { event.source = socket.id; socket.broadcast.emit('mouseup', event); }); });

Slide 95

Slide 95 text

var io = require('socket.io').listen(8080); io.sockets.on('connection', function (socket) { var color = '#'+Math.floor(Math.random()*16777215).toString(16); socket.emit('color', color); socket.on('mousedown', function (event) { event.source = socket.id; event.color = color; socket.broadcast.emit('mousedown', event); }); socket.on('mousemove', function (event) { event.source = socket.id; socket.broadcast.emit('mousemove', event); }); socket.on('mouseup', function (event) { event.source = socket.id; socket.broadcast.emit('mouseup', event); }); }); broadcast that shit

Slide 96

Slide 96 text

USE CASES •Games •Notifications •Collaboration •Statistics •Chat stack overflow travis-ci twitter

Slide 97

Slide 97 text

PATTERNS • pub/sub • ajax-triggered messages • server authority • server vs client-side templating

Slide 98

Slide 98 text

SECURITY • Security model • Proxy poisoning • Authentication • Encrypted transport (TLS/SSL) cross-site possible server receives origin header for auth, use cookies or pass a session id

Slide 99

Slide 99 text

ACHTUNG • beware of the state • re-starting the server • scaling

Slide 100

Slide 100 text

Push only?

Slide 101

Slide 101 text

EventSource.

Slide 102

Slide 102 text

WEBSOCKET LIBRARIES • socket.io • node: Worlize/WebSocket-Node • ruby: igrigorik/em-websocket • php: sebcode/php-websocketserver • js: gimite/web-socket-js that last one is the amazing flash client

Slide 103

Slide 103 text

LINKS • tools.ietf.org/html/rfc6455 • dev.w3.org/html5/websockets • developer.mozilla.org/en/WebSockets • socket.io • github.com/igorw/websockets-talk

Slide 104

Slide 104 text

github.com/igorw/websockets-talk all the code from this talk

Slide 105

Slide 105 text

CREDITS • thenounproject.com • stopwatch • lightning • sync • refresh

Slide 106

Slide 106 text

Questions? @igorwesome speakerdeck.com /u/igorw joind.in/4763