faye 0.8.11 → 1.0.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 (73) hide show
  1. data/{History.txt → CHANGELOG.md} +126 -105
  2. data/README.md +36 -0
  3. data/lib/faye-browser-min.js +2 -1
  4. data/lib/faye-browser-min.js.map +1 -8
  5. data/lib/faye-browser.js +923 -607
  6. data/lib/faye.rb +11 -5
  7. data/lib/faye/adapters/rack_adapter.rb +80 -85
  8. data/lib/faye/engines/connection.rb +7 -9
  9. data/lib/faye/engines/memory.rb +1 -0
  10. data/lib/faye/engines/proxy.rb +7 -6
  11. data/lib/faye/mixins/deferrable.rb +15 -0
  12. data/lib/faye/mixins/logging.rb +11 -22
  13. data/lib/faye/mixins/publisher.rb +9 -20
  14. data/lib/faye/protocol/channel.rb +2 -1
  15. data/lib/faye/protocol/client.rb +70 -48
  16. data/lib/faye/protocol/envelope.rb +24 -0
  17. data/lib/faye/protocol/extensible.rb +7 -4
  18. data/lib/faye/protocol/publication.rb +1 -1
  19. data/lib/faye/protocol/server.rb +8 -11
  20. data/lib/faye/protocol/socket.rb +6 -4
  21. data/lib/faye/protocol/subscription.rb +1 -1
  22. data/lib/faye/transport/http.rb +20 -27
  23. data/lib/faye/transport/local.rb +5 -5
  24. data/lib/faye/transport/transport.rb +42 -12
  25. data/lib/faye/transport/web_socket.rb +71 -38
  26. metadata +169 -137
  27. checksums.yaml +0 -7
  28. data/README.rdoc +0 -83
  29. data/spec/browser.html +0 -45
  30. data/spec/encoding_helper.rb +0 -7
  31. data/spec/install.sh +0 -78
  32. data/spec/javascript/channel_spec.js +0 -15
  33. data/spec/javascript/client_spec.js +0 -729
  34. data/spec/javascript/dispatcher_spec.js +0 -122
  35. data/spec/javascript/engine/memory_spec.js +0 -7
  36. data/spec/javascript/engine_spec.js +0 -417
  37. data/spec/javascript/faye_spec.js +0 -34
  38. data/spec/javascript/grammar_spec.js +0 -66
  39. data/spec/javascript/node_adapter_spec.js +0 -314
  40. data/spec/javascript/publisher_spec.js +0 -27
  41. data/spec/javascript/server/connect_spec.js +0 -168
  42. data/spec/javascript/server/disconnect_spec.js +0 -121
  43. data/spec/javascript/server/extensions_spec.js +0 -60
  44. data/spec/javascript/server/handshake_spec.js +0 -145
  45. data/spec/javascript/server/integration_spec.js +0 -131
  46. data/spec/javascript/server/publish_spec.js +0 -85
  47. data/spec/javascript/server/subscribe_spec.js +0 -247
  48. data/spec/javascript/server/unsubscribe_spec.js +0 -245
  49. data/spec/javascript/server_spec.js +0 -121
  50. data/spec/javascript/transport_spec.js +0 -135
  51. data/spec/node.js +0 -55
  52. data/spec/phantom.js +0 -17
  53. data/spec/ruby/channel_spec.rb +0 -17
  54. data/spec/ruby/client_spec.rb +0 -741
  55. data/spec/ruby/engine/memory_spec.rb +0 -7
  56. data/spec/ruby/engine_examples.rb +0 -427
  57. data/spec/ruby/faye_spec.rb +0 -30
  58. data/spec/ruby/grammar_spec.rb +0 -68
  59. data/spec/ruby/publisher_spec.rb +0 -27
  60. data/spec/ruby/rack_adapter_spec.rb +0 -241
  61. data/spec/ruby/server/connect_spec.rb +0 -170
  62. data/spec/ruby/server/disconnect_spec.rb +0 -120
  63. data/spec/ruby/server/extensions_spec.rb +0 -68
  64. data/spec/ruby/server/handshake_spec.rb +0 -143
  65. data/spec/ruby/server/integration_spec.rb +0 -133
  66. data/spec/ruby/server/publish_spec.rb +0 -81
  67. data/spec/ruby/server/subscribe_spec.rb +0 -247
  68. data/spec/ruby/server/unsubscribe_spec.rb +0 -247
  69. data/spec/ruby/server_spec.rb +0 -121
  70. data/spec/ruby/transport_spec.rb +0 -136
  71. data/spec/spec_helper.rb +0 -11
  72. data/spec/testswarm +0 -42
  73. data/spec/thin_proxy.rb +0 -37
