_bushido-faye-websocket 0.4.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (40) hide show
  1. data/CHANGELOG.txt +56 -0
  2. data/README.rdoc +366 -0
  3. data/examples/app.rb +50 -0
  4. data/examples/autobahn_client.rb +44 -0
  5. data/examples/client.rb +30 -0
  6. data/examples/config.ru +17 -0
  7. data/examples/haproxy.conf +21 -0
  8. data/examples/server.rb +44 -0
  9. data/examples/sse.html +39 -0
  10. data/examples/ws.html +44 -0
  11. data/ext/faye_websocket_mask/FayeWebsocketMaskService.java +61 -0
  12. data/ext/faye_websocket_mask/extconf.rb +5 -0
  13. data/ext/faye_websocket_mask/faye_websocket_mask.c +33 -0
  14. data/lib/faye/adapters/goliath.rb +47 -0
  15. data/lib/faye/adapters/rainbows.rb +32 -0
  16. data/lib/faye/adapters/rainbows_client.rb +70 -0
  17. data/lib/faye/adapters/thin.rb +62 -0
  18. data/lib/faye/eventsource.rb +124 -0
  19. data/lib/faye/websocket.rb +216 -0
  20. data/lib/faye/websocket/adapter.rb +21 -0
  21. data/lib/faye/websocket/api.rb +96 -0
  22. data/lib/faye/websocket/api/event.rb +33 -0
  23. data/lib/faye/websocket/api/event_target.rb +34 -0
  24. data/lib/faye/websocket/client.rb +84 -0
  25. data/lib/faye/websocket/draft75_parser.rb +87 -0
  26. data/lib/faye/websocket/draft76_parser.rb +84 -0
  27. data/lib/faye/websocket/hybi_parser.rb +320 -0
  28. data/lib/faye/websocket/hybi_parser/handshake.rb +78 -0
  29. data/lib/faye/websocket/hybi_parser/stream_reader.rb +29 -0
  30. data/lib/faye/websocket/utf8_match.rb +8 -0
  31. data/spec/faye/websocket/client_spec.rb +179 -0
  32. data/spec/faye/websocket/draft75_parser_examples.rb +48 -0
  33. data/spec/faye/websocket/draft75_parser_spec.rb +27 -0
  34. data/spec/faye/websocket/draft76_parser_spec.rb +34 -0
  35. data/spec/faye/websocket/hybi_parser_spec.rb +156 -0
  36. data/spec/rainbows.conf +3 -0
  37. data/spec/server.crt +15 -0
  38. data/spec/server.key +15 -0
  39. data/spec/spec_helper.rb +68 -0
  40. metadata +158 -0
