faye 0.7.2 → 0.8.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 might be problematic. Click here for more details.

Files changed (54) hide show
  1. data/History.txt +15 -3
  2. data/README.rdoc +2 -6
  3. data/lib/faye-browser-min.js +1 -1
  4. data/lib/faye.rb +25 -36
  5. data/lib/faye/adapters/rack_adapter.rb +43 -22
  6. data/lib/faye/engines/connection.rb +7 -10
  7. data/lib/faye/engines/memory.rb +28 -28
  8. data/lib/faye/engines/proxy.rb +109 -0
  9. data/lib/faye/mixins/logging.rb +1 -8
  10. data/lib/faye/mixins/timeouts.rb +1 -1
  11. data/lib/faye/protocol/channel.rb +3 -3
  12. data/lib/faye/protocol/client.rb +50 -45
  13. data/lib/faye/protocol/extensible.rb +11 -18
  14. data/lib/faye/protocol/server.rb +53 -38
  15. data/lib/faye/transport/http.rb +49 -27
  16. data/lib/faye/transport/transport.rb +3 -1
  17. data/lib/faye/transport/web_socket.rb +6 -10
  18. data/lib/faye/util/namespace.rb +2 -2
  19. data/spec/browser.html +3 -1
  20. data/spec/encoding_helper.rb +7 -0
  21. data/spec/javascript/client_spec.js +0 -5
  22. data/spec/javascript/engine/memory_spec.js +7 -0
  23. data/spec/javascript/engine_spec.js +22 -57
  24. data/spec/javascript/server/handshake_spec.js +10 -6
  25. data/spec/javascript/server/integration_spec.js +11 -10
  26. data/spec/javascript/server/publish_spec.js +85 -0
  27. data/spec/javascript/server_spec.js +5 -51
  28. data/spec/node.js +6 -6
  29. data/spec/ruby/client_spec.rb +1 -1
  30. data/spec/ruby/engine/memory_spec.rb +7 -0
  31. data/spec/ruby/{engine_spec.rb → engine_examples.rb} +28 -34
  32. data/spec/ruby/rack_adapter_spec.rb +1 -1
  33. data/spec/ruby/server/handshake_spec.rb +10 -6
  34. data/spec/ruby/server/publish_spec.rb +81 -0
  35. data/spec/ruby/server_spec.rb +6 -44
  36. data/spec/spec_helper.rb +5 -18
  37. data/spec/testswarm +1 -5
  38. metadata +105 -180
  39. data/lib/faye/engines/base.rb +0 -66
  40. data/lib/faye/engines/redis.rb +0 -225
  41. data/lib/faye/thin_extensions.rb +0 -75
  42. data/lib/faye/util/web_socket.rb +0 -89
  43. data/lib/faye/util/web_socket/api.rb +0 -103
  44. data/lib/faye/util/web_socket/client.rb +0 -82
  45. data/lib/faye/util/web_socket/draft75_parser.rb +0 -53
  46. data/lib/faye/util/web_socket/draft76_parser.rb +0 -53
  47. data/lib/faye/util/web_socket/protocol8_parser.rb +0 -324
  48. data/spec/javascript/web_socket/client_spec.js +0 -121
  49. data/spec/javascript/web_socket/draft75parser_spec.js +0 -39
  50. data/spec/javascript/web_socket/protocol8parser_spec.js +0 -153
  51. data/spec/redis.conf +0 -42
  52. data/spec/ruby/web_socket/client_spec.rb +0 -126
  53. data/spec/ruby/web_socket/draft75_parser_spec.rb +0 -41
  54. data/spec/ruby/web_socket/protocol8_parser_spec.rb +0 -145
