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

Sometimes WebSockets don't work

Sometimes WebSockets don't work

WebSockets have been with us for quite a while, but there are still people using ancient browsers or being held back by selfish proxies. Because of that, it's difficult to find out which type of connection is best for a client, so Pusher needed a flexible way to tell clients how to establish a real-time connection efficiently and also monitor their behaviour. This talk will give you an overview on how it works, what interesting metrics we got out of it and how we evaluated them.

Paweł Ledwoń

March 13, 2013
Tweet

More Decks by Paweł Ledwoń

Other Decks in Programming

Transcript

  1. Example var websocket = new WebSocket("ws://example.com"); websocket.onopen = function() {

    websocket.send("Howdy!"); }; websocket.onmessage = function(message) { console.log("BEEP", message.data); };
  2. Flash is not bad! when it’s connected it has all

    advantages of WebSockets just takes more time to connect
  3. Problem Jenny works for a bank. Her IT department thinks

    closing port 843 will make her more productive.
  4. Problem Jenny works for a bank. Her IT department thinks

    closing port 843 will make her more productive. Strategy Can’t detect the problem without waiting. Should try Flash and SockJS in parallel.
  5. Problem Larry is on a 3G network. Unfortunately, it kills

    WebSockets after 60 seconds. Strategy WebSockets and Flash should be disabled after noticing failed connections.
  6. Problem Suzzy has a Windows PC with Über Antivirus©; It

    thinks WebSockets will steal her credit card.
  7. Problem Strategy Should fall back to SockJS. Should also remember

    not to try WebSockets. Suzzy has a Windows PC with Über Antivirus©; It thinks WebSockets will steal her credit card.
  8. Problem Jeff is on a network which inspects HTTP traffic.

    WebSockets don’t work when unencrypted.
  9. Problem Strategy Should encrypt the connection or use SockJS. Jeff

    is on a network which inspects HTTP traffic. WebSockets don’t work when unencrypted.
  10. Strategy which transport to use? how to behave after connecting?

    how to detect failures? what to do if a transport fails?
  11. Responsibilities detect browser features connect in parallel retry attempts support

    delays cache transport info detect disconnections disable transports support timeouts
  12. Transport state machine new initialized initializing connecting open closed initialize

    initialize connect close* close* * transport can close itself
  13. Example var strategy = new OuterStrategy(new InnerStrategy()); var runner =

    strategy.connect(function(error, connection) { if (error) { console.log("Failed", error); } else { console.log("Connected", connection); } }); runner.abort();
  14. Example var strategy = new OuterStrategy(new InnerStrategy()); var runner =

    strategy.connect(function(error, connection) { if (error) { console.log("Failed", error); } else { console.log("Connected", connection); } }); runner.abort(); var strategy = new OuterStrategy(new InnerStrategy()); strategies can be composed
  15. Example var strategy = new OuterStrategy(new InnerStrategy()); var runner =

    strategy.connect(function(error, connection) { if (error) { console.log("Failed", error); } else { console.log("Connected", connection); } }); runner.abort(); var runner = strategy.connect(function(error, connection) { execution state encapsulated in the runner
  16. Example var strategy = new OuterStrategy(new InnerStrategy()); var runner =

    strategy.connect(function(error, connection) { if (error) { console.log("Failed", error); } else { console.log("Connected", connection); } }); runner.abort(); var runner = strategy.connect(function(error, connection) { errors and connections are passed to the callback
  17. Example var strategy = new OuterStrategy(new InnerStrategy()); var runner =

    strategy.connect(function(error, connection) { if (error) { console.log("Failed", error); } else { console.log("Connected", connection); } }); runner.abort(); if (error) { console.log("Failed", error); } strategies fail only once…
  18. Example var strategy = new OuterStrategy(new InnerStrategy()); var runner =

    strategy.connect(function(error, connection) { if (error) { console.log("Failed", error); } else { console.log("Connected", connection); } }); runner.abort(); else { console.log("Connected", connection); } …but can call back multiple times with connections
  19. Example var strategy = new OuterStrategy(new InnerStrategy()); var runner =

    strategy.connect(function(error, connection) { if (error) { console.log("Failed", error); } else { console.log("Connected", connection); } }); runner.abort(); runner.abort(); strategy execution can be aborted anytime
  20. ws sockjs transport transport delayed sequential sequential best connected there’s

    no point in using SockJS after a WebSocket connected choose the best transport
  21. ws sockjs transport transport if ws.isSupported() delayed sequential sequential best

    connected true we only want to do this if WebSockets are supported
  22. ws sockjs transport transport if ws.isSupported() delayed sequential sequential best

    connected sockjs transport sequential true false otherwise use SockJS without a delay
  23. ws sockjs transport transport if ws.isSupported() delayed sequential sequential best

    connected cached sockjs transport sequential true false cache connected transport name
  24. DSL

  25. Non-DSL var http = require("http"); var url = require("url"); var

    server = http.createServer(function(request, response) { var requestURL = url.parse(request.url); if (request.method === "GET") { var match = requestURL.pathname.match(/\/hello\/(.+)/); if (match) { response.writeHead(200); response.end("Hello, " + match[1] + "!"); return; } } response.writeHead(404); response.end(); });
  26. DSL var express = require("express"); var app = express(); app.get("/hello/:name"

    function(request, response) { response.send("Hello, " + request.params.name + "!"); });
  27. DSL should be 1. readable for humans 2. easy to

    parse in JavaScript 3. possible to send over HTTP 4. trivial to modify
  28. Function Nesting [“:mult”, 3, [“:add”, 2, 3]] nested expressions are

    evaluated first the result is passed as an argument // 3 * (2 + 3)
  29. Strategy Functions [“:is_supported”, “:transport”] [“:if”, test, true_branch, false_branch] [“:sequential”, params,

    strategy1, strategy2, …] [“:delayed”, delay, strategy] [“:best_connected_ever”, strategy1, strategy2, …] [“:first_connected”, strategy] [“:cached”, ttl, strategy]
  30. Initial Context var context = { def: function(...) { ...

    }, def_transport: function(...) { ... }, is_supported: function(...) { ... }, if: function(...) { ... }, sequential: function(...) { ... }, delayed: function(...) { ... }, best_connected_ever: function(...) { ... }, first_connected: function(...) { ... }, cached: function(...) { ... } };
  31. Parameters [":def", "ws_options", { hostUnencrypted: Pusher.host + ":" + Pusher.ws_port,

    hostEncrypted: Pusher.host + ":" + Pusher.wss_port }] [":def", "sockjs_options", { hostUnencrypted: Pusher.sockjs_host + ":" + Pusher.sockjs_http_port, hostEncrypted: Pusher.sockjs_host + ":" + Pusher.sockjs_https_port }] [":def", "timeouts", { loop: true, timeout: 15000, timeoutLimit: 60000 }]
  32. Transports [":def", "ws_manager", [":transport_manager", { lives: 2 }]], [":def_transport", "ws",

    "ws", 3, ":ws_options", ":ws_manager"], [":def_transport", "flash", "flash", 2, ":ws_options", ":ws_manager"], [":def_transport", "sockjs", "sockjs", 1, ":sockjs_options"], [":def", "ws_loop", [":sequential", ":timeouts", ":ws"]], [":def", "flash_loop", [":sequential", ":timeouts", ":flash"]], [":def", "sockjs_loop", [":sequential", ":timeouts", ":sockjs"]], disables transports after getting disconnected twice
  33. Transports [":def", "ws_manager", [":transport_manager", { lives: 2 }]], [":def_transport", "ws",

    "ws", 3, ":ws_options", ":ws_manager"], [":def_transport", "flash", "flash", 2, ":ws_options", ":ws_manager"], [":def_transport", "sockjs", "sockjs", 1, ":sockjs_options"], [":def", "ws_loop", [":sequential", ":timeouts", ":ws"]], [":def", "flash_loop", [":sequential", ":timeouts", ":flash"]], [":def", "sockjs_loop", [":sequential", ":timeouts", ":sockjs"]], declare loops with timeouts to avoid repetition
  34. Strategy [":cached", 1800000, [":first_connected", [":if", [":is_supported", ":ws"], [":best_connected_ever", ":ws_loop", [":delayed",

    2000, [":sockjs_loop"]]], [":if", [":is_supported", ":flash"], [":best_connected_ever", ":flash_loop", [":delayed", 2000, [":sockjs_loop"]]], // else [":sockjs_loop"] ]] ] ]
  35. Strategy [":cached", 1800000, [":first_connected", [":if", [":is_supported", ":ws"], [":best_connected_ever", ":ws_loop", [":delayed",

    2000, [":sockjs_loop"]]], [":if", [":is_supported", ":flash"], [":best_connected_ever", ":flash_loop", [":delayed", 2000, [":sockjs_loop"]]], // else [":sockjs_loop"] ]] ] ] WebSockets Flash SockJS
  36. Strategy [":cached", 1800000, [":first_connected", [":if", [":is_supported", ":ws"], [":best_connected_ever", ":ws_loop", [":delayed",

    2000, [":sockjs_loop"]]], [":if", [":is_supported", ":flash"], [":best_connected_ever", ":flash_loop", [":delayed", 2000, [":sockjs_loop"]]], // else [":sockjs_loop"] ]] ] ] delayed SockJS fallback for WebSocket branch delayed SockJS fallback for Flash branch
  37. Strategy [":cached", 1800000, [":first_connected", [":if", [":is_supported", ":ws"], [":best_connected_ever", ":ws_loop", [":delayed",

    2000, [":sockjs_loop"]]], [":if", [":is_supported", ":flash"], [":best_connected_ever", ":flash_loop", [":delayed", 2000, [":sockjs_loop"]]], // else [":sockjs_loop"] ]] ] ] we don’t support switching connections yet take the first one and finish
  38. Strategy [":cached", 1800000, [":first_connected", [":if", [":is_supported", ":ws"], [":best_connected_ever", ":ws_loop", [":delayed",

    2000, [":sockjs_loop"]]], [":if", [":is_supported", ":flash"], [":best_connected_ever", ":flash_loop", [":delayed", 2000, [":sockjs_loop"]]], // else [":sockjs_loop"] ]] ] ] cache last used transport for 30 minutes
  39. WebSockets vs HTTP successful connections 0 17500 35000 52500 70000

    WebSockets Flash SockJS first connection last connection
  40. WebSockets vs Flash latency 50% 75% 90% 95% 99% 680ms

    1450ms 1074ms 1969ms 1985ms 3199ms 3137ms 4932ms 7888ms 10971ms WebSockets Flash
  41. Browser Wars IE6 IE7 IE8 IE9 IE10 Chrome Safari Firefox

    Opera Mobile 131 (0.11%) 4668 (3.96%) 8706 (7.40%) 8007 (6.80%) 2049 (1.74%) 47692 (40.52%) 3093 (2.62%) 19316 (16.41%) 1302 (1.11%) 22759 (19.33%)