@@ -6,18 +6,20 @@ require 'em-http/version'
6
6
  require 'eventmachine'
7
7
  require 'faye/websocket'
8
8
  require 'forwardable'
9
+ require 'multi_json'
9
10
  require 'rack'
11
+ require 'securerandom'
10
12
  require 'set'
11
13
  require 'time'
12
14
  require 'uri'
13
- require 'yajl'
14
15
 
15
16
  module Faye
16
- VERSION = '0.8.11'
17
+ VERSION = '1.0.0'
17
18
 
18
19
  ROOT = File.expand_path(File.dirname(__FILE__))
19
20
 
20
21
  autoload :Publisher, File.join(ROOT, 'faye', 'mixins', 'publisher')
22
+ autoload :Deferrable, File.join(ROOT, 'faye', 'mixins', 'deferrable')
21
23
  autoload :Timeouts, File.join(ROOT, 'faye', 'mixins', 'timeouts')
22
24
  autoload :Logging, File.join(ROOT, 'faye', 'mixins', 'logging')
23
25
 
@@ -28,6 +30,7 @@ module Faye
28
30
  autoload :Grammar, File.join(ROOT, 'faye', 'protocol', 'grammar')
29
31
  autoload :Extensible, File.join(ROOT, 'faye', 'protocol', 'extensible')
30
32
  autoload :Channel, File.join(ROOT, 'faye', 'protocol', 'channel')
33
+ autoload :Envelope, File.join(ROOT, 'faye', 'protocol', 'envelope')
31
34
  autoload :Subscription, File.join(ROOT, 'faye', 'protocol', 'subscription')
32
35
  autoload :Publication, File.join(ROOT, 'faye', 'protocol', 'publication')
33
36
  autoload :Client, File.join(ROOT, 'faye', 'protocol', 'client')
@@ -48,7 +51,6 @@ module Faye
48
51
  class << self
49
52
  attr_accessor :logger
50
53
  end
51
- self.logger = method(:puts)
52
54
 
53
55
  def self.ensure_reactor_running!
54
56
  Engine.ensure_reactor_running!
@@ -59,7 +61,7 @@ module Faye
59
61
  end
60
62
 
61
63
  def self.client_id_from_messages(messages)
62
- first = [messages].flatten.first
64
+ first = [messages].flatten.find { |m| m['channel'] == '/meta/connect' }
63
65
  first && first['clientId']
64
66
  end
65
67
 
@@ -78,9 +80,13 @@ module Faye
78
80
  end
79
81
  end
80
82
 
83
+ def self.parse_url(url)
84
+ String === url ? URI.parse(url) : url
85
+ end
86
+
81
87
  def self.to_json(value)
82
88
  case value
83
- when Hash, Array then Yajl::Encoder.encode(value)
89
+ when Hash, Array then MultiJson.dump(value)
84
90
  when String, NilClass then value.inspect
85
91
  else value.to_s
86
92
  end
@@ -4,7 +4,7 @@ module Faye
4
4
  include Logging
5
5
 
6
6
  extend Forwardable
7
- def_delegators "@server.engine", :bind, :unbind
7
+ def_delegators '@server.engine', *Faye::Publisher.instance_methods
8
8
 
9
9
  ASYNC_RESPONSE = [-1, {}, []].freeze
10
10
 
@@ -15,19 +15,17 @@ module Faye
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
- VALID_JSONP_CALLBACK = /^[a-z_\$][a-z0-9_\$]*(\.[a-z_\$][a-z0-9_\$]*)*$/i
19
-
20
18
  # This header is passed by Rack::Proxy during testing. Rack::Proxy seems to
21
19
  # set content-length for you, and setting it in here really slows the tests
