faye 0.8.8 → 0.8.9

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 (67) hide show
  1. data/History.txt +16 -10
  2. data/README.rdoc +1 -1
  3. data/lib/faye-browser-min.js +1 -1
  4. data/lib/faye-browser-min.js.map +2 -2
  5. data/lib/faye-browser.js +302 -287
  6. data/lib/faye.rb +21 -21
  7. data/lib/faye/adapters/rack_adapter.rb +50 -48
  8. data/lib/faye/adapters/static_server.rb +22 -22
  9. data/lib/faye/engines/connection.rb +13 -13
  10. data/lib/faye/engines/memory.rb +21 -21
  11. data/lib/faye/engines/proxy.rb +23 -23
  12. data/lib/faye/error.rb +6 -6
  13. data/lib/faye/mixins/logging.rb +12 -12
  14. data/lib/faye/mixins/publisher.rb +6 -6
  15. data/lib/faye/mixins/timeouts.rb +1 -1
  16. data/lib/faye/protocol/channel.rb +24 -24
  17. data/lib/faye/protocol/client.rb +71 -73
  18. data/lib/faye/protocol/extensible.rb +7 -7
  19. data/lib/faye/protocol/grammar.rb +13 -13
  20. data/lib/faye/protocol/server.rb +57 -57
  21. data/lib/faye/protocol/socket.rb +4 -4
  22. data/lib/faye/protocol/subscription.rb +4 -4
  23. data/lib/faye/transport/http.rb +13 -13
  24. data/lib/faye/transport/local.rb +5 -5
  25. data/lib/faye/transport/transport.rb +25 -25
  26. data/lib/faye/transport/web_socket.rb +34 -30
  27. data/lib/faye/util/namespace.rb +4 -4
  28. data/spec/browser.html +5 -5
  29. data/spec/javascript/channel_spec.js +3 -3
  30. data/spec/javascript/client_spec.js +104 -98
  31. data/spec/javascript/engine/memory_spec.js +1 -1
  32. data/spec/javascript/engine_spec.js +70 -70
  33. data/spec/javascript/faye_spec.js +6 -6
  34. data/spec/javascript/grammar_spec.js +12 -12
  35. data/spec/javascript/node_adapter_spec.js +46 -46
  36. data/spec/javascript/publisher_spec.js +4 -4
  37. data/spec/javascript/server/connect_spec.js +21 -21
  38. data/spec/javascript/server/disconnect_spec.js +15 -15
  39. data/spec/javascript/server/extensions_spec.js +6 -6
  40. data/spec/javascript/server/handshake_spec.js +18 -18
  41. data/spec/javascript/server/integration_spec.js +23 -23
  42. data/spec/javascript/server/publish_spec.js +9 -9
  43. data/spec/javascript/server/subscribe_spec.js +30 -30
  44. data/spec/javascript/server/unsubscribe_spec.js +30 -30
  45. data/spec/javascript/server_spec.js +15 -15
  46. data/spec/javascript/transport_spec.js +32 -27
  47. data/spec/node.js +2 -2
  48. data/spec/ruby/channel_spec.rb +2 -2
  49. data/spec/ruby/client_spec.rb +100 -92
  50. data/spec/ruby/engine_examples.rb +51 -51
  51. data/spec/ruby/faye_spec.rb +5 -5
  52. data/spec/ruby/grammar_spec.rb +12 -12
  53. data/spec/ruby/publisher_spec.rb +4 -4
  54. data/spec/ruby/rack_adapter_spec.rb +34 -34
  55. data/spec/ruby/server/connect_spec.rb +22 -22
  56. data/spec/ruby/server/disconnect_spec.rb +16 -16
  57. data/spec/ruby/server/extensions_spec.rb +8 -8
  58. data/spec/ruby/server/handshake_spec.rb +20 -20
  59. data/spec/ruby/server/integration_spec.rb +22 -24
  60. data/spec/ruby/server/publish_spec.rb +9 -9
  61. data/spec/ruby/server/subscribe_spec.rb +31 -31
  62. data/spec/ruby/server/unsubscribe_spec.rb +31 -31
  63. data/spec/ruby/server_spec.rb +17 -17
  64. data/spec/ruby/transport_spec.rb +23 -23
  65. data/spec/testswarm +23 -10
  66. data/spec/thin_proxy.rb +5 -5
  67. metadata +90 -59
@@ -13,18 +13,18 @@ require 'uri'
13
13
  require 'yajl'
14
14
 
15
15
  module Faye
