face-faye 0.8.9

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