22
20
  # down. Better suggestions welcome.
23
21
  HTTP_X_NO_CONTENT_LENGTH = 'HTTP_X_NO_CONTENT_LENGTH'
24
22
 
25
23
  def initialize(app = nil, options = nil)
26
- @app = app if app.respond_to?(:call)
27
- @options = [app, options].grep(Hash).first || {}
24
+ @app = app if app.respond_to?(:call)
25
+ @options = [app, options].grep(Hash).first || {}
28
26
 
29
27
  @endpoint = @options[:mount] || DEFAULT_ENDPOINT
30
- @endpoint_re = Regexp.new('^' + @endpoint.gsub(/\/$/, '') + '(/[^/]+)*(\\.[^\\.]+)?$')
28
+ @endpoint_re = Regexp.new('^' + @endpoint.gsub(/\/$/, '') + '(/[^/]*)*(\\.[^\\.]+)?$')
31
29
  @server = Server.new(@options)
32
30
 
33
31
  @static = StaticServer.new(ROOT, /\.(?:js|map)$/)
@@ -38,6 +36,10 @@ module Faye
38
36
  [*extensions].each { |extension| add_extension(extension) }
39
37
  end
40
38
 
39
+ def listen(*args)
40
+ raise 'The listen() method is deprecated - see https://github.com/faye/faye-websocket-ruby#running-your-socket-application for information on running your Faye server'
41
+ end
42
+
41
43
  def add_extension(extension)
42
44
  @server.add_extension(extension)
43
45
  end
@@ -50,27 +52,6 @@ module Faye
50
52
  @client ||= Client.new(@server)
51
53
  end
52
54
 
53
- def listen(port, ssl_options = nil)
54
- Faye::WebSocket.load_adapter('thin')
55
- handler = Rack::Handler.get('thin')
56
- handler.run(self, :Port => port) do |s|
57
- if ssl_options
58
- s.ssl = true
59
- s.ssl_options = {
60
- :private_key_file => ssl_options[:key],
61
- :cert_chain_file => ssl_options[:cert]
62
- }
63
- end
64
- @thin_server = s
65
- end
66
- end
67
-
68
- def stop
69
- return unless @thin_server
70
- @thin_server.stop
71
- @thin_server = nil
72
- end
73
-
74
55
  def call(env)
75
56
  Faye.ensure_reactor_running!
76
57
  request = Rack::Request.new(env)
@@ -81,14 +62,15 @@ module Faye
81
62
  [404, TYPE_TEXT, ["Sure you're not looking for #{@endpoint} ?"]]
82
63
  end
83
64
 
65
+ return @static.call(env) if @static =~ request.path_info
66
+
84
67
  # http://groups.google.com/group/faye-users/browse_thread/thread/4a01bb7d25d3636a
85
68
  if env['REQUEST_METHOD'] == 'OPTIONS' or env['HTTP_ACCESS_CONTROL_REQUEST_METHOD'] == 'POST'
86
- return handle_options(request)
69
+ return handle_options
87
70
  end
88
71
 
89
- return @static.call(env) if @static =~ request.path_info
90
- return handle_websocket(env) if Faye::WebSocket.websocket?(env)
91
- return handle_eventsource(env) if Faye::EventSource.eventsource?(env)
72
+ return handle_websocket(request) if Faye::WebSocket.websocket?(env)
73
+ return handle_eventsource(request) if Faye::EventSource.eventsource?(env)
92
74
 
93
75
  handle_request(request)
94
76
  end
@@ -103,35 +85,27 @@ module Faye
103
85
 
104
86
  debug "Received message via HTTP #{request.request_method}: ?", json_msg
105
87
 
106
- message = Yajl::Parser.parse(json_msg)
88
+ message = MultiJson.load(json_msg)
89
+ request.env['rack.hijack'].call if request.env['rack.hijack']
90
+
107
91
  jsonp = request.params['jsonp'] || JSONP_CALLBACK
108
92
  headers = request.get? ? TYPE_SCRIPT.dup : TYPE_JSON.dup
109
93
  origin = request.env['HTTP_ORIGIN']
110
94
  callback = request.env['async.callback']