16
- VERSION = '0.8.8'
17
-
16
+ VERSION = '0.8.9'
17
+
18
18
  ROOT = File.expand_path(File.dirname(__FILE__))
19
-
19
+
20
20
  autoload :Publisher, File.join(ROOT, 'faye', 'mixins', 'publisher')
21
21
  autoload :Timeouts, File.join(ROOT, 'faye', 'mixins', 'timeouts')
22
22
  autoload :Logging, File.join(ROOT, 'faye', 'mixins', 'logging')
23
-
23
+
24
24
  autoload :Namespace, File.join(ROOT, 'faye', 'util', 'namespace')
25
-
25
+
26
26
  autoload :Engine, File.join(ROOT, 'faye', 'engines', 'proxy')
27
-
27
+
28
28
  autoload :Grammar, File.join(ROOT, 'faye', 'protocol', 'grammar')
29
29
  autoload :Extensible, File.join(ROOT, 'faye', 'protocol', 'extensible')
30
30
  autoload :Channel, File.join(ROOT, 'faye', 'protocol', 'channel')
@@ -32,37 +32,37 @@ module Faye
32
32
  autoload :Publication, File.join(ROOT, 'faye', 'protocol', 'publication')
33
33
  autoload :Client, File.join(ROOT, 'faye', 'protocol', 'client')
34
34
  autoload :Server, File.join(ROOT, 'faye', 'protocol', 'server')
35
-
35
+
36
36
  autoload :Transport, File.join(ROOT, 'faye', 'transport', 'transport')
37
37
  autoload :Error, File.join(ROOT, 'faye', 'error')
38
-
38
+
39
39
  autoload :RackAdapter, File.join(ROOT, 'faye', 'adapters', 'rack_adapter')
40
40
  autoload :StaticServer, File.join(ROOT, 'faye', 'adapters', 'static_server')
41
-
41
+
42
42
  BAYEUX_VERSION = '1.0'
43
43
  JSONP_CALLBACK = 'jsonpcallback'
44
44
  CONNECTION_TYPES = %w[long-polling cross-origin-long-polling callback-polling websocket eventsource in-process]
45
-
45
+
46
46
  MANDATORY_CONNECTION_TYPES = %w[long-polling callback-polling in-process]
47
-
47
+
48
48
  class << self
49
49
  attr_accessor :logger
50
50
  end
51
51
  self.logger = method(:puts)
52
-
52
+
53
53
  def self.ensure_reactor_running!
54
54
  Engine.ensure_reactor_running!
55
55
  end
56
-
56
+
57
57
  def self.random(*args)
58
58
  Engine.random(*args)
59
59
  end
60
-
60
+
61
61
  def self.client_id_from_messages(messages)
62
62
  first = [messages].flatten.first
63
63
  first && first['clientId']
64
64
  end
65
-
65
+
66
66
  def self.copy_object(object)
67
67
  case object
68
68
  when Hash
@@ -77,7 +77,7 @@ module Faye
77
77
  object
78
78
  end
79
79
  end
80
-
80
+
81
81
  def self.to_json(value)
82
82
  case value
83
83
  when Hash, Array then Yajl::Encoder.encode(value)
@@ -85,15 +85,15 @@ module Faye
85
85
  else value.to_s
86
86
  end
87
87
  end
88
-
88
+
89
89
  def self.async_each(list, iterator, callback)
90
90
  n = list.size
91
91
  i = -1
92
92
  calls = 0
93
93
  looping = false
94
-
94
+
95
95
  loop, resume = nil, nil
96
-
96
+
97
97
  iterate = lambda do
98
98
  calls -= 1
99
99
  i += 1
@@ -103,7 +103,7 @@ module Faye
103
103
  iterator.call(list[i], resume)
104
104
  end
105
105
  end
106
-
106
+
107
107
  loop = lambda do
108
108
  unless looping
109
109
  looping = true
@@ -111,7 +111,7 @@ module Faye
111
111
  looping = false
112
112
  end
113
113
  end
114
-
114
+
115
115
  resume = lambda do
116
116
  calls += 1
117
117
  loop.call
@@ -1,53 +1,53 @@
1
1
  module Faye
2
2
  class RackAdapter
3
-
3
+
4
4
  include Logging
5
5
 
6
6
  extend Forwardable
7
7
  def_delegators "@server.engine", :bind, :unbind
8
8
 
9
9
  ASYNC_RESPONSE = [-1, {}, []].freeze
10
-
10
+
11
11
  DEFAULT_ENDPOINT = '/bayeux'
12
12
  SCRIPT_PATH = 'faye-browser-min.js'
