Upgrade to Pro — share decks privately, control downloads, hide ads and more …

WebSockets

Igor Wiedler
January 28, 2012

 WebSockets

Talk on WebSockets for the PHPBenelux Conference 2012.

Igor Wiedler

January 28, 2012
Tweet

More Decks by Igor Wiedler

Other Decks in Programming

Transcript

  1. WEB

  2. • Headers • Content-Types • Caching • Authentication • Transactions

    • Stateless this provides you with lots of useful tools
  3. BUT

  4. Application HTTP Presentation TCP Session TCP Transport TCP Network IP

    Data Link Physical tcp has sessions http throws them away
  5. var ws = new WebSocket('ws://example.org:8080/updates'); ws.onopen = function () {

    ws.send('hello'); }; ws.onmessage = function (message) { console.log(message.data); };
  6. JS INTERFACE • WebSocket • onopen • onmessage • onerror

    • onclose • binaryType • MessageEvent • data
  7. $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
  8. $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);
  9. $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);
  10. $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);
  11. 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); });
  12. 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
  13. 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); });
  14. 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
  15. 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); });
  16. 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
  17. 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')); } }
  18. 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
  19. 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); }); });
  20. 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); }); });
  21. 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); }); });
  22. 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); }); });
  23. 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); }
  24. 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); }
  25. 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
  26. <div class="messages"></div> <form> <input type="text" name="message"> <button type="submit">Send</button> </form> <script

    src="http://localhost:8080/socket.io/socket.io.js"></script> <script src="jquery-1.7.min.js"></script> <script src="chat.js"></script>
  27. var socket = io.connect('http://localhost:8080'), messages = $('.messages'); socket.on('message', function (message)

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

    { var messageHtml = $('<div></div>').text(message.body); messages.append(messageHtml); messages.prop('scrollTop', 100000); });
  29. 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(''); });
  30. 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(''); });
  31. 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); }); });
  32. 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
  33. 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); } }); });
  34. 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
  35. 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); }); });
  36. 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); }); });
  37. 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); }); });
  38. 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); }); });
  39. 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
  40. 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
  41. 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