111
-
112
- if jsonp !~ VALID_JSONP_CALLBACK
113
- error 'Invalid JSON-P callback: ?', jsonp
114
- return [400, TYPE_TEXT, ['Bad request']]
115
- end
116
-
117
- @server.flush_connection(message) if request.get?
95
+ hijack = request.env['rack.hijack_io']
118
96
 
119
97
  headers['Access-Control-Allow-Origin'] = origin if origin
120
98
  headers['Cache-Control'] = 'no-cache, no-store'
121
- headers['X-Content-Type-Options'] = 'nosniff'
122
-
123
- @server.process(message, false) do |replies|
124
- response = Faye.to_json(replies)
125
99
 
126
- if request.get?
127
- response = "/**/#{ jsonp }(#{ response });"
128
- headers['Content-Disposition'] = 'attachment; filename=f.txt'
100
+ EventMachine.next_tick do
101
+ @server.process(message, request) do |replies|
102
+ response = Faye.to_json(replies)
103
+ response = "#{ jsonp }(#{ jsonp_escape(response) });" if request.get?
104
+ headers['Content-Length'] = response.bytesize.to_s unless request.env[HTTP_X_NO_CONTENT_LENGTH]
105
+ headers['Connection'] = 'close'
106
+ debug 'HTTP response: ?', response
107
+ send_response([200, headers, [response]], hijack, callback)
129
108
  end
130
-
131
- headers['Content-Length'] = response.bytesize.to_s unless request.env[HTTP_X_NO_CONTENT_LENGTH]
132
- headers['Connection'] = 'close'
133
- debug 'HTTP response: ?', response
134
- callback.call [200, headers, [response]]
135
109
  end
136
110
 
137
111
  ASYNC_RESPONSE
@@ -140,22 +114,59 @@ module Faye
140
114
  [400, TYPE_TEXT, ['Bad request']]
141
115
  end
142
116
 
143
- def handle_websocket(env)
144
- ws = Faye::WebSocket.new(env, nil, :ping => @options[:ping])
117
+ def message_from_request(request)
118
+ message = request.params['message']
119
+ return message if message
120
+
121
+ # Some clients do not send a content-type, e.g.
122
+ # Internet Explorer when using cross-origin-long-polling
123
+ # Some use application/xml when using CORS
124
+ content_type = request.env['CONTENT_TYPE'] || ''
125
+
126
+ if content_type.split(';').first == 'application/json'
127
+ request.body.read
128
+ else
129
+ CGI.parse(request.body.read)['message'][0]
130
+ end
131
+ end
132
+
133
+ def jsonp_escape(json)
134
+ json.gsub(/\u2028/, '\u2028').gsub(/\u2029/, '\u2029')
135
+ end
136
+
137
+ def send_response(response, hijack, callback)
138
+ return callback.call(response) if callback
139
+
140
+ buffer = "HTTP/1.1 #{response[0]} OK\r\n"
141
+ response[1].each do |name, value|
142
+ buffer << "#{name}: #{value}\r\n"
143
+ end
144
+ buffer << "\r\n"
145
+ response[2].each do |chunk|
146
+ buffer << chunk
147
+ end
148
+
149
+ hijack.write(buffer)
150
+ hijack.flush
151
+ hijack.close_write
152
+ end
153
+
154
+ def handle_websocket(request)
155
+ ws = Faye::WebSocket.new(request.env, nil, :ping => @options[:ping])
145
156
  client_id = nil
146
157
 
147
158
  ws.onmessage = lambda do |event|
148
159
  begin
149
160
  debug "Received message via WebSocket[#{ws.version}]: ?", event.data
150
161
 
151
- message = Yajl::Parser.parse(event.data)
162
+ message = MultiJson.load(event.data)
152
163
  cid = Faye.client_id_from_messages(message)
153
164
 
154
- @server.close_socket(client_id) if client_id and cid != client_id
155
- @server.open_socket(cid, ws)
165
+ @server.close_socket(client_id) if client_id and cid and cid != client_id
166
+ @server.open_socket(cid, ws, request)
156
167
  client_id = cid
157
168
 
158
- @server.process(message, false) do |replies|
169
+ @server.process(message, request) do |replies|
159
170
  ws.send(Faye.to_json(replies)) if ws