13
-
13
+
14
14
  TYPE_JSON = {'Content-Type' => 'application/json; charset=utf-8'}
15
15
  TYPE_SCRIPT = {'Content-Type' => 'text/javascript; charset=utf-8'}
16
16
  TYPE_TEXT = {'Content-Type' => 'text/plain; charset=utf-8'}
17
-
17
+
18
18
  # This header is passed by Rack::Proxy during testing. Rack::Proxy seems to
19
19
  # set content-length for you, and setting it in here really slows the tests
20
20
  # down. Better suggestions welcome.
21
21
  HTTP_X_NO_CONTENT_LENGTH = 'HTTP_X_NO_CONTENT_LENGTH'
22
-
22
+
23
23
  def initialize(app = nil, options = nil)
24
24
  @app = app if app.respond_to?(:call)
25
25
  @options = [app, options].grep(Hash).first || {}
26
-
26
+
27
27
  @endpoint = @options[:mount] || DEFAULT_ENDPOINT
28
- @endpoint_re = Regexp.new('^' + @endpoint + '(/[^/]+)*(\\.[^\\.]+)?$')
28
+ @endpoint_re = Regexp.new('^' + @endpoint.gsub(/\/$/, '') + '(/[^/]+)*(\\.[^\\.]+)?$')
29
29
  @server = Server.new(@options)
30
-
30
+
31
31
  @static = StaticServer.new(ROOT, /\.(?:js|map)$/)
32
32
  @static.map(File.basename(@endpoint) + '.js', SCRIPT_PATH)
33
33
  @static.map('client.js', SCRIPT_PATH)
34
-
34
+
35
35
  return unless extensions = @options[:extensions]
36
36
  [*extensions].each { |extension| add_extension(extension) }
37
37
  end
38
-
38
+
39
39
  def add_extension(extension)
40
40
  @server.add_extension(extension)
41
41
  end
42
-
42
+
43
43
  def remove_extension(extension)
44
44
  @server.remove_extension(extension)
45
45
  end
46
-
46
+
47
47
  def get_client
48
48
  @client ||= Client.new(@server)
49
49
  end
50
-
50
+
51
51
  def listen(port, ssl_options = nil)
52
52
  Faye::WebSocket.load_adapter('thin')
53
53
  handler = Rack::Handler.get('thin')
@@ -62,56 +62,56 @@ module Faye
62
62
  @thin_server = s
63
63
  end
64
64
  end
65
-
65
+
66
66
  def stop
67
67
  return unless @thin_server
68
68
  @thin_server.stop
69
69
  @thin_server = nil
70
70
  end
71
-
71
+
72
72
  def call(env)
73
73
  Faye.ensure_reactor_running!
74
74
  request = Rack::Request.new(env)
75
-
75
+
76
76
  unless request.path_info =~ @endpoint_re
77
77
  env['faye.client'] = get_client
78
78
  return @app ? @app.call(env) :
79
79
  [404, TYPE_TEXT, ["Sure you're not looking for #{@endpoint} ?"]]
80
80
  end
81
-
81
+
82
82
  # http://groups.google.com/group/faye-users/browse_thread/thread/4a01bb7d25d3636a
83
83
  if env['REQUEST_METHOD'] == 'OPTIONS' or env['HTTP_ACCESS_CONTROL_REQUEST_METHOD'] == 'POST'
84
84
  return handle_options(request)
85
85
  end
86
-
86
+
87
87
  return @static.call(env) if @static =~ request.path_info
88
88
  return handle_websocket(env) if Faye::WebSocket.websocket?(env)
89
89
  return handle_eventsource(env) if Faye::EventSource.eventsource?(env)
90
-
90
+
91
91
  handle_request(request)
92
92
  end
93
-
93
+
94
94
  private
95
-
95
+
96
96
  def handle_request(request)
97
97
  unless json_msg = message_from_request(request)
98
98
  error 'Received request with no message: ?', format_request(request)
99
99
  return [400, TYPE_TEXT, ['Bad request']]
100
100
  end
101
-
101
+
102
102
  debug "Received message via HTTP #{request.request_method}: ?", json_msg
103
-
103
+
104
104
  message = Yajl::Parser.parse(json_msg)
105
105
  jsonp = request.params['jsonp'] || JSONP_CALLBACK
106
106
  headers = request.get? ? TYPE_SCRIPT.dup : TYPE_JSON.dup
107
107
  origin = request.env['HTTP_ORIGIN']
108
108
  callback = request.env['async.callback']
109
-
109
+
110
110
  @server.flush_connection(message) if request.get?
