face-faye 0.8.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/History.txt +304 -0
- data/README.rdoc +83 -0
- data/lib/faye-browser-min.js +2 -0
- data/lib/faye-browser-min.js.map +8 -0
- data/lib/faye-browser.js +2194 -0
- data/lib/faye.rb +122 -0
- data/lib/faye/adapters/rack_adapter.rb +216 -0
- data/lib/faye/adapters/static_server.rb +56 -0
- data/lib/faye/engines/connection.rb +60 -0
- data/lib/faye/engines/memory.rb +112 -0
- data/lib/faye/engines/proxy.rb +121 -0
- data/lib/faye/error.rb +49 -0
- data/lib/faye/mixins/logging.rb +47 -0
- data/lib/faye/mixins/publisher.rb +30 -0
- data/lib/faye/mixins/timeouts.rb +22 -0
- data/lib/faye/protocol/channel.rb +124 -0
- data/lib/faye/protocol/client.rb +376 -0
- data/lib/faye/protocol/extensible.rb +43 -0
- data/lib/faye/protocol/grammar.rb +58 -0
- data/lib/faye/protocol/publication.rb +5 -0
- data/lib/faye/protocol/server.rb +293 -0
- data/lib/faye/protocol/socket.rb +23 -0
- data/lib/faye/protocol/subscription.rb +24 -0
- data/lib/faye/transport/http.rb +76 -0
- data/lib/faye/transport/local.rb +22 -0
- data/lib/faye/transport/transport.rb +116 -0
- data/lib/faye/transport/web_socket.rb +92 -0
- data/lib/faye/util/namespace.rb +20 -0
- data/spec/browser.html +45 -0
- data/spec/encoding_helper.rb +7 -0
- data/spec/install.sh +78 -0
- data/spec/javascript/channel_spec.js +15 -0
- data/spec/javascript/client_spec.js +729 -0
- data/spec/javascript/engine/memory_spec.js +7 -0
- data/spec/javascript/engine_spec.js +417 -0
- data/spec/javascript/faye_spec.js +34 -0
- data/spec/javascript/grammar_spec.js +66 -0
- data/spec/javascript/node_adapter_spec.js +307 -0
- data/spec/javascript/publisher_spec.js +27 -0
- data/spec/javascript/server/connect_spec.js +168 -0
- data/spec/javascript/server/disconnect_spec.js +121 -0
- data/spec/javascript/server/extensions_spec.js +60 -0
- data/spec/javascript/server/handshake_spec.js +145 -0
- data/spec/javascript/server/integration_spec.js +131 -0
- data/spec/javascript/server/publish_spec.js +85 -0
- data/spec/javascript/server/subscribe_spec.js +247 -0
- data/spec/javascript/server/unsubscribe_spec.js +245 -0
- data/spec/javascript/server_spec.js +121 -0
- data/spec/javascript/transport_spec.js +135 -0
- data/spec/node.js +55 -0
- data/spec/phantom.js +17 -0
- data/spec/ruby/channel_spec.rb +17 -0
- data/spec/ruby/client_spec.rb +741 -0
- data/spec/ruby/engine/memory_spec.rb +7 -0
- data/spec/ruby/engine_examples.rb +427 -0
- data/spec/ruby/faye_spec.rb +30 -0
- data/spec/ruby/grammar_spec.rb +68 -0
- data/spec/ruby/publisher_spec.rb +27 -0
- data/spec/ruby/rack_adapter_spec.rb +236 -0
- data/spec/ruby/server/connect_spec.rb +170 -0
- data/spec/ruby/server/disconnect_spec.rb +120 -0
- data/spec/ruby/server/extensions_spec.rb +68 -0
- data/spec/ruby/server/handshake_spec.rb +143 -0
- data/spec/ruby/server/integration_spec.rb +133 -0
- data/spec/ruby/server/publish_spec.rb +81 -0
- data/spec/ruby/server/subscribe_spec.rb +247 -0
- data/spec/ruby/server/unsubscribe_spec.rb +247 -0
- data/spec/ruby/server_spec.rb +121 -0
- data/spec/ruby/transport_spec.rb +136 -0
- data/spec/spec_helper.rb +11 -0
- data/spec/testswarm +42 -0
- data/spec/thin_proxy.rb +37 -0
- metadata +441 -0
data/lib/faye.rb
ADDED
@@ -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
|
+
|