Ville Lautanala describes different transport channels that allow pushing data from servers to clients in real time.
He also introduces a case study of Flowdock's experience with socket.io and WebSockets.
Presentation from Frontend Finland meetup, March 14th. A slightly modified version was presented at SFJS, April 3rd.
5. Stream changes
from backend
And send messages
* stream and apply changes
* send some messages to server
=> To achieve this, some kind of transport channel between server and client is necessary.
6. Long-polling
XHR-streaming
HTMLFile
Server-Sent Events
WebSocket
JSONP streaming
XHR multipart
Each have their shortcomings, work on different browsers, and some work cross-domain,
others not.
7. 3 implementations
with 7 transports
We couldn’t really decide what to use. There isn’t really a one-size fits all option, but you
definitely could do with less.
8. Long-polling
HTTP/1.1 200 OK
Content-Type: application/json;
[{'content': 'json'}]
The most obvious choice, works on every browser. Wait until you have something to tell to
client. Rinse, repeat.
9. XHR-streaming
HTTP/1.1 200 OK
{'content': 'json'}
{'content': 'json'}
{'content': 'json'}
Streaming: multiple chunks in one response. Widely used, not much advertised.
XHR-streaming works by parsing the response after each onReadyStateChange.
ResponseText will be huge if your HTTP request runs for long time, so periodic reconnection
is needed. Flowdock uses 60 seconds, requests have payload about user activity.
10. Server-Sent Events
HTTP/1.1 200 OK
Content-Type: text/event-stream
data: {'content': 'json'}
id: 2 Sent to server on reconnect
data: {'content': 'json'}
SSE aka ES is standardization of XHR streaming pattern. ID attribute allows seamless
reconnections from client point-of-view.
Hopefully will be in FD mobile soon.
11. In JavaScript
var es = new EventSource('/events');
es.onmessage = function (e) {
console.log(JSON.parse(e.data));
};
There’s actually something worth showing. Simple, bind message event listener.
Other events too
14. No Android
Unlike iOS, Andriod doesn’t have ES. Even Android 4.0 is missing this. Maybe Chrome for
Android will fix this.
15. Use shim instead
https://github.com/Yaffle/EventSource
Because SSE are “just” HTTP, you can implement it in JavaScript, today. And many have done
it. Use some available shim.
16. Streams are
unidirectional
XHR requests are needed to post data
17. Sockets are
bidirectional
Some kind of socket interface would be nice. Luckily for us, WebSockets are part of HTML5.
18. WebSockets
var socket = new WebSocket('/socket');
socket.onmessage = function (e) {
console.log(JSON.parse(e.data));
};
socket.onopen = function() {
var m = {'content': 'json'};
socket.send(JSON.stringify(m));
}
I’m not going to exaplain the WebSocket protocol here, the standardized version is a bit more
complicated than what we saw previously. The JavaScript API is more interesting.
Receiving messages with websockets is pretty similar to EventSource. Sending messages is
trivial as long as you have received onopen.
19. Caveats
• Limited browser support
• Proxy problems
• Safari crashes when used with Proxy
Auto-Configuration
Safari has different protocol than others, IE 10 will be the first IE to support WS. NO SHIMS
WebSockets require full HTTP 1.1.
No way to detect if user has PAC. Rare.
20. Socket.IO
“It's care-free realtime
100% in JavaScript.”
Abstraction layer on top of different transports. NO SSE. Server-side and client-side.
We’ve been rolling Socket.IO out slowly for last month or so (bugs!).
21. Bugs
• Reconnection race-conditions
• Infinite loops with XHR-polling
• DOM Exception 11
*Socket.IO didn’t know that there already was a reconnection attempt on going and
established multiple connections.
* XHR-polling transport client could get in state where it didn’t handshake properly but only
hammered the XHR-polling endpoint.
* INVALID_STATE_ERR, send data with unopened websocket. Yet another race-condition.
25. Latency matters
We knew websockets would be fastest, but by how much?
Benchmark by sending a message and test how long it took to receive it
26. 300
225
150
75
0
Ping XHR-streaming WebSocket
Round-trip latency
Baseline: ping to our servers in Germany was 52ms
XHR: 240ms. TCP and especially SSL handshake is expensive. Streaming part doesn’t add
much penalty.
WebSockets came pretty close to ping, about 61ms. Node proxy in test setup + our backend
server added latency.
27. Real-world metrics
from Flowdock
How many can use websockets? Clients as guinea pigs.
28. 96% use
WebSockets
No Flash fallback
Over 80% have up-to-date Webkit based browsers.
30. 1% have network
issues
(or have disabled WebSockets)
We don’t really know.
31. Network still sucks
Can’t just wait for reply even with websockets.
Network latencies vary, we have clients all around the world.
32. Render
optimistically
You have two basic choices, either use loading indicator or render users changes
optimistically. Needless to say, we try to be on the optimistic end of the road.
33. Basic operations in
Flowdock
1.Post new stuff
2.Receive new stuff
3.Modify existing stuff
Post new stuff: send messages
Receive: simple, just append to the stream. unless conflicts
Modify: mostly tag edits, but can also be message deletion. Highlights and unread handling
are tags.
34. Posting messages
• Clients process messages as much
as possible
• Server adds unique ID and
timestamp to data
• Message echoed to client
Client processes messages. In our case, this basically means that tags are parsed and
message rendered to DOM. Later, if necessary, these changes can be reverted.
Server adds some information to messages, which is needed to add new tags. Client uses this
information to complete the message.
Last, message is echoed to all clients, including sender. This causes duplication, but data is
needed to “complete” outgoing messages.
35. Order sync
SUP
LOL
BAR FOO
FOO
"Undo" pending operations
Apply operation from network
Re-apply pending ops
A bit like git rebase, but cheat a little
37. Idempotent
changes
"Operation, which can be applied multiple
times without changing the result beyond the
initial application."
Tags are good fit for this. You can add the same tag to a message multiple times. This is
used as our event-stream contains messages the client has sent.
38. Error handling is
tricky
Handling errors becomes quickly quite tricky. Good MVC would make it easier, but also it’s
not easy to know when some operation has failed because of poor network with WebSockets
and Socket.IO.
UX implications!
39. Protips
• Socket.IO will bite you
• SSE is safe choice for streaming
• Design for broken internet
People put laptop to sleep, live with it. Important for single page apps. Avoid reloading.