111
-
111
+
112
112
  headers['Access-Control-Allow-Origin'] = origin if origin
113
113
  headers['Cache-Control'] = 'no-cache, no-store'
114
-
114
+
115
115
  @server.process(message, false) do |replies|
116
116
  response = Faye.to_json(replies)
117
117
  response = "#{ jsonp }(#{ response });" if request.get?
@@ -120,26 +120,28 @@ module Faye
120
120
  debug 'HTTP response: ?', response
121
121
  callback.call [200, headers, [response]]
122
122
  end
123
-
123
+
124
124
  ASYNC_RESPONSE
125
125
  rescue => e
126
126
  error "#{e.message}\nBacktrace:\n#{e.backtrace * "\n"}"
127
127
  [400, TYPE_TEXT, ['Bad request']]
128
128
  end
129
-
129
+
130
130
  def handle_websocket(env)
131
131
  ws = Faye::WebSocket.new(env, nil, :ping => @options[:ping])
132
132
  client_id = nil
133
-
133
+
134
134
  ws.onmessage = lambda do |event|
135
135
  begin
136
136
  debug "Received message via WebSocket[#{ws.version}]: ?", event.data
137
-
138
- message = Yajl::Parser.parse(event.data)
139
- client_id = Faye.client_id_from_messages(message)
140
-
141
- @server.open_socket(client_id, ws)
142
-
137
+
138
+ message = Yajl::Parser.parse(event.data)
139
+ cid = Faye.client_id_from_messages(message)
140
+
141
+ @server.close_socket(client_id) if client_id and cid != client_id
142
+ @server.open_socket(cid, ws)
143
+ client_id = cid
144
+
143
145
  @server.process(message, false) do |replies|
144
146
  ws.send(Faye.to_json(replies)) if ws
145
147
  end
@@ -147,57 +149,57 @@ module Faye
147
149
  error "#{e.message}\nBacktrace:\n#{e.backtrace * "\n"}"
148
150
  end
149
151
  end
150
-
152
+
151
153
  ws.onclose = lambda do |event|
152
154
  @server.close_socket(client_id)
153
155
  ws = nil
154
156
  end
155
-
157
+
156
158
  ws.rack_response
157
159
  end
158
-
160
+
159
161
  def handle_eventsource(env)
160
162
  es = Faye::EventSource.new(env, :ping => @options[:ping])
161
163
  client_id = es.url.split('/').pop
162
-
164
+
163
165
  debug 'Opened EventSource connection for ?', client_id
164
166
  @server.open_socket(client_id, es)
165
-
167
+
166
168
  es.onclose = lambda do |event|
167
169
  @server.close_socket(client_id)
168
170
  es = nil
169
171
  end
170
-
172
+
171
173
  es.rack_response
172
174
  end
173
-
175
+
174
176
  def message_from_request(request)
175
177
  message = request.params['message']
176
178
  return message if message
177
-
179
+
178
180
  # Some clients do not send a content-type, e.g.
179
181
  # Internet Explorer when using cross-origin-long-polling
180
182
  # Some use application/xml when using CORS
181
183
  content_type = request.env['CONTENT_TYPE'] || ''
182
-
184
+
183
185
  if content_type.split(';').first == 'application/json'
184
186
  request.body.read
185
187
  else
186
188
  CGI.parse(request.body.read)['message'][0]
187
189
  end
188
190
  end
189
-
191
+
190
192
  def format_request(request)
191
193
  request.body.rewind
192
194
  string = "curl -X #{request.request_method.upcase}"
193
195
  string << " '#{request.url}'"
194
196
  if request.post?
195
- string << " -H 'Content-Type: #{request.env['CONTENT_TYPE']}'"
197
+ string << " -H 'Content-Type: #{request.env['CONTENT_TYPE']}'"
196
198
  string << " -d '#{request.body.read}'"
197
199
  end
198
200
  string
199
201
  end
200
-
202
+
201
203
  def handle_options(request)
202
204
  headers = {
203
205
  'Access-Control-Allow-Origin' => '*',
@@ -208,7 +210,7 @@ module Faye
208
210
  }
209
211
  [200, headers, ['']]
210
212
  end
211
-
213
+
212
214
  end
213
215
  end
214
216
 
@@ -1,29 +1,28 @@
1
1
  module Faye
2
2
  class StaticServer
3
-
3
+
4
4
  def initialize(directory, path_regex)
5
5
  @directory = directory
6
6
  @path_regex = path_regex
7
7
  @path_map = {}
8
8
  @index = {}
9
9
  end
10
-
10
+
11
11
  def map(request_path, filename)
