Contenu connexe Similaire à Building Realtime Apps with Ember.js and WebSockets (20) Building Realtime Apps with Ember.js and WebSockets4. Ben LimmerGEMConf - 5/21/2016 ember.party
Talk Roadmap
• WebSockets vs. AJAX
• Fundamentals of WebSockets
• Code!
• Other Considerations
16. Ben LimmerGEMConf - 5/21/2016 ember.party
Source: http://browserquest.mozilla.org/img/common/promo-title.jpg
17. Ben LimmerGEMConf - 5/21/2016 ember.party
chat
gamesdeployment
notifications
live dashboards
2nd screen
apps
activity
streams
comment
sections
realtime
progress
collaborative
editing
19. Ben LimmerGEMConf - 5/21/2016 ember.party
update?
nope.
(old way)
short polling
update?
nope.
data
update?
yep!
20. Ben LimmerGEMConf - 5/21/2016 ember.party
(old way)
long polling
request
Keep-Alive
timeout
request
Keep-Alive
data
response
request
Keep-Alive
21. Ben LimmerGEMConf - 5/21/2016 ember.party
WebSockets
handshake
connection opened
bi-directional
communication
22. Ben LimmerGEMConf - 5/21/2016 ember.party
WebSockets
no polling
full duplex over TCP
communication over
standard HTTP(S) ports
broadcast to all
connected clients
23. Ben LimmerGEMConf - 5/21/2016 ember.party
Talk Roadmap
• WebSockets vs. AJAX
• Fundamentals of WebSockets
• Code!
• Other Considerations
25. Ben LimmerGEMConf - 5/21/2016 ember.party
Request
GET wss://example.org/socket HTTP/1.1
Origin: https://example.org
Host: example.org
Sec-WebSocket-Key: zy6Dy9mSAIM7GJZNf9rI1A==
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Version: 13
Response
HTTP/1.1 101 Switching Protocols
Connection: Upgrade
Sec-WebSocket-Accept: EDJa7WCAQQzMCYNJM42Syuo9SqQ=
Upgrade: websocket
28. Ben LimmerGEMConf - 5/21/2016 ember.party
send(String 'foo');
send(Blob 010101);
send(ArrayBuffer file);
30. Ben LimmerGEMConf - 5/21/2016 ember.party
sub-protocols
• a contract between client/server
• 2 classes of sub-protocols
• well-defined (e.g. STOMP, WAMP)
• application specific protocols
31. Ben LimmerGEMConf - 5/21/2016 ember.party
Request with Protocol
GET wss://example.org/socket HTTP/1.1
Origin: https://example.org
Host: example.org
Sec-WebSocket-Key: zy6Dy9mSAIM7GJZNf9rI1A==
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Version: 13
Sec-WebSocket-Protocol: v10.stomp
32. Ben LimmerGEMConf - 5/21/2016 ember.party
STOMP
SEND
destination:/queue/a
hello queue a
^@
MESSAGE
destination:/queue/a
message-id: <message-identifier>
hello queue a
^@
35. Ben LimmerGEMConf - 5/21/2016 ember.party
Talk Roadmap
• AJAX vs. WebSockets
• Fundamentals of WebSockets
• Code!
• Other Considerations
42. Ben LimmerGEMConf - 5/21/2016 ember.party
• fast
• simple WebSocket implementation
• few bells and whistles
npm install ws
43. Ben LimmerGEMConf - 5/21/2016 ember.party
server/index.js
1 const WebSocketServer = require('ws').Server;
2
3 const wss = new WebSocketServer({
4 port: process.env.PORT
5 });
47. Ben LimmerGEMConf - 5/21/2016 ember.party
ember install ember-websockets
• integrates with the Ember runloop
• is an Ember.ObjectProxy
• abstracts away the WebSocket
48. Ben LimmerGEMConf - 5/21/2016 ember.party
app/services/rt-ember-socket.js
1 websockets: service(),
2
3 init() {
4 this._super(...arguments);
5
6 const socket = this.get('websockets').socketFor(host);
7
8 socket.on('open', this.open, this);
9 socket.on('close', this.reconnect, this);
10 },
11
12 online: false,
13 open() {
14 this.set('online', true);
15 },
16
17 reconnect() {
18 this.set('online', false);
19
20 Ember.run.later(this, () => {
21 this.get('socket').reconnect();
22 }, 5000);
23 },
52. Ben LimmerGEMConf - 5/21/2016 ember.party
server/index.js
1 const WebSocketServer = require('ws').Server;
2
3 const wss = new WebSocketServer({
4 port: process.env.PORT,
5 handleProtocols: function(protocol, cb) {
6 const supportedProtocol =
7 protocol[protocol.indexOf('rtember-1.0')];
8 if (supportedProtocol) {
9 cb(true, supportedProtocol);
10 } else {
11 cb(false);
12 }
13 },
14 });
54. Ben LimmerGEMConf - 5/21/2016 ember.party
app/services/rt-ember-socket.js
1 websockets: Ember.inject.service(),
2
3 socket: null,
4 init() {
5 this._super(...arguments);
6
7 const socket = this.get('websockets')
8 .socketFor(host, ['rtember-1.0']);
9
10 socket.on('open', this.open, this);
11 socket.on('close', this.reconnect, this);
12
13 this.set('socket', socket);
14 },
57. Ben LimmerGEMConf - 5/21/2016 ember.party
rtember-1.0 - sub-protocol
{
"frameType": "event",
"payload": {
“eventType": ... event type ...,
"eventInfo": ... event info ...
}
}
{
"frameType": "data",
"payload": {
... json api payload ...
}
}
or
59. Ben LimmerGEMConf - 5/21/2016 ember.party
1 wss.on('connection', function(ws) {
2 sendInitialGifs(ws);
3 });
4
5 function sendInitialGifs(ws) {
6 const gifs = gifDb;
7 const random = _.sampleSize(gifs, 25);
8
9 sendDataToClient(ws, serializeGifs(random));
10 }
11
12 function sendDataToClient(ws, payload) {
13 const payload = {
14 frameType: FRAME_TYPES.DATA,
15 payload,
16 }
17 ws.send(JSON.stringify(payload));
18 }
60. Ben LimmerGEMConf - 5/21/2016 ember.party
app/services/rt-ember-socket.js
1 init() {
2 ...
3 socket.on('message', this.handleMessage, this);
4 ...
5 },
6
7 handleMessage(msg) {
8 const { frameType, payload } = JSON.parse(msg.data);
9
10 if (frameType === FRAME_TYPES.DATA) {
11 this.handleData(payload);
12 } else {
13 warn(`Encountered unknown frame type: ${frameType}`);
14 }
15 },
16
17 handleData(payload) {
18 this.get('store').pushPayload(payload);
19 }
62. Ben LimmerGEMConf - 5/21/2016 ember.party
{
"frameType": "data",
"payload": {
"data": [{
"type": "gif",
"id": "3o8doPV2heuYjdN2Fy",
"attributes": {
"url": "http://giphy.com/3o8doPV2heuYjdN2Fy/giphy.gif"
}
}, { ... }, { ... }]
}
}
64. Ben LimmerGEMConf - 5/21/2016 ember.party
app/routes/index.js
1 export default Ember.Route.extend({
2 model() {
3 return this.store.peekAll('gif');
4 }
5 });
66. Ben LimmerGEMConf - 5/21/2016 ember.party
app/templates/index.hbs
{{gif-tv gifs=model}}
app/templates/components/gif-tv.hbs
<div class='suggestions'>
{{#each gifs as |gif|}}
<img src={{gif.url}} />
{{/each}}
</div>
70. Ben LimmerGEMConf - 5/21/2016 ember.party
rtember-1.0 - sub-protocol
{
"frameType": "event",
"payload": {
“eventType": ... event type ...,
"eventInfo": ... event info ...
}
}
71. Ben LimmerGEMConf - 5/21/2016 ember.party
Share GIF Event
{
"frameType": "event",
"payload": {
"eventType": "share_gif",
"eventInfo": "<gif_id>"
}
}
{
"frameType": "data",
"payload": {[
<shared_gif>,
<previously_shared_gif>
]}
}
72. Ben LimmerGEMConf - 5/21/2016 ember.party
{
"frameType": "data",
"payload": {
"data": [
{
"type": "gifs",
"id": "3o8doPV2heuYjdN2Fy",
"attributes": {
"url": "http://giphy.com/3o8doPV2heuYjdN2Fy/giphy.gif",
"shared": false
}
},
{
"type": "gifs",
"id": "xTiQyBOIQe5cgiyUPS",
"attributes": {
"url": "http://giphy.com/xTiQyBOIQe5cgiyUPS/giphy.gif",
"shared": true
}
}
]
}
}
73. Ben LimmerGEMConf - 5/21/2016 ember.party
app/templates/components/gif-tv.hbs
<div class='suggestions'>
{{#each gifs as |gif|}}
<img {{action shareGif gif}} src={{gif.url}} />
{{/each}}
</div>
app/components/gif-tv.js
1 export default Ember.Component.extend({
2 rtEmberSocket: service(),
3
4 shareGif(gif) {
5 this.get('rtEmberSocket')
6 .sendEvent(EVENTS.SHARE_GIF, gif.get('id'));
7 },
8 });
74. Ben LimmerGEMConf - 5/21/2016 ember.party
6 .sendEvent(EVENTS.SHARE_GIF, gif.get('id'));
7 },
8 });
app/services/rt-ember-socket.js
1 sendEvent(eventType, eventInfo) {
2 this.get('socket').send(JSON.stringify({
3 frameType: FRAME_TYPES.EVENT,
4 payload: {
5 eventType,
6 eventInfo,
7 },
8 }));
9 }
75. Ben LimmerEmberJS Meetup - 2/24/2016 ember.party
1 ws.on('message', function(rawData) {
2 const data = JSON.parse(rawData);
3
4 if (data.frameType === FRAME_TYPES.EVENT) {
5 const newShare = _.find(gifDb, {
6 id: data.payload.eventInfo
7 });
8 const previouslyShared = _.find(gifDb, 'shared');
9
10 newShare.shared = true;
11 previouslyShared.shared = false;
12
13 const framePayload = {
14 frameType: FRAME_TYPES.DATA,
15 payload: serializeGifs([previouslyShared, newShare]),
16 };
17 const rawPayload = JSON.stringify(framePayload);
18 wss.clients.forEach((client) => {
19 client.send(rawPayload);
20 });
21 }
22 });
77. Ben LimmerGEMConf - 5/21/2016 ember.party
1 ws.on('message', function(rawData) {
2 try {
3 const data = JSON.parse(rawData);
4
5 if (data.frameType === FRAME_TYPES.EVENT) {
6 if (data.payload.eventType !== EVENTS.SHARE_GIF) {
7 throw Error(); // unknown event
8 }
9
10 const newShare = ...;
11 if (!newShare) {
12 throw Error(); // unknown gif
13 }
14 ...
15 }
16 } catch(e) {
17 ws.close(1003); // unsupported data
18 }
19 });
78. Ben LimmerGEMConf - 5/21/2016 ember.party
{
"frameType": "data",
"payload": {
"data": [
{
"type": "gifs",
"id": "3o8doPV2heuYjdN2Fy",
"attributes": {
"url": "http://giphy.com/3o8doPV2heuYjdN2Fy/giphy.gif",
"shared": false
}
},
{
"type": "gifs",
"id": "xTiQyBOIQe5cgiyUPS",
"attributes": {
"url": "http://giphy.com/xTiQyBOIQe5cgiyUPS/giphy.gif",
"shared": true
}
}
]
}
}
79. Ben LimmerGEMConf - 5/21/2016 ember.party
app/templates/components/gif-tv.hbs
app/components/gif-tv.js
1 export default Ember.Component.extend({
2 sharedGif: computed('gifs.@each.shared', function() {
3 return this.get('gifs').findBy('shared', true);
4 }),
5 });
<div class='shared-gif'>
<img src={{sharedGif.url}} />
</div>
<div class='suggestions'>
<!-- ... -->
</div>
83. Ben LimmerGEMConf - 5/21/2016 ember.party
Talk Roadmap
• AJAX vs. WebSockets
• Fundamentals of WebSockets
• Code!
• Other Considerations
84. Ben LimmerGEMConf - 5/21/2016 ember.party
other considerations
• security
• websocket support (libraries)
• learn from example
85. Ben LimmerGEMConf - 5/21/2016 ember.party
security
• Use TLS (wss:// vs. ws://)
• Verify the Origin header
• Verify the request by using a random token on
handshake
source: WebSocket (Andrew Lombardi) - O’Reilly
86. Ben LimmerGEMConf - 5/21/2016 ember.party
other considerations
• security
• websocket support (libraries)
• learn from example
89. Ben LimmerGEMConf - 5/21/2016 ember.party
socket.io
• Graceful fallback to polling / flash (!)
• Syntactic Sugar vs. ws package
• Support in ember-websockets add-on
91. Ben LimmerGEMConf - 5/21/2016 ember.party
pusher.com
• Graceful fallback
• Presence support
• Authentication / Security strategies
• No infrastructure required
92. Ben LimmerGEMConf - 5/21/2016 ember.party
other considerations
• security
• websocket support (libraries)
• learn from example
95. Ben LimmerGEMConf - 5/21/2016 ember.party
https://github.com/blimmer/real-time-ember-client
https://github.com/blimmer/real-time-ember-server
l1m5blimmer
97. • WebSocket: Lightweight Client-Server
Communications (O’Reilly)
• WebSockets: Methods for Real-Time Data
Streaming (Steve Schwartz)
Credits