SLIDE 1 The realtime web: HTTP/1.1 to WebSocket, SPDY and beyond
Guillermo Rauch @ QCon. November 2012.
SLIDE 2
Guillermo.
SLIDE 3
CTO and co-founder at LearnBoost.
SLIDE 4
Creator of socket.io and engine.io.
SLIDE 5
@rauchg on twitter
SLIDE 6
http://devthought.com
SLIDE 7
Author of Wiley’s Smashing Node
http://smashingnode.com
SLIDE 8
The realtime web
SLIDE 9
HTTP over TCP/IP
SLIDE 10
No specific techniques or protocols
SLIDE 11
Realtime = fast data exchange.
SLIDE 12
Let's analyze the timeline of a typical web request.
SLIDE 13
https://mywebsite.com
SLIDE 14
- 1. We first need to look up the DNS record for mywebsite.com
SLIDE 15
- DNS. Low bandwidth. Potentially high latency.
SLIDE 16
Browsers have different approaches and optimizations.
SLIDE 17
Our best shot at optimizing this is <link rel="dns-prefetch">.
SLIDE 19 “In January this year (2010), Gmail switched to using HTTPS for everything by default […] In our production frontend machines, SSL/TLS accounts for less than 1% of the CPU load, less than 10KB of memory per connection and less than 2% of network overhead.”
http://www.imperialviolet.org/2010/06/25/overclocking-ssl.html (emphasis mine)
SLIDE 20 SSL allows us to upgrade the protocol without breaking
- utdated proxies (eg: WebSocket).
SLIDE 21
- 3. Request headers
- 1. Accept:text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
- 2. Accept-Charset:ISO-8859-1,utf-8;q=0.7,*;q=0.3
- 3. Accept-Encoding:gzip,deflate,sdch
- 4. Accept-Language:en-US,en;q=0.8
- 5. Cache-Control:max-age=0
- 6. Connection:keep-alive
- 7. Host:www.imperialviolet.org
- 8. If-Modified-Since:Tue, 06 Nov 2012 21:22:53 GMT
- 9. User-Agent:Mozilla/5.0 (Macintosh; Intel Mac OS X 10_8_2) AppleWebKit/537.17 (KHTML, like Gecko) Chrome/24.0.1312.5 Safari/537.17
SLIDE 22
Consider a character-by-character realtime editor (GDocs).
SLIDE 23
In the following example we send the character “a”.
SLIDE 24
- 1. Accept:text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
- 2. Accept-Charset:ISO-8859-1,utf-8;q=0.7,*;q=0.3
- 3. Accept-Encoding:gzip,deflate,sdch
- 4. Accept-Language:en-US,en;q=0.8
- 5. Cache-Control:max-age=0
- 6. Connection:keep-alive
- 7. Host:www.myhost.com
- 8. User-Agent:Mozilla/5.0 (Macintosh; Intel Mac OS X 10_8_2) AppleWebKit/537.17 (KHTML, like Gecko) Chrome/24.0.1312.5 Safari/537.17
- 1. { “c”: “a” }
Headers Body (JSON obj)
SLIDE 25
HTTP/1.1 overhead: 370~ bytes per character. Boo.
SLIDE 26
This only considers sending data.
SLIDE 27
How ‘bout getting data?
SLIDE 28
We poll the server through a GET request
SLIDE 29
When the server responds, we send another one.
SLIDE 30
And another one.
SLIDE 31
…and another one. Boo.
SLIDE 32
If the server has no data to send us, we would be generating a LOT of traffic.
SLIDE 33
Instead, we make the server wait a bit if it has no data.
SLIDE 34
This is what we call long-polling.
SLIDE 35
Pseudo-code time.
SLIDE 36
function send(data){ var xhr = new XMLHttpRequest(); xhr.open(‘POST’, ‘/’, false); xhr.send(data); } function get(fn) { var xhr = new XMLHttpRequest(); xhr.open(‘GET’, ‘/’, false); xhr.send(data); xhr.onreadystatechange = function(){ if (200 == xhr.status) { fn(); // send data to user get(); // restart poll } } }
SLIDE 37
Most applications need to send data in a particular order.
SLIDE 38
For our example, the order that the user types.
SLIDE 39
Therefore, to replicate socket semantics we need to implement buffering. Boo.
SLIDE 40
For example, the user types a then b c d every 100ms.
SLIDE 41
If an existing send() is in-flight…
SLIDE 42
We accumulate the other data packets in an array.
SLIDE 43
Then we send that over when the server replies.
SLIDE 44
But instead of just sending one request per character.
SLIDE 45
We try to create a data packet with “b” “c” and “d” to minimize the request headers traffic.
SLIDE 46
- 1. Accept:text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
- 2. Accept-Charset:ISO-8859-1,utf-8;q=0.7,*;q=0.3
- 3. Accept-Encoding:gzip,deflate,sdch
- 4. Accept-Language:en-US,en;q=0.8
- 5. Cache-Control:max-age=0
- 6. Connection:keep-alive
- 7. Host:www.myhost.com
- 8. User-Agent:Mozilla/5.0 (Macintosh; Intel Mac OS X 10_8_2) AppleWebKit/537.17 (KHTML, like Gecko) Chrome/24.0.1312.5 Safari/537.17
- 1. [{ “c”: “b” }, { “c”: “c” }, { “c”: “d” }]
Headers Body (JSON Array)
SLIDE 47
This means, however, that we need a protocol.
SLIDE 48
A protocol on top of a protocol (HTTP).
SLIDE 49
Server and client need to agree that the data is not simply the request body, but within it.
SLIDE 50
This is a lot of work before you can focus on your app!
SLIDE 51
This has been a common trend in web development.
SLIDE 52
Need rounded corners?
SLIDE 53
<div class=”wrap-outer”> <div class=”wrap”> <div class=”wrap-inner”> <div class=”content”> </div> </div> </div> </div>
Circa 2001
.wrap-outer { background: …; padding: …; } .wrap { background: …; padding: …; } .wrap-inner { background: …; padding: …; }
SLIDE 54
<div class=”content”> </div>
Now
.content { border-radius: 3px; }
SLIDE 55
For the realtime web…
SLIDE 56 Circa 2001
- Data buffering on server within GET polls.
- Data buffering on client.
- JSON protocol or equivalent.
- XMLHttpRequest fallbacks
- Cross-domain tricks
- …
SLIDE 58
Simpler client side API.
SLIDE 59
var ws = new WebSocket(); ws.onopen = function(){}; ws.onclose = function(){}; ws.onmessage = function(){};
SLIDE 60
Light-weight protocol.
SLIDE 61
Instead of sending many HTTP requests.
SLIDE 62
Browser sends one that contains a header: Upgrade: WebSocket
SLIDE 63
The connection is then taken over.
SLIDE 64
Like TCP but unlike HTTP , it’s bi-directional.
SLIDE 65
We can send and receive over the same socket.
SLIDE 66
In terms of our realtime app example…
SLIDE 67
WebSocket overhead: 2-5~* bytes per character.
* depending on spec
SLIDE 68
But WS is buggy on mobile, unavailable in older browsers, not understood by all middleware, breaks often without SSL.
SLIDE 69
Meet engine.io, the data transport engine of socket.io
SLIDE 70
var ws = new eio.Socket(); ws.onopen = function(){}; ws.onclose = function(){}; ws.onmessage = function(){};
SLIDE 71
Same API, long-polling first, upgrades to WS if possible.
SLIDE 72
The main advantage of WS is lightweight framing!
SLIDE 73
Most of the benefits that WS offers, like lower-bandwidth, would still benefit XMLHttpRequest.
SLIDE 74
Meet SPDY.
SLIDE 75
Headers overhead?
SLIDE 76 “SPDY compresses request and response HTTP headers, resulting in fewer packets and fewer bytes transmitted.”
http://www.chromium.org/spdy/spdy-whitepaper
SLIDE 77
TCP overhead?
SLIDE 78 “SPDY allows for unlimited concurrent streams over a single TCP connection.”
http://www.chromium.org/spdy/spdy-whitepaper
SLIDE 79
SPDY works transparently to your app.
SLIDE 80
SPDY assumes SSL.
SLIDE 81
Just upgrade your SSL terminator or load balancer and reap the benefits!
SLIDE 82
In other words, if you host your app on Heroku…
SLIDE 83
Keep doing what you’re doing, and wait for the upgrade.
SLIDE 84
But wait, many apps still need socket-like semantics.
SLIDE 85
I want to write “socket.send(‘data’);” instead of “new XMLHttpRequest”
SLIDE 86
I’m not even sending XML!@!!@
SLIDE 87
Solution: WebSocket layering over SPDY/3.
SLIDE 88 “With this protocol, WebSocket API get both benefits of usable bi-directional communication API, and a fast, secure, and scalable multiplexing mechanism on the top of SPDY”
https://docs.google.com/document/d/1zUEFzz7NCls3Yms8hXxY4wGXJ3EEvoZc3GihrqPQcM0/edit#
SLIDE 89
Currently a draft.
SLIDE 90
SPDY/3 is currently experimental and available through chrome://flags.
SLIDE 91
Solution until the ideal world occurs: engine.io
SLIDE 92 Moving forward… Most applications are not data-packet
SLIDE 93
socket.io builds on top of engine.io to offer easy-to-use realtime events for everyone.
SLIDE 94
var socket = io.connect(); socket.on(‘character’, function(){ // render! }); socket.on(‘chat message’, function(){ // render! }); socket.emit(‘ready!’);
SLIDE 95
The goal is to not just focus on the speed of data transfer but also speed of development.
SLIDE 96
While ensuring the reliability that the data can be exchanged in the fastest way, in every network, on every device.
SLIDE 97
What’s next?
SLIDE 99 chrome.experimental.socket.create('tcp', '127.0.0.1', 8080, function(socketInfo) { chrome.experimental.socket.connect(socketInfo.socketId, function (result) { chrome.experimental.socket.write(socketInfo.socketId, "Hello, world!"); }); });
SLIDE 100
For now, only for extensions.
SLIDE 101
- Removing the server out of the equation.
SLIDE 102
WebRTC PeerConnection.
SLIDE 103
Currently called “webkitDeprecatedPeerConnection”
SLIDE 104
Let’s be patient!
SLIDE 105
The end.