_bushido-faye 0.8.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (69) hide show
  1. data/History.txt +247 -0
  2. data/README.rdoc +92 -0
  3. data/lib/faye-browser-min.js +1 -0
  4. data/lib/faye.rb +121 -0
  5. data/lib/faye/adapters/rack_adapter.rb +210 -0
  6. data/lib/faye/engines/connection.rb +60 -0
  7. data/lib/faye/engines/memory.rb +112 -0
  8. data/lib/faye/engines/proxy.rb +111 -0
  9. data/lib/faye/error.rb +49 -0
  10. data/lib/faye/mixins/logging.rb +47 -0
  11. data/lib/faye/mixins/publisher.rb +30 -0
  12. data/lib/faye/mixins/timeouts.rb +22 -0
  13. data/lib/faye/protocol/channel.rb +124 -0
  14. data/lib/faye/protocol/client.rb +378 -0
  15. data/lib/faye/protocol/extensible.rb +43 -0
  16. data/lib/faye/protocol/grammar.rb +58 -0
  17. data/lib/faye/protocol/publication.rb +5 -0
  18. data/lib/faye/protocol/server.rb +282 -0
  19. data/lib/faye/protocol/subscription.rb +24 -0
  20. data/lib/faye/transport/http.rb +76 -0
  21. data/lib/faye/transport/local.rb +22 -0
  22. data/lib/faye/transport/transport.rb +115 -0
  23. data/lib/faye/transport/web_socket.rb +99 -0
  24. data/lib/faye/util/namespace.rb +20 -0
  25. data/spec/browser.html +45 -0
  26. data/spec/encoding_helper.rb +7 -0
  27. data/spec/install.sh +78 -0
  28. data/spec/javascript/channel_spec.js +15 -0
  29. data/spec/javascript/client_spec.js +714 -0
  30. data/spec/javascript/engine/memory_spec.js +7 -0
  31. data/spec/javascript/engine_spec.js +417 -0
  32. data/spec/javascript/faye_spec.js +15 -0
  33. data/spec/javascript/grammar_spec.js +66 -0
  34. data/spec/javascript/node_adapter_spec.js +307 -0
  35. data/spec/javascript/publisher_spec.js +27 -0
  36. data/spec/javascript/server/connect_spec.js +168 -0
  37. data/spec/javascript/server/disconnect_spec.js +121 -0
  38. data/spec/javascript/server/extensions_spec.js +60 -0
  39. data/spec/javascript/server/handshake_spec.js +145 -0
  40. data/spec/javascript/server/integration_spec.js +124 -0
  41. data/spec/javascript/server/publish_spec.js +85 -0
  42. data/spec/javascript/server/subscribe_spec.js +247 -0
  43. data/spec/javascript/server/unsubscribe_spec.js +245 -0
  44. data/spec/javascript/server_spec.js +110 -0
  45. data/spec/javascript/transport_spec.js +130 -0
  46. data/spec/node.js +55 -0
  47. data/spec/phantom.js +17 -0
  48. data/spec/ruby/channel_spec.rb +17 -0
  49. data/spec/ruby/client_spec.rb +724 -0
  50. data/spec/ruby/engine/memory_spec.rb +7 -0
  51. data/spec/ruby/engine_examples.rb +427 -0
  52. data/spec/ruby/faye_spec.rb +14 -0
  53. data/spec/ruby/grammar_spec.rb +68 -0
  54. data/spec/ruby/publisher_spec.rb +27 -0
  55. data/spec/ruby/rack_adapter_spec.rb +236 -0
  56. data/spec/ruby/server/connect_spec.rb +170 -0
  57. data/spec/ruby/server/disconnect_spec.rb +120 -0
  58. data/spec/ruby/server/extensions_spec.rb +68 -0
  59. data/spec/ruby/server/handshake_spec.rb +143 -0
  60. data/spec/ruby/server/integration_spec.rb +126 -0
  61. data/spec/ruby/server/publish_spec.rb +81 -0
  62. data/spec/ruby/server/subscribe_spec.rb +247 -0
  63. data/spec/ruby/server/unsubscribe_spec.rb +247 -0
  64. data/spec/ruby/server_spec.rb +110 -0
  65. data/spec/ruby/transport_spec.rb +134 -0
  66. data/spec/spec_helper.rb +11 -0
  67. data/spec/testswarm +29 -0
  68. data/spec/thin_proxy.rb +37 -0
  69. metadata +302 -0