160
171
  end
161
172
  rescue => e
@@ -171,12 +182,12 @@ module Faye
171
182
  ws.rack_response
172
183
  end
173
184
 
174
- def handle_eventsource(env)
175
- es = Faye::EventSource.new(env, :ping => @options[:ping])
185
+ def handle_eventsource(request)
186
+ es = Faye::EventSource.new(request.env, :ping => @options[:ping])
176
187
  client_id = es.url.split('/').pop
177
188
 
178
189
  debug 'Opened EventSource connection for ?', client_id
179
- @server.open_socket(client_id, es)
190
+ @server.open_socket(client_id, es, request)
180
191
 
181
192
  es.onclose = lambda do |event|
182
193
  @server.close_socket(client_id)
@@ -186,20 +197,15 @@ module Faye
186
197
  es.rack_response
187
198
  end
188
199
 
189
- def message_from_request(request)
190
- message = request.params['message']
191
- return message if message
192
-
193
- # Some clients do not send a content-type, e.g.
194
- # Internet Explorer when using cross-origin-long-polling
195
- # Some use application/xml when using CORS
196
- content_type = request.env['CONTENT_TYPE'] || ''
197
-
198
- if content_type.split(';').first == 'application/json'
199
- request.body.read
200
- else
201
- CGI.parse(request.body.read)['message'][0]
202
- end
200
+ def handle_options
201
+ headers = {
202
+ 'Access-Control-Allow-Credentials' => 'false',
203
+ 'Access-Control-Allow-Headers' => 'Accept, Content-Type, Pragma, X-Requested-With',
204
+ 'Access-Control-Allow-Methods' => 'POST, GET, PUT, DELETE, OPTIONS',
205
+ 'Access-Control-Allow-Origin' => '*',
206
+ 'Access-Control-Max-Age' => '86400'
207
+ }
208
+ [200, headers, []]
203
209
  end
204
210
 
205
211
  def format_request(request)
@@ -213,17 +219,6 @@ module Faye
213
219
  string
214
220
  end
215
221
 
216
- def handle_options(request)
217
- headers = {
218
- 'Access-Control-Allow-Origin' => '*',
219
- 'Access-Control-Allow-Credentials' => 'false',
220
- 'Access-Control-Max-Age' => '86400',
221
- 'Access-Control-Allow-Methods' => 'POST, GET, PUT, DELETE, OPTIONS',
222
- 'Access-Control-Allow-Headers' => 'Accept, Content-Type, Pragma, X-Requested-With'
223
- }
224
- [200, headers, ['']]
225
- end
226
-
227
222
  end
228
223
  end
229
224
 
@@ -2,7 +2,7 @@ module Faye
2
2
  module Engine
3
3
 
4
4
  class Connection
5
- include EventMachine::Deferrable
5
+ include Deferrable
6
6
  include Timeouts
7
7
 
8
8
  attr_accessor :socket
@@ -24,7 +24,7 @@ module Faye
24
24
  options = options || {}
25
25
  timeout = options['timeout'] ? options['timeout'] / 1000.0 : @engine.timeout
26
26
 
27
- set_deferred_status(:deferred)
27
+ set_deferred_status(:unknown)
28
28
  callback(&block)
29
29
 
30
30
  begin_delivery_timeout
@@ -32,19 +32,17 @@ module Faye
32
32
  end
33
33
 
34
34
  def flush!(force = false)
35
- release_connection!(force)
35
+ @engine.close_connection(@id) if force or socket.nil?
36
+
37
+ remove_timeout(:connection)
38
+ remove_timeout(:delivery)
39
+
36
40
  set_deferred_status(:succeeded, @inbox.entries)
37
41
  @inbox = []
38
42
  end
39
43
 
40
44
  private
41
45
 
42
- def release_connection!(force = false)
43
- @engine.close_connection(@id) if force or socket.nil?
44
- remove_timeout(:connection)
45
- remove_timeout(:delivery)
46
- end
47
-
48
46
  def begin_delivery_timeout
49
47
  return if @inbox.empty?
50
48
  add_timeout(:delivery, MAX_DELAY) { flush! }