@@ -1,66 +0,0 @@
1
- module Faye
2
- module Engine
3
-
4
- MAX_DELAY = 0.0
5
- INTERVAL = 0.0
6
- TIMEOUT = 60.0
7
-
8
- def self.register(type, klass)
9
- @backends ||= {}
10
- @backends[type] = klass
11
- end
12
-
13
- def self.get(options)
14
- options ||= {}
15
- klass = @backends[options[:type]] || Memory
16
- klass.new(options)
17
- end
18
-
19
- class Base
20
- include Publisher
21
- include Logging
22
-
23
- attr_reader :interval, :timeout
24
-
25
- def initialize(options)
26
- @options = options
27
- @connections = {}
28
- @interval = @options[:interval] || INTERVAL
29
- @timeout = @options[:timeout] || TIMEOUT
30
-
31
- debug 'Created new engine: ?', @options
32
- end
33
-
34
- def connect(client_id, options = {}, &callback)
35
- debug 'Accepting connection from ?', client_id
36
- ping(client_id)
37
- conn = connection(client_id, true)
38
- conn.connect(options, &callback)
39
- empty_queue(client_id)
40
- end
41
-
42
- def connection(client_id, create)
43
- conn = @connections[client_id]
44
- return conn if conn or not create
45
- @connections[client_id] = Connection.new(self, client_id)
46
- end
47
-
48
- def close_connection(client_id)
49
- debug 'Closing connection for ?', client_id
50
- @connections.delete(client_id)
51
- end
52
-
53
- def flush(client_id)
54
- debug 'Flushing message queue for ?', client_id
55
- conn = @connections[client_id]
56
- conn.flush! if conn
57
- end
58
- end
59
-
60
- %w[connection memory redis].each do |type|
61
- require File.join(ROOT, 'faye', 'engines', type)
62
- end
63
-
64
- end
65
- end
66
-
@@ -1,225 +0,0 @@
1
- module Faye
2
- module Engine
3
-
4
- class Redis < Base
5
- DEFAULT_HOST = 'localhost'
6
- DEFAULT_PORT = 6379
7
- DEFAULT_DATABASE = 0
8
- DEFAULT_GC = 60
9
- LOCK_TIMEOUT = 120
10
-
11
- def init
12
- return if @redis
13
- require 'em-hiredis'
14
-
15
- host = @options[:host] || DEFAULT_HOST
16
- port = @options[:port] || DEFAULT_PORT
17
- db = @options[:database] || 0
18
- auth = @options[:password]
19
- gc = @options[:gc] || DEFAULT_GC
20
- @ns = @options[:namespace] || ''
21
- socket = @options[:socket]
22
-
23
- if socket
24
- @redis = EventMachine::Hiredis::Client.connect(socket, nil)
25
- @subscriber = EventMachine::Hiredis::Client.connect(socket, nil)
26
- else
27
- @redis = EventMachine::Hiredis::Client.connect(host, port)
28
- @subscriber = EventMachine::Hiredis::Client.connect(host, port)
29
- end
30
-
31
- if auth
32
- @redis.auth(auth)
33
- @subscriber.auth(auth)
34
- end
35
- @redis.select(db)
36
- @subscriber.select(db)
37
-
38
- @subscriber.subscribe(@ns + '/notifications')
39
- @subscriber.on(:message) do |topic, message|
40
- empty_queue(message) if topic == @ns + '/notifications'
41
- end
42
-
43
- @gc = EventMachine.add_periodic_timer(gc, &method(:gc))
44
- end
45
-
46
- def disconnect
47
- @subscriber.unsubscribe(@ns + '/notifications')
48
- EventMachine.cancel_timer(@gc)
49
- end
50
-
51
- def create_client(&callback)
52
- init
53
- client_id = Faye.random
54
- @redis.zadd(@ns + '/clients', 0, client_id) do |added|
55
- if added == 0
56
- create_client(&callback)
57
- else
58
- debug 'Created new client ?', client_id
59
- ping(client_id)
60
- callback.call(client_id)
61
- trigger(:handshake, client_id)
62
- end
63
- end
64
- end
65
-
66
- def destroy_client(client_id, &callback)
67
- init
68
- @redis.zrem(@ns + '/clients', client_id)
69
- @redis.del(@ns + "/clients/#{client_id}/messages")
70
-
71
- @redis.smembers(@ns + "/clients/#{client_id}/channels") do |channels|
72
- n, i = channels.size, 0
73
- if n == 0
74
- debug 'Destroyed client ?', client_id
75
- callback.call if callback
76
- trigger(:disconnect, client_id)
77
- else
78
- channels.each do |channel|
79
- unsubscribe(client_id, channel) do
80
- i += 1
81
- if i == n
82
- debug 'Destroyed client ?', client_id
83
- callback.call if callback
84
- trigger(:disconnect, client_id)
85
- end
86
- end
87
- end
88
- end
89
- end
90
- end
91
-
92
- def client_exists(client_id, &callback)
93
- init
94
- @redis.zscore(@ns + '/clients', client_id) do |score|
95
- callback.call(score != nil)
96
- end
97
- end
98
-
99
- def ping(client_id)
100
- init
101
- return unless Numeric === @timeout
102
-
103
- time = get_current_time
104
- debug 'Ping ?, ?', client_id, time
105
- @redis.zadd(@ns + '/clients', time, client_id)
106
- end
107
-
108
- def subscribe(client_id, channel, &callback)
109
- init
110
- @redis.sadd(@ns + "/clients/#{client_id}/channels", channel) do |added|
111
- trigger(:subscribe, client_id, channel) if added == 1
112
- end
113
- @redis.sadd(@ns + "/channels#{channel}", client_id) do
114
- debug 'Subscribed client ? to channel ?', client_id, channel
115
- callback.call if callback
116
- end
117
- end
118
-
119
- def unsubscribe(client_id, channel, &callback)
120
- init
121
- @redis.srem(@ns + "/clients/#{client_id}/channels", channel) do |removed|
122
- trigger(:unsubscribe, client_id, channel) if removed == 1
123
- end
124
- @redis.srem(@ns + "/channels#{channel}", client_id) do
125
- debug 'Unsubscribed client ? from channel ?', client_id, channel
126
- callback.call if callback
127
- end
128
- end
129
-
130
- def publish(message)
131
- init
132
- debug 'Publishing message ?', message
133
-
134
- json_message = JSON.dump(message)
135
- channels = Channel.expand(message['channel'])
136
- keys = channels.map { |c| @ns + "/channels#{c}" }
137
-
138
- @redis.sunion(*keys) do |clients|
139
- clients.each do |client_id|
140
- debug 'Queueing for client ?: ?', client_id, message
141
- @redis.rpush(@ns + "/clients/#{client_id}/messages", json_message)
142
- @redis.publish(@ns + '/notifications', client_id)
143
- end
144
- end
145
-
146
- trigger(:publish, message['clientId'], message['channel'], message['data'])
147
- end
148
-
149
- private
150
-
151
- def get_current_time
152
- (Time.now.to_f * 1000).to_i
153
- end
154
-
155
- def empty_queue(client_id)
156
- return unless conn = connection(client_id, false)
157
- init
158
-
159
- key = @ns + "/clients/#{client_id}/messages"
160
-
161
- @redis.multi
162
- @redis.lrange(key, 0, -1)
163
- @redis.del(key)
164
- @redis.exec.callback do |json_messages, deleted|
165
- json_messages.each do |json_message|
166
- conn.deliver(JSON.parse(json_message))
167
- end
168
- end
169
- end
170
-
171
- def gc
172
- return unless Numeric === @timeout
173
- with_lock 'gc' do |release_lock|
174
- cutoff = get_current_time - 1000 * 2 * @timeout
175
- @redis.zrangebyscore(@ns + '/clients', 0, cutoff) do |clients|
176
- i, n = 0, clients.size
177
- if i == n
178
- release_lock.call
179
- else
180
- clients.each do |client_id|
181
- destroy_client(client_id) do
182
- i += 1
183
- release_lock.call if i == n
184
- end
185
- end
186
- end
187
- end
188
- end
189
- end
190
-
191
- def with_lock(lock_name, &block)
192
- lock_key = @ns + '/locks/' + lock_name
193
- current_time = get_current_time
194
- expiry = current_time + LOCK_TIMEOUT * 1000 + 1
195
-
196
- release_lock = lambda do
197
- @redis.del(lock_key) if get_current_time < expiry
198
- end
199
-
200
- @redis.setnx(lock_key, expiry) do |set|
201
- if set == 1
202
- block.call(release_lock)
203
- else
204
-
205
- @redis.get(lock_key) do |timeout|
206
- if timeout
207
- lock_timeout = timeout.to_i(10)
208
- if lock_timeout < current_time
209
- @redis.getset(lock_key, expiry) do |old_value|
210
- block.call(release_lock) if old_value == timeout
211
- end
212
- end
213
- end
214
- end
215
-
216
- end
217
- end
218
- end
219
-
220
- end
221
-
222
- register 'redis', Redis
223
-
224
- end
225
- end
@@ -1,75 +0,0 @@
1
- # WebSocket extensions for Thin
2
- # Based on code from the Cramp project
3
- # http://github.com/lifo/cramp
4
-
5
- # Copyright (c) 2009-2011 Pratik Naik
6
- #
7
- # Permission is hereby granted, free of charge, to any person obtaining
8
- # a copy of this software and associated documentation files (the
9
- # "Software"), to deal in the Software without restriction, including
10
- # without limitation the rights to use, copy, modify, merge, publish,
11
- # distribute, sublicense, and/or sell copies of the Software, and to
12
- # permit persons to whom the Software is furnished to do so, subject to
13
- # the following conditions:
14
- #
15
- # The above copyright notice and this permission notice shall be
16
- # included in all copies or substantial portions of the Software.
17
- #
18
- # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
19
- # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
20
- # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
21
- # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
22
- # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
23
- # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
24
- # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
25
-
26
- class Thin::Connection
27
- def receive_data(data)
28
- trace { data }
29
-
30
- case @serving
31
- when :websocket
32
- callback = @request.env[Thin::Request::WEBSOCKET_RECEIVE_CALLBACK]
33
- callback.call(data) if callback
34
- else
35
- if @request.parse(data)
36
- if @request.websocket?
37
- @request.env['em.connection'] = self
38
- @response.persistent!
39
- @response.websocket = true
40
- @serving = :websocket
41
- end
42
-
43
- process
44
- end
45
- end
46
- rescue Thin::InvalidRequest => e
47
- log "!! Invalid request"
48
- log_error e
49
- close_connection
50
- end
51
- end
52
-
53
- class Thin::Request
54
- WEBSOCKET_RECEIVE_CALLBACK = 'websocket.receive_callback'.freeze
55
- def websocket?
56
- @env['HTTP_CONNECTION'] and
57
- @env['HTTP_CONNECTION'].split(/\s*,\s*/).include?('Upgrade') and
58
- ['WebSocket', 'websocket'].include?(@env['HTTP_UPGRADE'])
59
- end
60
- end
61
-
62
- class Thin::Response
63
- # Headers for sending Websocket upgrade
64
- attr_accessor :websocket
65
-
66
- def each
67
- yield(head) unless websocket
68
- if @body.is_a?(String)
69
- yield @body
70
- else
71
- @body.each { |chunk| yield chunk }
72
- end
73
- end
74
- end
75
-
@@ -1,89 +0,0 @@
1
- module Faye
2
- class WebSocket
3
-
4
- root = File.expand_path('..', __FILE__) + '/web_socket'
5
-
6
- autoload :API, root + '/api'
7
- autoload :Client, root + '/client'
8
- autoload :Draft75Parser, root + '/draft75_parser'
9
- autoload :Draft76Parser, root + '/draft76_parser'
10
- autoload :Protocol8Parser, root + '/protocol8_parser'
11
-
12
- attr_reader :env
13
- include API
14
-
15
- extend Forwardable
16
- def_delegators :@parser, :version
17
-
18
- def self.parser(env)
19
- if env['HTTP_SEC_WEBSOCKET_VERSION']
20
- Protocol8Parser
21
- elsif env['HTTP_SEC_WEBSOCKET_KEY1']
22
- Draft76Parser
23
- else
24
- Draft75Parser
25
- end
26
- end
27
-
28
- def initialize(env)
29
- @env = env
30
- @callback = @env['async.callback']
31
- @stream = Stream.new(self, @env['em.connection'])
32
- @callback.call [200, RackAdapter::TYPE_JSON, @stream]
33
-
34
- @url = determine_url
35
- @ready_state = CONNECTING
36
- @buffered_amount = 0
37
-
38
- @parser = WebSocket.parser(@env).new(self)
39
- @stream.write(@parser.handshake_response)
40
-
41
- @ready_state = OPEN
42
-
43
- event = Event.new('open')
44
- event.init_event('open', false, false)
45
- dispatch_event(event)
46
-
47
- @env[Thin::Request::WEBSOCKET_RECEIVE_CALLBACK] = @parser.method(:parse)
48
- end
49
-
50
- private
51
-
52
- def determine_url
53
- secure = if @env.has_key?('HTTP_X_FORWARDED_PROTO')
54
- @env['HTTP_X_FORWARDED_PROTO'] == 'https'
55
- else
56
- @env['HTTP_ORIGIN'] =~ /^https:/i
57
- end
58
-
59
- scheme = secure ? 'wss:' : 'ws:'
60
- "#{ scheme }//#{ @env['HTTP_HOST'] }#{ @env['REQUEST_URI'] }"
61
- end
62
- end
63
-
64
- class WebSocket::Stream
65
- include EventMachine::Deferrable
66
-
67
- extend Forwardable
68
- def_delegators :@connection, :close_connection, :close_connection_after_writing
69
-
70
- def initialize(web_socket, connection)
71
- @web_socket = web_socket
72
- @connection = connection
73
- end
74
-
75
- def each(&callback)
76
- @data_callback = callback
77
- end
78
-
79
- def fail
80
- @web_socket.close(1006, '', false)
81
- end
82
-
83
- def write(data)
84
- return unless @data_callback
85
- @data_callback.call(data)
86
- end
87
- end
88
-
89
- end