@@ -0,0 +1,56 @@
1
+ === 0.4.4 / 2012-03-16
2
+
3
+ * Fix installation on JRuby with a platform-specific gem
4
+
5
+
6
+ === 0.4.3 / 2012-03-12
7
+
8
+ * Make extconf.rb a no-op on JRuby
9
+
10
+
11
+ === 0.4.2 / 2012-03-09
12
+
13
+ * Port masking-function C extension to Java for JRuby
14
+
15
+
16
+ === 0.4.1 / 2012-02-26
17
+
18
+ * Treat anything other than an Array as a string when calling send()
19
+ * Fix error loading UTF-8 validation code on Ruby 1.9 with -Ku flag
20
+
21
+
22
+ === 0.4.0 / 2012-02-13
23
+
24
+ * Add ping() method to server-side WebSocket and EventSource
25
+ * Buffer send() calls until the draft-76 handshake is complete
26
+ * Fix HTTPS problems on Node 0.7
27
+
28
+
29
+ === 0.3.0 / 2012-01-13
30
+
31
+ * Add support for EventSource connections
32
+ * Support the Thin, Rainbows and Goliath web servers
33
+
34
+
35
+ === 0.2.0 / 2011-12-21
36
+
37
+ * Add support for Sec-WebSocket-Protocol negotiation
38
+ * Support hixie-76 close frames and 75/76 ignored segments
39
+ * Improve performance of HyBi parsing/framing functions
40
+ * Write masking function in C
41
+
42
+
43
+ === 0.1.2 / 2011-12-05
44
+
45
+ * Make hixie-76 sockets work through HAProxy
46
+
47
+
48
+ === 0.1.1 / 2011-11-30
49
+
50
+ * Fix add_event_listener() interface methods
51
+
52
+
53
+ === 0.1.0 / 2011-11-27
54
+
55
+ * Initial release, based on WebSocket components from Faye
56
+
@@ -0,0 +1,366 @@
1
+ = Faye::WebSocket
2
+
3
+ * Travis CI build: {<img src="https://secure.travis-ci.org/faye/faye-websocket-ruby.png" />}[http://travis-ci.org/faye/faye-websocket-ruby]
4
+ * Autobahn tests: {server}[http://faye.jcoglan.com/autobahn/servers/], {client}[http://faye.jcoglan.com/autobahn/clients/]
5
+
6
+ This is a robust, general-purpose WebSocket implementation extracted from the
7
+ {Faye}[http://faye.jcoglan.com] project. It provides classes for easily building
8
+ WebSocket servers and clients in Ruby. It does not provide a server itself, but
9
+ rather makes it easy to handle WebSocket connections within an existing
10
+ {Rack}[http://rack.rubyforge.org/] application. It does not provide any
11
+ abstraction other than the standard
12
+ {WebSocket API}[http://dev.w3.org/html5/websockets/].
13
+
14
+ It also provides an abstraction for handling {EventSource}[http://dev.w3.org/html5/eventsource/]
15
+ connections, which are one-way connections that allow the server to push data to
16
+ the client. They are based on streaming HTTP responses and can be easier to
17
+ access via proxies than WebSockets.
18
+
19
+ Currently, the following web servers are supported, and can be accessed directly
20
+ or via HAProxy:
21
+
22
+ * {Thin}[http://code.macournoyer.com/thin/]
23
+ * {Rainbows}[http://rainbows.rubyforge.org/] using EventMachine
24
+ * {Goliath}[http://postrank-labs.github.com/goliath/]
25
+
26
+ The server-side socket can process {draft-75}[http://tools.ietf.org/html/draft-hixie-thewebsocketprotocol-75],
27
+ {draft-76}[http://tools.ietf.org/html/draft-hixie-thewebsocketprotocol-76],
28
+ {hybi-07}[http://tools.ietf.org/html/draft-ietf-hybi-thewebsocketprotocol-07]
29
+ and later versions of the protocol. It selects protocol versions automatically,
30
+ supports both +text+ and +binary+ messages, and transparently handles +ping+,
31
+ +pong+, +close+ and fragmented messages.
32
+
33
+
34
+ == Handling WebSocket connections in Rack
35
+
36
+ You can handle WebSockets on the server side by listening for requests using the
37
+ <tt>Faye::WebSocket.websocket?</tt> method, and creating a new socket for the
38
+ request. This socket object exposes the usual WebSocket methods for receiving
39
+ and sending messages. For example this is how you'd implement an echo server:
40
+
41
+ # app.rb
42
+ require 'faye/websocket'
43
+
44
+ App = lambda do |env|
45
+ if Faye::WebSocket.websocket?(env)
46
+ ws = Faye::WebSocket.new(env)
47
+
48
+ ws.onmessage = lambda do |event|
49
+ ws.send(event.data)
50
+ end
51
+
52
+ ws.onclose = lambda do |event|
53
+ p [:close, event.code, event.reason]
54
+ ws = nil
55
+ end
56
+
57
+ # Return async Rack response
58
+ ws.rack_response
59
+
60
+ else
61
+ # Normal HTTP request
62
+ [200, {'Content-Type' => 'text/plain'}, ['Hello']]
63
+ end
64
+ end
65
+
66
+ This is a standard Rack app, so it can be run using a <tt>config.ru</tt> file.
67
+ However, so that incoming requests can be properly prepared to process WebSocket
68
+ connections, you need to tell <tt>Faye::WebSocket</tt> which adapter to load;
69
+ this can be either +thin+, +rainbows+ or +goliath+. If one of these servers is
70
+ already loaded before <tt>faye/websocket</tt> is loaded, it will load
71
+ appropriate adapters automatically.
72
+
73
+ # config.ru
74
+ require './app'
75
+ Faye::WebSocket.load_adapter('thin')
76
+ run App
77
+
78
+ Note that under certain circumstances (notably a draft-76 client connecting
79
+ through an HTTP proxy), the WebSocket handshake will not be complete after you
80
+ call `Faye::WebSocket.new` because the server will not have received the entire
81
+ handshake from the client yet. In this case, calls to `ws.send` will buffer the
82
+ message in memory until the handshake is complete, at which point any buffered
83
+ messages will be sent to the client.
84
+
85
+ If you need to detect when the WebSocket handshake is complete, you can use the
86
+ `onopen` event.
87
+
88
+ If the connection's protocol version supports it, you can call <tt>ws.ping()</tt>
89
+ to send a ping message and wait for the client's response. This method takes a
90
+ message string, and an optional callback that fires when a matching pong message
91
+ is received. It returns +true+ iff a ping message was sent. If the client does
92
+ not support ping/pong, this method sends no data and returns +false+.
93
+
94
+ ws.ping 'Mic check, one, two' do
95
+ # fires when pong is received
96
+ end
97
+
98
+
99
+ == Using the WebSocket client
100
+
101
+ The client supports both the plain-text +ws+ protocol and the encrypted +wss+
102
+ protocol, and has exactly the same interface as a socket you would use in a web
103
+ browser. On the wire it identifies itself as hybi-13.
104
+
105
+ require 'faye/websocket'
106
+ require 'eventmachine'
107
+
108
+ EM.run {
109
+ ws = Faye::WebSocket::Client.new('ws://www.example.com/')
110
+
111
+ ws.onopen = lambda do |event|
112
+ p [:open]
113
+ ws.send('Hello, world!')
114
+ end
115
+
116
+ ws.onmessage = lambda do |event|
117
+ p [:message, event.data]
118
+ end
119
+
120
+ ws.onclose = lambda do |event|
121
+ p [:close, event.code, event.reason]
122
+ ws = nil
123
+ end
124
+ }
125
+
126
+
127
+ == Subprotocol negotiation
128
+
129
+ The WebSocket protocol allows peers to select and identify the application
130
+ protocol to use over the connection. On the client side, you can set which
131
+ protocols the client accepts by passing a list of protocol names when you
132
+ construct the socket:
133
+
134
+ ws = Faye::WebSocket::Client.new('ws://www.example.com/', ['irc', 'amqp'])
135
+
136
+ On the server side, you can likewise pass in the list of protocols the server
137
+ supports after the other constructor arguments:
138
+
139
+ ws = Faye::WebSocket.new(env, ['irc', 'amqp'])
140
+
141
+ If the client and server agree on a protocol, both the client- and server-side
142
+ socket objects expose the selected protocol through the <tt>ws.protocol</tt>
143
+ property. If they cannot agree on a protocol to use, the client closes the
144
+ connection.
145
+
146
+
147
+ == WebSocket API
148
+
149
+ The WebSocket API consists of several event handlers and a method for sending
150
+ messages.
151
+
152
+ * <b><tt>onopen</tt></b> fires when the socket connection is established. Event
153
+ has no attributes.
154
+ * <b><tt>onerror</tt></b> fires when the connection attempt fails. Event has no
155
+ attributes.
156
+ * <b><tt>onmessage</tt></b> fires when the socket receives a message. Event has
157
+ one attribute, <b><tt>data</tt></b>, which is either a +String+ (for text
158
+ frames) or an +Array+ of byte-sized integers (for binary frames).
159
+ * <b><tt>onclose</tt></b> fires when either the client or the server closes the
160
+ connection. Event has two optional attributes, <b><tt>code</tt></b> and
161
+ <b><tt>reason</tt></b>, that expose the status code and message sent by the
162
+ peer that closed the connection.
163
+ * <b><tt>send(message)</tt></b> accepts either a +String+ or an +Array+ of
164
+ byte-sized integers and sends a text or binary message over the connection to
165
+ the other peer.
166
+ * <b><tt>close(code, reason)</tt></b> closes the connection, sending the given
167
+ status code and reason text, both of which are optional.
168
+ * <b><tt>protocol</tt></b> is a string (which may be empty) identifying the
169
+ subprotocol the socket is using.
170
+
171
+
172
+ == Handling EventSource connections in Rack
173
+
174
+ EventSource connections provide a very similar interface, although because they
175
+ only allow the server to send data to the client, there is no +onmessage+ API.
176
+ EventSource allows the server to push text messages to the client, where each
177
+ message has an optional event-type and ID.
178
+
179
+ # app.rb
180
+ require 'faye/websocket'
181
+
182
+ App = lambda do |env|
183
+ if Faye::EventSource.eventsource?(env)
184
+ es = Faye::EventSource.new(env)
185
+ p [:open, es.url, es.last_event_id]
186
+
187
+ # Periodically send messages
188
+ loop = EM.add_periodic_timer(1) { es.send('Hello') }
189
+
190
+ es.onclose = lambda do |event|
191
+ EM.cancel_timer(loop)
192
+ es = nil
193
+ end
194
+
195
+ # Return async Rack response
196
+ es.rack_response
197
+
198
+ else
199
+ # Normal HTTP request
200
+ [200, {'Content-Type' => 'text/plain'}, ['Hello']]
201
+ end
202
+ end
203
+
204
+ The +send+ method takes two optional parameters, <tt>:event</tt> and
205
+ <tt>:id</tt>. The default event-type is <tt>'message'</tt> with no ID. For
206
+ example, to send a +notification+ event with ID +99+:
207
+
208
+ es.send('Breaking News!', :event => 'notification', :id => '99')
209
+
210
+ The +EventSource+ object exposes the following properties:
211
+
212
+ * <b><tt>url</tt></b> is a string containing the URL the client used to create
213
+ the EventSource.
214
+ * <b><tt>last_event_id</tt></b> is a string containing the last event ID
215
+ received by the client. You can use this when the client reconnects after a
216
+ dropped connection to determine which messages need resending.
217
+
218
+ When you initialize an EventSource with <tt>Faye::EventSource.new</tt>, you can
219
+ pass configuration options after the +env+ parameter. Available options are:
220
+
221
+ * <b><tt>:retry</tt></b> is a number that tells the client how long (in seconds)
222
+ it should wait after a dropped connection before attempting to reconnect.
223
+ * <b><tt>:ping</tt></b> is a number that tells the server how often (in seconds)
224
+ to send 'ping' packets to the client to keep the connection open, to defeat
225
+ timeouts set by proxies. The client will ignore these messages.
226
+
227
+ For example, this creates a connection that pings every 15 seconds and is
228
+ retryable every 10 seconds if the connection is broken:
229
+
230
+ es = Faye::EventSource.new(es, :ping => 15, :retry => 10)
231
+
232
+ You can send a ping message at any time by calling <tt>es.ping()</tt>. Unlike
233
+ WebSocket the client does not send a response to this; it is merely to send some
234
+ data over the wire to keep the connection alive.
235
+
236
+
237
+ == Running your socket application
238
+
239
+ To use this library you must be using an EventMachine-based server; currently
240
+ Thin, Rainbows and Goliath are supported.
241
+
242
+
243
+ === Running the app with Thin
244
+
245
+ Thin can be started via the command line if you've set up a <tt>config.ru</tt>
246
+ file for your application:
247
+
248
+ thin start -R config.ru -p 9292
249
+
250
+ Or, you can use +rackup+. In development mode, this adds middlewares that don't
251
+ work with async apps, so you must start it in production mode:
252
+
253
+ rackup config.ru -s thin -E production -p 9292
254
+
255
+ It can also be started using the <tt>Rack::Handler</tt> interface common to many
256
+ Ruby servers. It must be run using EventMachine, and you can configure Thin
257
+ further in a block passed to +run+:
258
+
259
+ require 'eventmachine'
260
+ require 'rack'
261
+ require 'thin'
262
+ require './app'
263
+
264
+ EM.run {
265
+ thin = Rack::Handler.get('thin')
266
+
267
+ thin.run(App, :Port => 9292) do |server|
268
+ # You can set options on the server here, for example to set up SSL:
269
+ server.ssl_options = {
270
+ :private_key_file => 'path/to/ssl.key',
271
+ :cert_chain_file => 'path/to/ssl.crt'
272
+ }
273
+ server.ssl = true
274
+ end
275
+ }
276
+
277
+
278
+ === Running the app with Rainbows
279
+
280
+ <tt>Faye::WebSocket</tt> can only be run using EventMachine. To begin with,
281
+ you'll need a Rainbows config file that tells it to use EventMachine, along with
282
+ whatever Rainbows/Unicorn configuration you require.
283
+
284
+ # rainbows.conf
285
+ Rainbows! do
286
+ use :EventMachine
287
+ end
288
+
289
+ You can then run your <tt>config.ru</tt> file from the command line. Again,
290
+ <tt>Rack::Lint</tt> will complain unless you put the application in production
291
+ mode.
292
+
293
+ rainbows config.ru -c path/to/rainbows.conf -E production -p 9292
294
+
295
+ Rainbows also has a Ruby API for starting a server:
296
+
297
+ require 'rainbows'
298
+ require './app'
299
+
300
+ rackup = Unicorn::Configurator::RACKUP
301
+ rackup[:port] = 9292
302
+ rackup[:set_listener] = true
303
+ options = rackup[:options]
304
+ options[:config_file] = 'path/to/rainbows.conf'
305
+
306
+ server = Rainbows::HttpServer.new(App, options)
307
+
308
+ # This is non-blocking; use server.start.join to block
309
+ server.start
310
+
311
+
312
+ === Running the app with Goliath
313
+
314
+ Goliath can be made to run arbitrary Rack apps by delegating to them from a
315
+ <tt>Goliath::API</tt> instance. A simple server looks like this:
316
+
317
+ require 'goliath'
318
+ require './app'
319
+
320
+ class EchoServer < Goliath::API
321
+ def response(env)
322
+ App.call(env)
323
+ end
324
+ end
325
+
326
+ <tt>Faye::WebSocket</tt> can also be used inline within a Goliath app:
327
+
328
+ require 'goliath'
329
+ require 'faye/websocket'
330
+
331
+ class EchoServer < Goliath::API
332
+ def response(env)
333
+ ws = Faye::WebSocket.new(env)
334
+
335
+ ws.onmessage = lambda do |event|
336
+ ws.send(event.data)
337
+ end
338
+
339
+ ws.rack_response
340
+ end
341
+ end
342
+
343
+
344
+ == License
345
+
346
+ (The MIT License)
347
+
348
+ Copyright (c) 2009-2012 James Coglan
349
+
350
+ Permission is hereby granted, free of charge, to any person obtaining a copy of
351
+ this software and associated documentation files (the 'Software'), to deal in
352
+ the Software without restriction, including without limitation the rights to use,
353
+ copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
354
+ Software, and to permit persons to whom the Software is furnished to do so,
355
+ subject to the following conditions:
356
+
357
+ The above copyright notice and this permission notice shall be included in all
358
+ copies or substantial portions of the Software.
359
+
360
+ THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
361
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
362
+ FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
363
+ COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
364
+ IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
365
+ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
366
+
@@ -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'], :ping => 5)
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
+