@@ -0,0 +1,43 @@
1
+ module Faye
2
+ module Extensible
3
+ include Logging
4
+
5
+ def add_extension(extension)
6
+ @extensions ||= []
7
+ @extensions << extension
8
+ extension.added(self) if extension.respond_to?(:added)
9
+ end
10
+
11
+ def remove_extension(extension)
12
+ return unless @extensions
13
+ @extensions.delete_if do |ext|
14
+ next false unless ext == extension
15
+ extension.removed(self) if extension.respond_to?(:removed)
16
+ true
17
+ end
18
+ end
19
+
20
+ def pipe_through_extensions(stage, message, &callback)
21
+ debug 'Passing through ? extensions: ?', stage, message
22
+
23
+ return callback.call(message) unless @extensions
24
+ extensions = @extensions.dup
25
+
26
+ pipe = lambda do |message|
27
+ next callback.call(message) unless message
28
+
29
+ extension = extensions.shift
30
+ next callback.call(message) unless extension
31
+
32
+ if extension.respond_to?(stage)
33
+ extension.__send__(stage, message, pipe)
34
+ else
35
+ pipe.call(message)
36
+ end
37
+ end
38
+ pipe.call(message)
39
+ end
40
+
41
+ end
42
+ end
43
+
@@ -0,0 +1,58 @@
1
+ module Faye
2
+ module Grammar
3
+
4
+ def self.rule(&block)
5
+ source = instance_eval(&block)
6
+ %r{^#{string(source)}$}
7
+ end
8
+
9
+ def self.choice(*list)
10
+ '(' + list.map(&method(:string)) * '|' + ')'
11
+ end
12
+
13
+ def self.repeat(*pattern)
14
+ '(' + string(pattern) + ')*'
15
+ end
16
+
17
+ def self.oneormore(*pattern)
18
+ '(' + string(pattern) + ')+'
19
+ end
20
+
21
+ def self.string(item)
22
+ return item.map(&method(:string)) * '' if Array === item
23
+ String === item ? item : item.source.gsub(/^\^/, '').gsub(/\$$/, '')
24
+ end
25
+
26
+ LOWALPHA = rule {[ '[a-z]' ]}
27
+ UPALPHA = rule {[ '[A-Z]' ]}
28
+ ALPHA = rule {[ choice(LOWALPHA, UPALPHA) ]}
29
+ DIGIT = rule {[ '[0-9]' ]}
30
+ ALPHANUM = rule {[ choice(ALPHA, DIGIT) ]}
31
+ MARK = rule {[ choice(*%w[\\- \\_ \\! \\~ \\( \\) \\$ \\@]) ]}
32
+ STRING = rule {[ repeat(choice(ALPHANUM, MARK, ' ', '\\/', '\\*', '\\.')) ]}
33
+ TOKEN = rule {[ oneormore(choice(ALPHANUM, MARK)) ]}
34
+ INTEGER = rule {[ oneormore(DIGIT) ]}
35
+
36
+ CHANNEL_SEGMENT = rule {[ TOKEN ]}
37
+ CHANNEL_SEGMENTS = rule {[ CHANNEL_SEGMENT, repeat('\\/', CHANNEL_SEGMENT) ]}
38
+ CHANNEL_NAME = rule {[ '\\/', CHANNEL_SEGMENTS ]}
39
+
40
+ WILD_CARD = rule {[ '\\*{1,2}' ]}
41
+ CHANNEL_PATTERN = rule {[ repeat('\\/', CHANNEL_SEGMENT), '\\/', WILD_CARD ]}
42
+
43
+ VERSION_ELEMENT = rule {[ ALPHANUM, repeat(choice(ALPHANUM, '\\-', '\\_')) ]}
44
+ VERSION = rule {[ INTEGER, repeat('\\.', VERSION_ELEMENT) ]}
45
+
46
+ CLIENT_ID = rule {[ oneormore(ALPHANUM) ]}
47
+
48
+ ID = rule {[ oneormore(ALPHANUM) ]}
49
+
50
+ ERROR_MESSAGE = rule {[ STRING ]}
51
+ ERROR_ARGS = rule {[ STRING, repeat(',', STRING) ]}
52
+ ERROR_CODE = rule {[ DIGIT, DIGIT, DIGIT ]}
53
+ ERROR = rule {[ choice(string([ERROR_CODE, ':', ERROR_ARGS, ':', ERROR_MESSAGE]),
54
+ string([ERROR_CODE, ':', ':', ERROR_MESSAGE])) ]}
55
+
56
+ end
57
+ end
58
+
@@ -0,0 +1,5 @@
1
+ module Faye
2
+ class Publication
3
+ include EventMachine::Deferrable
4
+ end
5
+ end
@@ -0,0 +1,282 @@
1
+ module Faye
2
+ class Server
3
+
4
+ include Logging
5
+ include Extensible
6
+
7
+ attr_reader :engine
8
+
9
+ def initialize(options = {})
10
+ @options = options || {}
11
+ engine_opts = @options[:engine] || {}
12
+ engine_opts[:timeout] = @options[:timeout]
13
+ @engine = Faye::Engine.get(engine_opts)
14
+
15
+ info 'Created new server: ?', @options
16
+ end
17
+
18
+ def flush_connection(messages)
19
+ client_id = Faye.client_id_from_messages(messages)
20
+ info 'Flushing connection for ?', client_id
21
+ @engine.flush(client_id) if client_id
22
+ end
23
+
24
+ def open_socket(client_id, socket)
25
+ @engine.open_socket(client_id, socket)
26
+ end
27
+
28
+ def close_socket(client_id)
29
+ @engine.flush(client_id)
30
+ end
31
+
32
+ def process(messages, local = false, &callback)
33
+ messages = [messages].flatten
34
+ info 'Processing messages: ? (local: ?)', messages, local
35
+
36
+ return callback.call([]) if messages.size == 0
37
+ processed, responses = 0, []
38
+
39
+ gather_replies = lambda do |replies|
40
+ responses.concat(replies)
41
+ processed += 1
42
+ responses.compact!
43
+ info 'Returning replies: ?', responses
44
+ callback.call(responses) if processed == messages.size
45
+ end
46
+
47
+ handle_reply = lambda do |replies|
48
+ extended, expected = 0, replies.size
49
+ gather_replies.call(replies) if expected == 0
50
+
51
+ replies.each_with_index do |reply, i|
52
+ debug 'Processing reply: ?', reply
53
+ pipe_through_extensions(:outgoing, reply) do |message|
54
+ replies[i] = message
55
+ extended += 1
56
+ gather_replies.call(replies) if extended == expected
57
+ end
58
+ end
59
+ end
60
+
61
+ messages.each do |message|
62
+ pipe_through_extensions(:incoming, message) do |piped_message|
63
+ handle(piped_message, local, &handle_reply)
64
+ end
65
+ end
66
+ end
67
+
68
+ def make_response(message)
69
+ response = {}
70
+
71
+ response['id'] = message['id'] if message['id']
72
+ response['clientId'] = message['clientId'] if message['clientId']
73
+ response['channel'] = message['channel'] if message['channel']
74
+ response['error'] = message['error'] if message['error']
75
+
76
+ response['successful'] = !response['error']
77
+ response
78
+ end
79
+
80
+ def handle(message, local = false, &callback)
81
+ return callback.call([]) if !message
82
+ info 'Handling message: ? (local: ?)', message, local
83
+
84
+ channel_name = message['channel']
85
+ error = message['error']
86
+
87
+ return handle_meta(message, local, &callback) if Channel.meta?(channel_name)
88
+
89
+ if Grammar::CHANNEL_NAME !~ channel_name
90
+ error = Faye::Error.channel_invalid(channel_name)
91
+ end
92
+
93
+ @engine.publish(message) unless error
94
+
95
+ response = make_response(message)
96
+ response['error'] = error if error
97
+ response['successful'] = !response['error']
98
+ callback.call([response])
99
+ end
100
+
101
+ def handle_meta(message, local, &callback)
102
+ method = Channel.parse(message['channel'])[1]
103
+ client_id = message['clientId']
104
+
105
+ __send__(method, message, local) do |responses|
106
+ responses = [responses].flatten
107
+ responses.each { |r| advize(r, message['connectionType']) }
108
+ callback.call(responses)
109
+ end
110
+ end
111
+
112
+ def advize(response, connection_type)
113
+ return unless [Channel::HANDSHAKE, Channel::CONNECT].include?(response['channel'])
114
+
115
+ if connection_type == 'eventsource'
116
+ interval = (@engine.timeout * 1000).floor
117
+ timeout = 0
118
+ else
119
+ interval = (@engine.interval * 1000).floor
120
+ timeout = (@engine.timeout * 1000).floor
121
+ end
122
+
123
+ advice = response['advice'] ||= {}
124
+ if response['error']
125
+ advice['reconnect'] ||= 'handshake'
126
+ else
127
+ advice['reconnect'] ||= 'retry'
128
+ advice['interval'] ||= interval
129
+ advice['timeout'] ||= timeout
130
+ end
131
+ end
132
+
133
+ # MUST contain * version
134
+ # * supportedConnectionTypes
135
+ # MAY contain * minimumVersion
136
+ # * ext
137
+ # * id
138
+ def handshake(message, local = false, &callback)
139
+ response = make_response(message)
140
+ response['version'] = BAYEUX_VERSION
141
+
142
+ response['error'] = Error.parameter_missing('version') if message['version'].nil?
143
+
144
+ client_conns = message['supportedConnectionTypes']
145
+
146
+ response['supportedConnectionTypes'] = CONNECTION_TYPES
147
+
148
+ if client_conns
149
+ common_conns = client_conns.select { |c| CONNECTION_TYPES.include?(c) }
150
+ response['error'] = Error.conntype_mismatch(*client_conns) if common_conns.empty?
151
+ else
152
+ response['error'] = Error.parameter_missing('supportedConnectionTypes')
153
+ end
154
+
155
+ response['successful'] = response['error'].nil?
156
+ return callback.call(response) unless response['successful']
157
+
158
+ @engine.create_client do |client_id|
159
+ response['clientId'] = client_id
160
+ callback.call(response)
161
+ end
162
+ end
163
+
164
+ # MUST contain * clientId
165
+ # * connectionType
166
+ # MAY contain * ext
167
+ # * id
168
+ def connect(message, local = false, &callback)
169
+ response = make_response(message)
170
+ client_id = message['clientId']
171
+ connection_type = message['connectionType']
172
+
173
+ @engine.client_exists(client_id) do |exists|
174
+ response['error'] = Error.client_unknown(client_id) unless exists
175
+ response['error'] = Error.parameter_missing('clientId') if client_id.nil?
176
+
177
+ unless CONNECTION_TYPES.include?(connection_type)
178
+ response['error'] = Error.conntype_mismatch(connection_type)
179
+ end
180
+
181
+ response['error'] = Error.parameter_missing('connectionType') if connection_type.nil?
182
+
183
+ response['successful'] = response['error'].nil?
184
+
185
+ if !response['successful']
186
+ response.delete('clientId')
187
+ next callback.call(response)
188
+ end
189
+
190
+ if message['connectionType'] == 'eventsource'
191
+ message['advice'] ||= {}
192
+ message['advice']['timeout'] = 0
193
+ end
194
+
195
+ @engine.connect(response['clientId'], message['advice']) do |events|
196
+ callback.call([response] + events)
197
+ end
198
+ end
199
+ end
200
+
201
+ # MUST contain * clientId
202
+ # MAY contain * ext
203
+ # * id
204
+ def disconnect(message, local = false, &callback)
205
+ response = make_response(message)
206
+ client_id = message['clientId']
207
+
208
+ @engine.client_exists(client_id) do |exists|
209
+ response['error'] = Error.client_unknown(client_id) unless exists
210
+ response['error'] = Error.parameter_missing('clientId') if client_id.nil?
211
+
212
+ response['successful'] = response['error'].nil?
213
+ response.delete('clientId') unless response['successful']
214
+
215
+ @engine.destroy_client(client_id) if response['successful']
216
+ callback.call(response)
217
+ end
218
+ end
219
+
220
+ # MUST contain * clientId
221
+ # * subscription
222
+ # MAY contain * ext
223
+ # * id
224
+ def subscribe(message, local = false, &callback)
225
+ response = make_response(message)
226
+ client_id = message['clientId']
227
+ subscription = [message['subscription']].flatten
228
+
229
+ @engine.client_exists(client_id) do |exists|
230
+ response['error'] = Error.client_unknown(client_id) unless exists
231
+ response['error'] = Error.parameter_missing('clientId') if client_id.nil?
232
+ response['error'] = Error.parameter_missing('subscription') if message['subscription'].nil?
233
+
234
+ response['subscription'] = message['subscription'] || []
235
+
236
+ subscription.each do |channel|
237
+ next if response['error']
238
+ response['error'] = Error.channel_forbidden(channel) unless local or Channel.subscribable?(channel)
239
+ response['error'] = Error.channel_invalid(channel) unless Channel.valid?(channel)
240
+
241
+ next if response['error']
242
+ @engine.subscribe(client_id, channel)
243
+ end
244
+
245
+ response['successful'] = response['error'].nil?
246
+ callback.call(response)
247
+ end
248
+ end
249
+
250
+ # MUST contain * clientId
251
+ # * subscription
252
+ # MAY contain * ext
253
+ # * id
254
+ def unsubscribe(message, local = false, &callback)
255
+ response = make_response(message)
256
+ client_id = message['clientId']
257
+ subscription = [message['subscription']].flatten
258
+
259
+ @engine.client_exists(client_id) do |exists|
260
+ response['error'] = Error.client_unknown(client_id) unless exists
261
+ response['error'] = Error.parameter_missing('clientId') if client_id.nil?
262
+ response['error'] = Error.parameter_missing('subscription') if message['subscription'].nil?
263
+
264
+ response['subscription'] = message['subscription'] || []
265
+
266
+ subscription.each do |channel|
267
+ next if response['error']
268
+ response['error'] = Error.channel_forbidden(channel) unless local or Channel.subscribable?(channel)
269
+ response['error'] = Error.channel_invalid(channel) unless Channel.valid?(channel)
270
+
271
+ next if response['error']
272
+ @engine.unsubscribe(client_id, channel)
273
+ end
274
+
275
+ response['successful'] = response['error'].nil?
276
+ callback.call(response)
277
+ end
278
+ end
279
+
280
+ end
281
+ end
282
+
@@ -0,0 +1,24 @@
1
+ module Faye
2
+ class Subscription
3
+ include EventMachine::Deferrable
4
+
5
+ def initialize(client, channels, callback)
6
+ @client = client
7
+ @channels = channels
8
+ @callback = callback
9
+ @cancelled = false
10
+ end
11
+
12
+ def cancel
13
+ return if @cancelled
14
+ @client.unsubscribe(@channels, &@callback)
15
+ @cancelled = true
16
+ end
17
+
18
+ def unsubscribe
19
+ cancel
20
+ end
21
+
22
+ end
23
+ end
24
+
@@ -0,0 +1,76 @@
1
+ module Faye
2
+
3
+ class Transport::Http < Transport
4
+ def self.usable?(endpoint, &callback)
5
+ callback.call(endpoint.is_a?(String))
6
+ end
7
+
8
+ def request(message, timeout)
9
+ retry_block = retry_block(message, timeout)
10
+
11
+ content = Faye.to_json(message)
12
+ cookies = @cookies.get_cookies(@endpoint)
13
+ params = build_params(URI.parse(@endpoint), content, cookies)
14
+ request = create_request(params)
15
+
16
+ request.callback do
17
+ handle_response(request.response, retry_block)
18
+ store_cookies([*request.response_header['SET_COOKIE']].compact)
19
+ end
20
+ request.errback do
21
+ retry_block.call
22
+ trigger(:down)
23
+ end
24
+ end
25
+
26
+ private
27
+
28
+ def build_params(uri, content, cookies)
29
+ {
30
+ :head => {
31
+ 'Content-Length' => content.bytesize,
32
+ 'Content-Type' => 'application/json',
33
+ 'Cookie' => cookies * '; ',
34
+ 'Host' => uri.host
35
+ }.merge(@headers),
36
+
37
+ :body => content,
38
+ :timeout => -1 # for em-http-request < 1.0
39
+ }
40
+ end
41
+
42
+ def create_request(params)
43
+ version = EventMachine::HttpRequest::VERSION.split('.')[0].to_i
44
+ client = if version >= 1
45
+ options = { # for em-http-request >= 1.0
46
+ :inactivity_timeout => 0 # connection inactivity (post-setup) timeout (0 = disable timeout)
47
+ }
48
+ EventMachine::HttpRequest.new(@endpoint, options)
49
+ else
50
+ EventMachine::HttpRequest.new(@endpoint)
51
+ end
52
+
53
+ client.post(params)
54
+ end
55
+
56
+ def handle_response(response, retry_block)
57
+ message = Yajl::Parser.parse(response) rescue nil
58
+ if message
59
+ receive(message)
60
+ trigger(:up)
61
+ else
62
+ retry_block.call
63
+ trigger(:down)
64
+ end
65
+ end
66
+
67
+ def store_cookies(cookies)
68
+ cookies.each do |cookie|
69
+ @cookies.set_cookie(@endpoint, cookie)
70
+ end
71
+ end
72
+ end
73
+
74
+ Transport.register 'long-polling', Transport::Http
75
+
76
+ end