faye-websocket 0.2.0 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of faye-websocket might be problematic. Click here for more details.

@@ -1,3 +1,9 @@
1
+ === 0.3.0 / 2012-01-13
2
+
3
+ * Add support for EventSource connections
4
+ * Support the Thin, Rainbows and Goliath web servers
5
+
6
+
1
7
  === 0.2.0 / 2011-12-21
2
8
 
3
9
  * Add support for Sec-WebSocket-Protocol negotiation
@@ -4,9 +4,21 @@ This is a robust, general-purpose WebSocket implementation extracted from the
4
4
  {Faye}[http://faye.jcoglan.com] project. It provides classes for easily building
5
5
  WebSocket servers and clients in Ruby. It does not provide a server itself, but
6
6
  rather makes it easy to handle WebSocket connections within an existing
7
- {Rack}[http://rack.rubyforge.org/] application running under
8
- {Thin}[http://code.macournoyer.com/thin/]. It does not provide any abstraction
9
- other than the standard {WebSocket API}[http://dev.w3.org/html5/websockets/].
7
+ {Rack}[http://rack.rubyforge.org/] application. It does not provide any
8
+ abstraction other than the standard
9
+ {WebSocket API}[http://dev.w3.org/html5/websockets/].
10
+
11
+ It also provides an abstraction for handling {EventSource}[http://dev.w3.org/html5/eventsource/]
12
+ connections, which are one-way connections that allow the server to push data to
13
+ the client. They are based on streaming HTTP responses and can be easier to
14
+ access via proxies than WebSockets.
15
+
16
+ Currently, the following web servers are supported, and can be accessed directly
17
+ or via HAProxy:
18
+
19
+ * {Thin}[http://code.macournoyer.com/thin/]
20
+ * {Rainbows}[http://rainbows.rubyforge.org/] using EventMachine
21
+ * {Goliath}[http://postrank-labs.github.com/goliath/]
10
22
 
11
23
  The server-side socket can process {draft-75}[http://tools.ietf.org/html/draft-hixie-thewebsocketprotocol-75],
12
24
  {draft-76}[http://tools.ietf.org/html/draft-hixie-thewebsocketprotocol-76],
@@ -16,19 +28,18 @@ supports both +text+ and +binary+ messages, and transparently handles +ping+,
16
28
  +pong+, +close+ and fragmented messages.
17
29
 
18
30
 
19
- == Accepting WebSocket connections in Rack
31
+ == Handling WebSocket connections in Rack
20
32
 
21
- You can handle WebSockets on the server side by listening for HTTP Upgrade
22
- requests, and creating a new socket for the request. This socket object exposes
23
- the usual WebSocket methods for receiving and sending messages. For example this
24
- is how you'd implement an echo server:
33
+ You can handle WebSockets on the server side by listening for requests using the
34
+ <tt>Faye::WebSocket.websocket?</tt> method, and creating a new socket for the
35
+ request. This socket object exposes the usual WebSocket methods for receiving
36
+ and sending messages. For example this is how you'd implement an echo server:
25
37
 
38
+ # app.rb
26
39
  require 'faye/websocket'
27
- require 'rack'
28
- require 'eventmachine'
29
40
 
30
- app = lambda do |env|
31
- if env['HTTP_UPGRADE']
41
+ App = lambda do |env|
42
+ if Faye::WebSocket.websocket?(env)
32
43
  ws = Faye::WebSocket.new(env)
33
44
 
34
45
  ws.onmessage = lambda do |event|
@@ -40,27 +51,33 @@ is how you'd implement an echo server:
40
51
  ws = nil
41
52
  end
42
53
 
43
- # Thin async response
44
- [-1, {}, []]
54
+ # Return async Rack response
55
+ ws.rack_response
45
56
 
46
57
  else
47
58
  # Normal HTTP request
48
59
  [200, {'Content-Type' => 'text/plain'}, ['Hello']]
49
60
  end
50
61
  end
51
-
52
- EM.run {
53
- thin = Rack::Handler.get('thin')
54
- thin.run(app, :Port => 9292)
55
- }
62
+
63
+ This is a standard Rack app, so it can be run using a <tt>config.ru</tt> file.
64
+ However, so that incoming requests can be properly prepared to process WebSocket
65
+ connections, you need to tell <tt>Faye::WebSocket</tt> which adapter to load;
66
+ this can be either +thin+, +rainbows+ or +goliath+. If one of these servers is
67
+ already loaded before <tt>faye/websocket</tt> is loaded, it will load
68
+ appropriate adapters automatically.
69
+
70
+ # config.ru
71
+ require './app'
72
+ Faye::WebSocket.load_adapter('thin')
73
+ run App
56
74
 
57
75
 
58
76
  == Using the WebSocket client
59
77
 
60
78
  The client supports both the plain-text +ws+ protocol and the encrypted +wss+
61
79
  protocol, and has exactly the same interface as a socket you would use in a web
62
- browser. On the wire it identifies itself as hybi-13, though it's compatible
63
- with servers speaking later versions of the protocol.
80
+ browser. On the wire it identifies itself as hybi-13.
64
81
 
65
82
  require 'faye/websocket'
66
83
  require 'eventmachine'
@@ -125,15 +142,183 @@ messages.
125
142
  the other peer.
126
143
  * <b><tt>close(code, reason)</tt></b> closes the connection, sending the given
127
144
  status code and reason text, both of which are optional.
128
- * <b><tt>protocol</tt></b> is a string or +nil+ identifying the subprotocol th
129
- socket is using.
145
+ * <b><tt>protocol</tt></b> is a string (which may be empty) identifying the
146
+ subprotocol the socket is using.
147
+
148
+
149
+ == Handling EventSource connections in Rack
150
+
151
+ EventSource connections provide a very similar interface, although because they
152
+ only allow the server to send data to the client, there is no +onmessage+ API.
153
+ EventSource allows the server to push text messages to the client, where each
154
+ message has an optional event-type and ID.
155
+
156
+ # app.rb
157
+ require 'faye/websocket'
158
+
159
+ App = lambda do |env|
160
+ if Faye::EventSource.eventsource?(env)
161
+ es = Faye::EventSource.new(env)
162
+ p [:open, es.url, es.last_event_id]
163
+
164
+ # Periodically send messages
165
+ loop = EM.add_periodic_timer(1) { es.send('Hello') }
166
+
167
+ es.onclose = lambda do |event|
168
+ EM.cancel_timer(loop)
169
+ es = nil
170
+ end
171
+
172
+ # Return async Rack response
173
+ es.rack_response
174
+
175
+ else
176
+ # Normal HTTP request
177
+ [200, {'Content-Type' => 'text/plain'}, ['Hello']]
178
+ end
179
+ end
180
+
181
+ The +send+ method takes two optional parameters, <tt>:event</tt> and
182
+ <tt>:id</tt>. The default event-type is <tt>'message'</tt> with no ID. For
183
+ example, to send a +notification+ event with ID +99+:
184
+
185
+ es.send('Breaking News!', :event => 'notification', :id => '99')
186
+
187
+ The +EventSource+ object exposes the following properties:
188
+
189
+ * <b><tt>url</tt></b> is a string containing the URL the client used to create
190
+ the EventSource.
191
+ * <b><tt>last_event_id</tt></b> is a string containing the last event ID
192
+ received by the client. You can use this when the client reconnects after a
193
+ dropped connection to determine which messages need resending.
194
+
195
+ When you initialize an EventSource with <tt>Faye::EventSource.new</tt>, you can
196
+ pass configuration options after the +env+ parameter. Available options are:
197
+
198
+ * <b><tt>:retry</tt></b> is a number that tells the client how long (in seconds)
199
+ it should wait after a dropped connection before attempting to reconnect.
200
+ * <b><tt>:ping</tt></b> is a number that tells the server how often (in seconds)
201
+ to send 'ping' packets to the client to keep the connection open, to defeat
202
+ timeouts set by proxies. The client will ignore these messages.
203
+
204
+ For example, this creates a connection that pings every 15 seconds and is
205
+ retryable every 10 seconds if the connection is broken:
206
+
207
+ es = Faye::EventSource.new(es, :ping => 15, :retry => 10)
208
+
209
+
210
+ == Running your socket application
211
+
212
+ To use this library you must be using an EventMachine-based server; currently
213
+ Thin, Rainbows and Goliath are supported.
214
+
215
+
216
+ === Running the app with Thin
217
+
218
+ Thin can be started via the command line if you've set up a <tt>config.ru</tt>
219
+ file for your application:
220
+
221
+ thin start -R config.ru -p 9292
222
+
223
+ Or, you can use +rackup+. In development mode, this adds middlewares that don't
224
+ work with async apps, so you must start it in production mode:
225
+
226
+ rackup config.ru -s thin -E production -p 9292
227
+
228
+ It can also be started using the <tt>Rack::Handler</tt> interface common to many
229
+ Ruby servers. It must be run using EventMachine, and you can configure Thin
230
+ further in a block passed to +run+:
231
+
232
+ require 'eventmachine'
233
+ require 'rack'
234
+ require 'thin'
235
+ require './app'
236
+
237
+ EM.run {
238
+ thin = Rack::Handler.get('thin')
239
+
240
+ thin.run(App, :Port => 9292) do |server|
241
+ # You can set options on the server here, for example to set up SSL:
242
+ server.ssl_options = {
243
+ :private_key_file => 'path/to/ssl.key',
244
+ :cert_chain_file => 'path/to/ssl.crt'
245
+ }
246
+ server.ssl = true
247
+ end
248
+ }
249
+
250
+
251
+ === Running the app with Rainbows
252
+
253
+ <tt>Faye::WebSocket</tt> can only be run using EventMachine. To begin with,
254
+ you'll need a Rainbows config file that tells it to use EventMachine, along with
255
+ whatever Rainbows/Unicorn configuration you require.
256
+
257
+ # rainbows.conf
258
+ Rainbows! do
259
+ use :EventMachine
260
+ end
261
+
262
+ You can then run your <tt>config.ru</tt> file from the command line. Again,
263
+ <tt>Rack::Lint</tt> will complain unless you put the application in production
264
+ mode.
265
+
266
+ rainbows config.ru -c path/to/rainbows.conf -E production -p 9292
267
+
268
+ Rainbows also has a Ruby API for starting a server:
269
+
270
+ require 'rainbows'
271
+ require './app'
272
+
273
+ rackup = Unicorn::Configurator::RACKUP
274
+ rackup[:port] = 9292
275
+ rackup[:set_listener] = true
276
+ options = rackup[:options]
277
+ options[:config_file] = 'path/to/rainbows.conf'
278
+
279
+ server = Rainbows::HttpServer.new(App, options)
280
+
281
+ # This is non-blocking; use server.start.join to block
282
+ server.start
283
+
284
+
285
+ === Running the app with Goliath
286
+
287
+ Goliath can be made to run arbitrary Rack apps by delegating to them from a
288
+ <tt>Goliath::API</tt> instance. A simple server looks like this:
289
+
290
+ require 'goliath'
291
+ require './app'
292
+
293
+ class EchoServer < Goliath::API
294
+ def response(env)
295
+ App.call(env)
296
+ end
297
+ end
298
+
299
+ <tt>Faye::WebSocket</tt> can also be used inline within a Goliath app:
300
+
301
+ require 'goliath'
302
+ require 'faye/websocket'
303
+
304
+ class EchoServer < Goliath::API
305
+ def response(env)
306
+ ws = Faye::WebSocket.new(env)
307
+
308
+ ws.onmessage = lambda do |event|
309
+ ws.send(event.data)
310
+ end
311
+
312
+ ws.rack_response
313
+ end
314
+ end
130
315
 
131
316
 
132
317
  == License
133
318
 
134
319
  (The MIT License)
135
320
 
136
- Copyright (c) 2009-2011 James Coglan
321
+ Copyright (c) 2009-2012 James Coglan
137
322
 
138
323
  Permission is hereby granted, free of charge, to any person obtaining a copy of
139
324
  this software and associated documentation files (the 'Software'), to deal in
@@ -0,0 +1,50 @@
1
+ require File.expand_path('../../lib/faye/websocket', __FILE__)
2
+ require 'rack'
3
+
4
+ static = Rack::File.new(File.dirname(__FILE__))
5
+
6
+ App = lambda do |env|
7
+ if Faye::WebSocket.websocket?(env)
8
+ ws = Faye::WebSocket.new(env, ['irc', 'xmpp'])
9
+ p [:open, ws.url, ws.version, ws.protocol]
10
+
11
+ ws.onmessage = lambda do |event|
12
+ ws.send(event.data)
13
+ end
14
+
15
+ ws.onclose = lambda do |event|
16
+ p [:close, event.code, event.reason]
17
+ ws = nil
18
+ end
19
+
20
+ ws.rack_response
21
+
22
+ elsif Faye::EventSource.eventsource?(env)
23
+ es = Faye::EventSource.new(env)
24
+ time = es.last_event_id.to_i
25
+
26
+ p [:open, es.url, es.last_event_id]
27
+
28
+ loop = EM.add_periodic_timer(2) do
29
+ time += 1
30
+ es.send("Time: #{time}")
31
+ EM.add_timer(1) do
32
+ es.send('Update!!', :event => 'update', :id => time) if es
33
+ end
34
+ end
35
+
36
+ es.send("Welcome!\n\nThis is an EventSource server.")
37
+
38
+ es.onclose = lambda do |event|
39
+ EM.cancel_timer(loop)
40
+ p [:close, es.url]
41
+ es = nil
42
+ end
43
+
44
+ es.rack_response
45
+
46
+ else
47
+ static.call(env)
48
+ end
49
+ end
50
+
@@ -0,0 +1,17 @@
1
+ # Run using your favourite async server:
2
+ #
3
+ # thin start -R examples/config.ru -p 7000
4
+ # rainbows -c spec/rainbows.conf -E production examples/config.ru -p 7000
5
+ #
6
+ # If you run using one of these commands, the webserver is loaded before this
7
+ # file, so Faye::WebSocket can figure out which adapter to load. If instead you
8
+ # run using `rackup`, you need the `load_adapter` line below.
9
+ #
10
+ # rackup -E production -s thin examples/config.ru -p 7000
11
+
12
+ require 'rubygems'
13
+ require File.expand_path('../app', __FILE__)
14
+ # Faye::WebSocket.load_adapter('thin')
15
+
16
+ run App
17
+
@@ -1,43 +1,44 @@
1
1
  require 'rubygems'
2
- require File.expand_path('../../lib/faye/websocket', __FILE__)
3
- require 'rack'
4
- require 'eventmachine'
2
+ require 'rack/content_length'
3
+ require 'rack/chunked'
5
4
 
6
5
  port = ARGV[0] || 7000
7
6
  secure = ARGV[1] == 'ssl'
7
+ engine = ARGV[2] || 'thin'
8
+ spec = File.expand_path('../../spec', __FILE__)
8
9
 
9
- static = Rack::File.new(File.dirname(__FILE__))
10
+ require File.expand_path('../app', __FILE__)
11
+ Faye::WebSocket.load_adapter(engine)
10
12
 
11
- app = lambda do |env|
12
- if env['HTTP_UPGRADE']
13
- socket = Faye::WebSocket.new(env, ['irc', 'xmpp'])
14
- p [:open, socket.url, socket.version, socket.protocol]
15
-
16
- socket.onmessage = lambda do |event|
17
- socket.send(event.data)
18
- end
19
-
20
- socket.onclose = lambda do |event|
21
- p [:close, event.code, event.reason]
22
- socket = nil
13
+ case engine
14
+
15
+ when 'goliath'
16
+ class WebSocketServer < Goliath::API
17
+ def response(env)
18
+ App.call(env)
23
19
  end
24
-
25
- [-1, {}, []]
26
- else
27
- static.call(env)
28
20
  end
29
- end
30
21
 
31
- EM.run {
32
- thin = Rack::Handler.get('thin')
33
- thin.run(app, :Port => port) do |server|
34
- if secure
35
- server.ssl = true
36
- server.ssl_options = {
37
- :private_key_file => File.expand_path('../../spec/server.key', __FILE__),
38
- :cert_chain_file => File.expand_path('../../spec/server.crt', __FILE__)
39
- }
22
+ when 'rainbows'
23
+ rackup = Unicorn::Configurator::RACKUP
24
+ rackup[:port] = port
25
+ rackup[:set_listener] = true
26
+ options = rackup[:options]
27
+ options[:config_file] = spec + '/rainbows.conf'
28
+ Rainbows::HttpServer.new(App, options).start.join
29
+
30
+ when 'thin'
31
+ EM.run {
32
+ thin = Rack::Handler.get('thin')
33
+ thin.run(App, :Port => port) do |server|
34
+ if secure
35
+ server.ssl_options = {
36
+ :private_key_file => spec + '/server.key',
37
+ :cert_chain_file => spec + '/server.crt'
38
+ }
39
+ server.ssl = true
40
+ end
40
41
  end
41
- end
42
- }
42
+ }
43
+ end
43
44
 
@@ -0,0 +1,39 @@
1
+ <!doctype html>
2
+ <html>
3
+ <head>
4
+ <meta http-equiv="Content-type" content="text/html; charset=utf-8">
5
+ <title>EventSource test</title>
6
+ </head>
7
+ <body>
8
+
9
+ <h1>EventSource test</h1>
10
+ <ul></ul>
11
+
12
+ <script type="text/javascript">
13
+ var logger = document.getElementsByTagName('ul')[0],
14
+ socket = new EventSource('/');
15
+
16
+ var log = function(text) {
17
+ logger.innerHTML += '<li>' + text + '</li>';
18
+ };
19
+
20
+ socket.onopen = function() {
21
+ log('OPEN');
22
+ };
23
+
24
+ socket.onmessage = function(event) {
25
+ log('MESSAGE: ' + event.data);
26
+ };
27
+
28
+ socket.addEventListener('update', function(event) {
29
+ log('UPDATE(' + event.lastEventId + '): ' + event.data);
30
+ });
31
+
32
+ socket.onerror = function(event) {
33
+ log('ERROR: ' + event.message);
34
+ };
35
+ </script>
36
+
37
+ </body>
38
+ </html>
39
+