12
12
  @path_map[request_path] = filename
13
13
  end
14
-
14
+
15
15
  def =~(pathname)
16
16
  @path_regex =~ pathname
17
17
  end
18
-
18
+
19
19
  def call(env)
20
- pathname = env['PATH_INFO']
21
- pathname = File.basename(pathname)
22
- pathname = @path_map[pathname] || pathname
23
-
24
- cache = @index[pathname] ||= {}
25
- fullpath = File.join(@directory, pathname)
26
-
20
+ filename = File.basename(env['PATH_INFO'])
21
+ filename = @path_map[filename] || filename
22
+
23
+ cache = @index[filename] ||= {}
24
+ fullpath = File.join(@directory, filename)
25
+
27
26
  begin
28
27
  cache[:content] ||= File.read(fullpath)
29
28
  cache[:digest] ||= Digest::SHA1.hexdigest(cache[:content])
@@ -31,26 +30,27 @@ module Faye
31
30
  rescue
32
31
  return [404, {}, []]
33
32
  end
34
-
35
- type = /\.js$/ =~ fullpath ? RackAdapter::TYPE_SCRIPT : RackAdapter::TYPE_JSON
36
- headers = type.dup
37
- ims = env['HTTP_IF_MODIFIED_SINCE']
38
-
33
+
34
+ type = /\.js$/ =~ fullpath ? RackAdapter::TYPE_SCRIPT : RackAdapter::TYPE_JSON
35
+ ims = env['HTTP_IF_MODIFIED_SINCE']
36
+
39
37
  no_content_length = env[RackAdapter::HTTP_X_NO_CONTENT_LENGTH]
40
-
41
- headers['Content-Length'] = '0' unless no_content_length
42
- headers['ETag'] = cache[:digest]
43
- headers['Last-Modified'] = cache[:mtime].httpdate
44
-
38
+
39
+ headers = {
40
+ 'ETag' => cache[:digest],
41
+ 'Last-Modified' => cache[:mtime].httpdate
42
+ }
43
+
45
44
  if env['HTTP_IF_NONE_MATCH'] == cache[:digest]
46
45
  [304, headers, ['']]
47
46
  elsif ims and cache[:mtime] <= Time.httpdate(ims)
48
47
  [304, headers, ['']]
49
48
  else
50
49
  headers['Content-Length'] = cache[:content].bytesize.to_s unless no_content_length
50
+ headers.update(type)
51
51
  [200, headers, [cache[:content]]]
52
52
  end
53
53
  end
54
-
54
+
55
55
  end
56
56
  end
@@ -1,60 +1,60 @@
1
1
  module Faye
2
2
  module Engine
3
-
3
+
4
4
  class Connection
5
5
  include EventMachine::Deferrable
6
6
  include Timeouts
7
-
7
+
8
8
  attr_accessor :socket
9
-
9
+
10
10
  def initialize(engine, id, options = {})
11
11
  @engine = engine
12
12
  @id = id
13
13
  @options = options
14
14
  @inbox = Set.new
15
15
  end
16
-
16
+
17
17
  def deliver(message)
18
18
  return socket.send(message) if socket
19
19
  return unless @inbox.add?(message)
20
20
  begin_delivery_timeout
21
21
  end
22
-
22
+
23
23
  def connect(options, &block)
24
24
  options = options || {}
25
25
  timeout = options['timeout'] ? options['timeout'] / 1000.0 : @engine.timeout
26
-
26
+
27
27
  set_deferred_status(:deferred)
28
28
  callback(&block)
29
-
29
+
30
30
  begin_delivery_timeout
31
31
  begin_connection_timeout(timeout)
32
32
  end
33
-
33
+
34
34
  def flush!(force = false)
35
35
  release_connection!(force)
36
36
  set_deferred_status(:succeeded, @inbox.entries)
37
37
  @inbox = []
38
38
  end
39
-
39
+
40
40
  private
41
-
41
+
42
42
  def release_connection!(force = false)
43
43
  @engine.close_connection(@id) if force or socket.nil?
44
44
  remove_timeout(:connection)
45
45
  remove_timeout(:delivery)
46
46
  end
47
-
47
+
48
48
  def begin_delivery_timeout
49
49
  return if @inbox.empty?
50
50
  add_timeout(:delivery, MAX_DELAY) { flush! }
51
51
  end
52
-
52
+
53
53
  def begin_connection_timeout(timeout)
54
54
  add_timeout(:connection, timeout) { flush! }
55
55
  end
56
56
  end
57
-
57
+
58
58
  end
59
59
  end
60
60