faye-ouvrages 1.1.2

Sign up to get free protection for your applications and to get access to all the features.
data/lib/faye.rb ADDED
@@ -0,0 +1,128 @@
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 'multi_json'
10
+ require 'rack'
11
+ require 'securerandom'
12
+ require 'set'
13
+ require 'time'
14
+ require 'uri'
15
+
16
+ module Faye
17
+ VERSION = '1.1.2'
18
+
19
+ ROOT = File.expand_path(File.dirname(__FILE__))
20
+
21
+ autoload :Deferrable, File.join(ROOT, 'faye', 'mixins', 'deferrable')
22
+ autoload :Logging, File.join(ROOT, 'faye', 'mixins', 'logging')
23
+ autoload :Publisher, File.join(ROOT, 'faye', 'mixins', 'publisher')
24
+ autoload :Timeouts, File.join(ROOT, 'faye', 'mixins', 'timeouts')
25
+
26
+ autoload :Namespace, File.join(ROOT, 'faye', 'util', 'namespace')
27
+
28
+ autoload :Engine, File.join(ROOT, 'faye', 'engines', 'proxy')
29
+
30
+ autoload :Channel, File.join(ROOT, 'faye', 'protocol', 'channel')
31
+ autoload :Client, File.join(ROOT, 'faye', 'protocol', 'client')
32
+ autoload :Dispatcher, File.join(ROOT, 'faye', 'protocol', 'dispatcher')
33
+ autoload :Scheduler, File.join(ROOT, 'faye', 'protocol', 'scheduler')
34
+ autoload :Extensible, File.join(ROOT, 'faye', 'protocol', 'extensible')
35
+ autoload :Grammar, File.join(ROOT, 'faye', 'protocol', 'grammar')
36
+ autoload :Publication, File.join(ROOT, 'faye', 'protocol', 'publication')
37
+ autoload :Server, File.join(ROOT, 'faye', 'protocol', 'server')
38
+ autoload :Subscription, File.join(ROOT, 'faye', 'protocol', 'subscription')
39
+
40
+ autoload :Error, File.join(ROOT, 'faye', 'error')
41
+ autoload :Transport, File.join(ROOT, 'faye', 'transport', 'transport')
42
+
43
+ autoload :RackAdapter, File.join(ROOT, 'faye', 'adapters', 'rack_adapter')
44
+ autoload :StaticServer, File.join(ROOT, 'faye', 'adapters', 'static_server')
45
+
46
+ BAYEUX_VERSION = '1.0'
47
+ JSONP_CALLBACK = 'jsonpcallback'
48
+ CONNECTION_TYPES = %w[long-polling cross-origin-long-polling callback-polling websocket eventsource in-process]
49
+
50
+ MANDATORY_CONNECTION_TYPES = %w[long-polling callback-polling in-process]
51
+
52
+ class << self
53
+ attr_accessor :logger
54
+ end
55
+
56
+ def self.ensure_reactor_running!
57
+ Engine.ensure_reactor_running!
58
+ end
59
+
60
+ def self.random(*args)
61
+ Engine.random(*args)
62
+ end
63
+
64
+ def self.client_id_from_messages(messages)
65
+ first = [messages].flatten.find { |m| m['channel'] == '/meta/connect' }
66
+ first && first['clientId']
67
+ end
68
+
69
+ def self.copy_object(object)
70
+ case object
71
+ when Hash
72
+ clone = {}
73
+ object.each { |k,v| clone[k] = copy_object(v) }
74
+ clone
75
+ when Array
76
+ clone = []
77
+ object.each { |v| clone << copy_object(v) }
78
+ clone
79
+ else
80
+ object
81
+ end
82
+ end
83
+
84
+ def self.parse_url(url)
85
+ String === url ? URI.parse(url) : url
86
+ end
87
+
88
+ def self.to_json(value)
89
+ case value
90
+ when Hash, Array then MultiJson.dump(value)
91
+ when String, NilClass then value.inspect
92
+ else value.to_s
93
+ end
94
+ end
95
+
96
+ def self.async_each(list, iterator, callback)
97
+ n = list.size
98
+ i = -1
99
+ calls = 0
100
+ looping = false
101
+
102
+ loop, resume = nil, nil
103
+
104
+ iterate = lambda do
105
+ calls -= 1
106
+ i += 1
107
+ if i == n
108
+ callback.call if callback
109
+ else
110
+ iterator.call(list[i], resume)
111
+ end
112
+ end
113
+
114
+ loop = lambda do
115
+ unless looping
116
+ looping = true
117
+ iterate.call while calls > 0
118
+ looping = false
119
+ end
120
+ end
121
+
122
+ resume = lambda do
123
+ calls += 1
124
+ loop.call
125
+ end
126
+ resume.call
127
+ end
128
+ end
@@ -0,0 +1,260 @@
1
+ module Faye
2
+ class RackAdapter
3
+
4
+ include Logging
5
+
6
+ extend Forwardable
7
+ def_delegators '@server.engine', *Faye::Publisher.instance_methods
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
+ VALID_JSONP_CALLBACK = /^[a-z_\$][a-z0-9_\$]*(\.[a-z_\$][a-z0-9_\$]*)*$/i
19
+
20
+ # This header is passed by Rack::Proxy during testing. Rack::Proxy seems to
21
+ # set content-length for you, and setting it in here really slows the tests
22
+ # down. Better suggestions welcome.
23
+ HTTP_X_NO_CONTENT_LENGTH = 'HTTP_X_NO_CONTENT_LENGTH'
24
+
25
+ def initialize(app = nil, options = nil, &block)
26
+ @app = app if app.respond_to?(:call)
27
+ @options = [app, options].grep(Hash).first || {}
28
+
29
+ ::WebSocket::Driver.validate_options(@options, [:engine, :mount, :ping, :timeout, :extensions, :websocket_extensions])
30
+
31
+ @endpoint = @options[:mount] || DEFAULT_ENDPOINT
32
+ @extensions = []
33
+ @endpoint_re = Regexp.new('^' + @endpoint.gsub(/\/$/, '') + '(/[^/]*)*(\\.[^\\.]+)?$')
34
+ @server = Server.new(@options)
35
+
36
+ @static = StaticServer.new(File.join(ROOT, 'client'), /\.(?:js|map)$/)
37
+ @static.map(File.basename(@endpoint) + '.js', SCRIPT_PATH)
38
+ @static.map('client.js', SCRIPT_PATH)
39
+
40
+ if extensions = @options[:extensions]
41
+ [*extensions].each { |extension| add_extension(extension) }
42
+ end
43
+
44
+ if websocket_extensions = @options[:websocket_extensions]
45
+ [*websocket_extensions].each { |ext| add_websocket_extension(ext) }
46
+ end
47
+
48
+ block.call(self) if block
49
+ end
50
+
51
+ def listen(*args)
52
+ 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'
53
+ end
54
+
55
+ def add_extension(extension)
56
+ @server.add_extension(extension)
57
+ end
58
+
59
+ def remove_extension(extension)
60
+ @server.remove_extension(extension)
61
+ end
62
+
63
+ def add_websocket_extension(extension)
64
+ @extensions << extension
65
+ end
66
+
67
+ def close
68
+ @server.close
69
+ end
70
+
71
+ def get_client
72
+ @client ||= Client.new(@server)
73
+ end
74
+
75
+ def call(env)
76
+ Faye.ensure_reactor_running!
77
+ request = Rack::Request.new(env)
78
+
79
+ unless request.path_info =~ @endpoint_re
80
+ env['faye.client'] = get_client
81
+ return @app ? @app.call(env) :
82
+ [404, TYPE_TEXT, ["Sure you're not looking for #{@endpoint} ?"]]
83
+ end
84
+
85
+ return @static.call(env) if @static =~ request.path_info
86
+
87
+ # http://groups.google.com/group/faye-users/browse_thread/thread/4a01bb7d25d3636a
88
+ if env['REQUEST_METHOD'] == 'OPTIONS' or env['HTTP_ACCESS_CONTROL_REQUEST_METHOD'] == 'POST'
89
+ return handle_options
90
+ end
91
+
92
+ return handle_websocket(request) if Faye::WebSocket.websocket?(env)
93
+ return handle_eventsource(request) if Faye::EventSource.eventsource?(env)
94
+
95
+ handle_request(request)
96
+ end
97
+
98
+ private
99
+
100
+ def handle_request(request)
101
+ unless json_msg = message_from_request(request)
102
+ error 'Received request with no message: ?', format_request(request)
103
+ return [400, TYPE_TEXT, ['Bad request']]
104
+ end
105
+
106
+ unless json_msg.force_encoding('UTF-8').valid_encoding?
107
+ error 'Received request with invalid encoding: ?', format_request(request)
108
+ return [400, TYPE_TEXT, ['Bad request']]
109
+ end
110
+
111
+ debug("Received message via HTTP #{request.request_method}: ?", json_msg)
112
+
113
+ message = MultiJson.load(json_msg)
114
+ jsonp = request.params['jsonp'] || JSONP_CALLBACK
115
+ headers = request.get? ? TYPE_SCRIPT.dup : TYPE_JSON.dup
116
+ origin = request.env['HTTP_ORIGIN']
117
+ callback = request.env['async.callback']
118
+
119
+ if jsonp !~ VALID_JSONP_CALLBACK
120
+ error 'Invalid JSON-P callback: ?', jsonp
121
+ return [400, TYPE_TEXT, ['Bad request']]
122
+ end
123
+
124
+ headers['Access-Control-Allow-Origin'] = origin if origin
125
+ headers['Cache-Control'] = 'no-cache, no-store'
126
+ headers['X-Content-Type-Options'] = 'nosniff'
127
+
128
+ request.env['rack.hijack'].call if request.env['rack.hijack']
129
+ hijack = request.env['rack.hijack_io']
130
+
131
+ EventMachine.next_tick do
132
+ @server.process(message, request) do |replies|
133
+ response = Faye.to_json(replies)
134
+
135
+ if request.get?
136
+ response = "/**/#{ jsonp }(#{ jsonp_escape(response) });"
137
+ headers['Content-Disposition'] = 'attachment; filename=f.txt'
138
+ end
139
+
140
+ headers['Content-Length'] = response.bytesize.to_s unless request.env[HTTP_X_NO_CONTENT_LENGTH]
141
+ headers['Connection'] = 'close'
142
+ debug('HTTP response: ?', response)
143
+ send_response([200, headers, [response]], hijack, callback)
144
+ end
145
+ end
146
+
147
+ ASYNC_RESPONSE
148
+ rescue => e
149
+ error "#{e.message}\nBacktrace:\n#{e.backtrace * "\n"}"
150
+ [400, TYPE_TEXT, ['Bad request']]
151
+ end
152
+
153
+ def message_from_request(request)
154
+ message = request.params['message']
155
+ return message if message
156
+
157
+ # Some clients do not send a content-type, e.g.
158
+ # Internet Explorer when using cross-origin-long-polling
159
+ # Some use application/xml when using CORS
160
+ content_type = request.env['CONTENT_TYPE'] || ''
161
+
162
+ if content_type.split(';').first == 'application/json'
163
+ request.body.read
164
+ else
165
+ CGI.parse(request.body.read)['message'][0]
166
+ end
167
+ end
168
+
169
+ def jsonp_escape(json)
170
+ json.gsub(/\u2028/, '\u2028').gsub(/\u2029/, '\u2029')
171
+ end
172
+
173
+ def send_response(response, hijack, callback)
174
+ return callback.call(response) if callback
175
+
176
+ buffer = "HTTP/1.1 #{response[0]} OK\r\n"
177
+ response[1].each do |name, value|
178
+ buffer << "#{name}: #{value}\r\n"
179
+ end
180
+ buffer << "\r\n"
181
+ response[2].each do |chunk|
182
+ buffer << chunk
183
+ end
184
+
185
+ hijack.write(buffer)
186
+ hijack.flush
187
+ hijack.close_write
188
+ end
189
+
190
+ def handle_websocket(request)
191
+ options = {:extensions => @extensions, :ping => @options[:ping]}
192
+ ws = Faye::WebSocket.new(request.env, [], options)
193
+ client_id = nil
194
+
195
+ ws.onmessage = lambda do |event|
196
+ begin
197
+ debug("Received message via WebSocket[#{ws.version}]: ?", event.data)
198
+
199
+ message = MultiJson.load(event.data)
200
+ cid = Faye.client_id_from_messages(message)
201
+
202
+ @server.close_socket(client_id, false) if client_id and cid and cid != client_id
203
+ @server.open_socket(cid, ws, request)
204
+ client_id = cid if cid
205
+
206
+ @server.process(message, request) do |replies|
207
+ ws.send(Faye.to_json(replies)) if ws
208
+ end
209
+ rescue => e
210
+ error "#{e.message}\nBacktrace:\n#{e.backtrace * "\n"}"
211
+ end
212
+ end
213
+
214
+ ws.onclose = lambda do |event|
215
+ @server.close_socket(client_id)
216
+ ws = nil
217
+ end
218
+
219
+ ws.rack_response
220
+ end
221
+
222
+ def handle_eventsource(request)
223
+ es = Faye::EventSource.new(request.env, :ping => @options[:ping])
224
+ client_id = es.url.split('/').pop
225
+
226
+ debug('Opened EventSource connection for ?', client_id)
227
+ @server.open_socket(client_id, es, request)
228
+
229
+ es.onclose = lambda do |event|
230
+ @server.close_socket(client_id)
231
+ es = nil
232
+ end
233
+
234
+ es.rack_response
235
+ end
236
+
237
+ def handle_options
238
+ headers = {
239
+ 'Access-Control-Allow-Credentials' => 'false',
240
+ 'Access-Control-Allow-Headers' => 'Accept, Authorization, Content-Type, Pragma, X-Requested-With',
241
+ 'Access-Control-Allow-Methods' => 'POST, GET',
242
+ 'Access-Control-Allow-Origin' => '*',
243
+ 'Access-Control-Max-Age' => '86400'
244
+ }
245
+ [200, headers, []]
246
+ end
247
+
248
+ def format_request(request)
249
+ request.body.rewind
250
+ string = "curl -X #{request.request_method.upcase}"
251
+ string << " '#{request.url}'"
252
+ if request.post?
253
+ string << " -H 'Content-Type: #{request.env['CONTENT_TYPE']}'"
254
+ string << " -d '#{request.body.read}'"
255
+ end
256
+ string
257
+ end
258
+
259
+ end
260
+ end
@@ -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,58 @@
1
+ module Faye
2
+ module Engine
3
+
4
+ class Connection
5
+ include 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
+ message.delete('clientId')
19
+ return @socket.send(message) if @socket
20
+ return unless @inbox.add?(message)
21
+ begin_delivery_timeout
22
+ end
23
+
24
+ def connect(options, &block)
25
+ options = options || {}
26
+ timeout = options['timeout'] ? options['timeout'] / 1000.0 : @engine.timeout
27
+
28
+ set_deferred_status(:unknown)
29
+ callback(&block)
30
+
31
+ begin_delivery_timeout
32
+ begin_connection_timeout(timeout)
33
+ end
34
+
35
+ def flush
36
+ remove_timeout(:connection)
37
+ remove_timeout(:delivery)
38
+
39
+ set_deferred_status(:succeeded, @inbox.entries)
40
+ @inbox = []
41
+
42
+ @engine.close_connection(@id) unless @socket
43
+ end
44
+
45
+ private
46
+
47
+ def begin_delivery_timeout
48
+ return if @inbox.empty?
49
+ add_timeout(:delivery, MAX_DELAY) { flush }
50
+ end
51
+
52
+ def begin_connection_timeout(timeout)
53
+ add_timeout(:connection, timeout) { flush }
54
+ end
55
+ end
56
+
57
+ end
58
+ end