@@ -37,6 +37,7 @@ module Faye
37
37
  @messages.delete(client_id)
38
38
  @server.debug 'Destroyed client ?', client_id
39
39
  @server.trigger(:disconnect, client_id)
40
+ @server.trigger(:close, client_id)
40
41
  callback.call if callback
41
42
  end
42
43
 
@@ -20,10 +20,10 @@ module Faye
20
20
  end
21
21
 
22
22
  def self.random(bitlength = ID_LENGTH)
23
- limit = 2 ** bitlength - 1
24
- max_size = limit.to_s(36).size
25
- string = rand(limit).to_s(36)
26
- string = '0' + string while string.size < max_size
23
+ limit = 2 ** bitlength
24
+ max_size = (limit - 1).to_s(36).size
25
+ string = SecureRandom.random_number(limit).to_s(36)
26
+ string = '0' + string while string.size < max_size
27
27
  string
28
28
  end
29
29
 
@@ -37,6 +37,8 @@ module Faye
37
37
  def_delegators :@engine, *METHODS
38
38
 
39
39
  def initialize(options)
40
+ super()
41
+
40
42
  @options = options
41
43
  @connections = {}
42
44
  @interval = @options[:interval] || INTERVAL
@@ -45,7 +47,7 @@ module Faye
45
47
  engine_class = @options[:type] || Memory
46
48
  @engine = engine_class.create(self, @options)
47
49
 
48
- bind :disconnect do |client_id|
50
+ bind :close do |client_id|
49
51
  EventMachine.next_tick { close_connection(client_id) }
50
52
  end
51
53
 
@@ -82,7 +84,6 @@ module Faye
82
84
  end
83
85
 
84
86
  def open_socket(client_id, socket)
85
- return unless client_id
86
87
  conn = connection(client_id, true)
87
88
  conn.socket = socket
88
89
  end
@@ -0,0 +1,15 @@
1
+ module Faye
2
+ module Deferrable
3
+
4
+ include EventMachine::Deferrable
5
+
6
+ def set_deferred_status(status, *args)
7
+ if status == :unknown
8
+ @deferred_status = @deferred_args = @callbacks = @errbacks = nil
9
+ end
10
+ super
11
+ end
12
+
13
+ end
14
+ end
15
+
@@ -1,45 +1,34 @@
1
1
  module Faye
2
2
  module Logging
3
3
 
4
- DEFAULT_LOG_LEVEL = :error
5
-
6
4
  LOG_LEVELS = {
5
+ :fatal => 4,
7
6
  :error => 3,
8
7
  :warn => 2,
9
8
  :info => 1,
10
9
  :debug => 0
11
10
  }
12
11
 
13
- class << self
14
- attr_writer :log_level
15
-
16
- def log_level
17
- @log_level || DEFAULT_LOG_LEVEL
18
- end
19
- end
20
-
21
- attr_writer :log_level
12
+ private
22
13
 
23
- def log_level
24
- @log_level || Logging.log_level
14
+ LOG_LEVELS.each do |level, value|
15
+ define_method(level) { |*args| write_log(args, level) }
25
16
  end
26
17
 
27
- def log(message_args, level)
18
+ def write_log(message_args, level)
28
19
  return unless Faye.logger
29
- return if LOG_LEVELS[log_level] > LOG_LEVELS[level]
30
20
 
31
21
  message = message_args.shift.gsub(/\?/) do
32
22
  Faye.to_json(message_args.shift)
33
23
  end
34
24
 
35
- timestamp = Time.now.strftime('%Y-%m-%d %H:%M:%S')
36
- banner = " [#{ level.to_s.upcase }] [#{ self.class.name }] "
37
-
38
- Faye.logger.call(timestamp + banner + message)
39
- end
25
+ banner = "[#{ self.class.name }] "
40
26
 
41
- LOG_LEVELS.each do |level, value|
42
- define_method(level) { |*args| log(args, level) }
27
+ if Faye.logger.respond_to?(level)
28
+ Faye.logger.__send__(level, banner + message)
29
+ elsif Faye.logger.respond_to?(:call)
30
+ Faye.logger.call(banner + message)
31
+ end
43
32
  end
44
33
 
45
34
  end