faye 0.7.2 → 0.8.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.
- data/History.txt +15 -3
- data/README.rdoc +2 -6
- data/lib/faye-browser-min.js +1 -1
- data/lib/faye.rb +25 -36
- data/lib/faye/adapters/rack_adapter.rb +43 -22
- data/lib/faye/engines/connection.rb +7 -10
- data/lib/faye/engines/memory.rb +28 -28
- data/lib/faye/engines/proxy.rb +109 -0
- data/lib/faye/mixins/logging.rb +1 -8
- data/lib/faye/mixins/timeouts.rb +1 -1
- data/lib/faye/protocol/channel.rb +3 -3
- data/lib/faye/protocol/client.rb +50 -45
- data/lib/faye/protocol/extensible.rb +11 -18
- data/lib/faye/protocol/server.rb +53 -38
- data/lib/faye/transport/http.rb +49 -27
- data/lib/faye/transport/transport.rb +3 -1
- data/lib/faye/transport/web_socket.rb +6 -10
- data/lib/faye/util/namespace.rb +2 -2
- data/spec/browser.html +3 -1
- data/spec/encoding_helper.rb +7 -0
- data/spec/javascript/client_spec.js +0 -5
- data/spec/javascript/engine/memory_spec.js +7 -0
- data/spec/javascript/engine_spec.js +22 -57
- data/spec/javascript/server/handshake_spec.js +10 -6
- data/spec/javascript/server/integration_spec.js +11 -10
- data/spec/javascript/server/publish_spec.js +85 -0
- data/spec/javascript/server_spec.js +5 -51
- data/spec/node.js +6 -6
- data/spec/ruby/client_spec.rb +1 -1
- data/spec/ruby/engine/memory_spec.rb +7 -0
- data/spec/ruby/{engine_spec.rb → engine_examples.rb} +28 -34
- data/spec/ruby/rack_adapter_spec.rb +1 -1
- data/spec/ruby/server/handshake_spec.rb +10 -6
- data/spec/ruby/server/publish_spec.rb +81 -0
- data/spec/ruby/server_spec.rb +6 -44
- data/spec/spec_helper.rb +5 -18
- data/spec/testswarm +1 -5
- metadata +105 -180
- data/lib/faye/engines/base.rb +0 -66
- data/lib/faye/engines/redis.rb +0 -225
- data/lib/faye/thin_extensions.rb +0 -75
- data/lib/faye/util/web_socket.rb +0 -89
- data/lib/faye/util/web_socket/api.rb +0 -103
- data/lib/faye/util/web_socket/client.rb +0 -82
- data/lib/faye/util/web_socket/draft75_parser.rb +0 -53
- data/lib/faye/util/web_socket/draft76_parser.rb +0 -53
- data/lib/faye/util/web_socket/protocol8_parser.rb +0 -324
- data/spec/javascript/web_socket/client_spec.js +0 -121
- data/spec/javascript/web_socket/draft75parser_spec.js +0 -39
- data/spec/javascript/web_socket/protocol8parser_spec.js +0 -153
- data/spec/redis.conf +0 -42
- data/spec/ruby/web_socket/client_spec.rb +0 -126
- data/spec/ruby/web_socket/draft75_parser_spec.rb +0 -41
- data/spec/ruby/web_socket/protocol8_parser_spec.rb +0 -145
@@ -0,0 +1,109 @@
|
|
1
|
+
module Faye
|
2
|
+
module Engine
|
3
|
+
|
4
|
+
METHODS = %w[create_client client_exists destroy_client ping subscribe unsubscribe]
|
5
|
+
MAX_DELAY = 0.0
|
6
|
+
INTERVAL = 0.0
|
7
|
+
TIMEOUT = 60.0
|
8
|
+
ID_LENGTH = 128
|
9
|
+
|
10
|
+
autoload :Connection, File.expand_path('../connection', __FILE__)
|
11
|
+
autoload :Memory, File.expand_path('../memory', __FILE__)
|
12
|
+
|
13
|
+
def self.ensure_reactor_running!
|
14
|
+
Thread.new { EM.run } unless EM.reactor_running?
|
15
|
+
Thread.pass until EM.reactor_running?
|
16
|
+
end
|
17
|
+
|
18
|
+
def self.get(options)
|
19
|
+
Proxy.new(options)
|
20
|
+
end
|
21
|
+
|
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
|
27
|
+
string
|
28
|
+
end
|
29
|
+
|
30
|
+
class Proxy
|
31
|
+
include Publisher
|
32
|
+
include Logging
|
33
|
+
|
34
|
+
attr_reader :interval, :timeout
|
35
|
+
|
36
|
+
extend Forwardable
|
37
|
+
def_delegators :@engine, *METHODS
|
38
|
+
|
39
|
+
def initialize(options)
|
40
|
+
@options = options
|
41
|
+
@connections = {}
|
42
|
+
@interval = @options[:interval] || INTERVAL
|
43
|
+
@timeout = @options[:timeout] || TIMEOUT
|
44
|
+
|
45
|
+
engine_class = @options[:type] || Memory
|
46
|
+
@engine = engine_class.create(self, @options)
|
47
|
+
|
48
|
+
debug 'Created new engine: ?', @options
|
49
|
+
end
|
50
|
+
|
51
|
+
def connect(client_id, options = {}, &callback)
|
52
|
+
debug 'Accepting connection from ?', client_id
|
53
|
+
@engine.ping(client_id)
|
54
|
+
conn = connection(client_id, true)
|
55
|
+
conn.connect(options, &callback)
|
56
|
+
@engine.empty_queue(client_id)
|
57
|
+
end
|
58
|
+
|
59
|
+
def has_connection?(client_id)
|
60
|
+
@connections.has_key?(client_id)
|
61
|
+
end
|
62
|
+
|
63
|
+
def connection(client_id, create)
|
64
|
+
conn = @connections[client_id]
|
65
|
+
return conn if conn or not create
|
66
|
+
@connections[client_id] = Connection.new(self, client_id)
|
67
|
+
end
|
68
|
+
|
69
|
+
def close_connection(client_id)
|
70
|
+
debug 'Closing connection for ?', client_id
|
71
|
+
@connections.delete(client_id)
|
72
|
+
end
|
73
|
+
|
74
|
+
def open_socket(client_id, socket)
|
75
|
+
conn = connection(client_id, true)
|
76
|
+
conn.socket = socket
|
77
|
+
end
|
78
|
+
|
79
|
+
def deliver(client_id, messages)
|
80
|
+
return if !messages || messages.empty?
|
81
|
+
conn = connection(client_id, false)
|
82
|
+
return false unless conn
|
83
|
+
messages.each(&conn.method(:deliver))
|
84
|
+
true
|
85
|
+
end
|
86
|
+
|
87
|
+
def generate_id
|
88
|
+
Engine.random
|
89
|
+
end
|
90
|
+
|
91
|
+
def flush(client_id)
|
92
|
+
debug 'Flushing connection for ?', client_id
|
93
|
+
conn = connection(client_id, false)
|
94
|
+
conn.flush!(true) if conn
|
95
|
+
end
|
96
|
+
|
97
|
+
def disconnect
|
98
|
+
@engine.disconnect if @engine.respond_to?(:disconnect)
|
99
|
+
end
|
100
|
+
|
101
|
+
def publish(message)
|
102
|
+
channels = Channel.expand(message['channel'])
|
103
|
+
@engine.publish(message, channels)
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
data/lib/faye/mixins/logging.rb
CHANGED
@@ -1,11 +1,4 @@
|
|
1
1
|
module Faye
|
2
|
-
|
3
|
-
class << self
|
4
|
-
attr_accessor :logger
|
5
|
-
end
|
6
|
-
|
7
|
-
self.logger = method(:puts)
|
8
|
-
|
9
2
|
module Logging
|
10
3
|
|
11
4
|
DEFAULT_LOG_LEVEL = :error
|
@@ -42,7 +35,7 @@ module Faye
|
|
42
35
|
timestamp = Time.now.strftime('%Y-%m-%d %H:%M:%S')
|
43
36
|
banner = " [#{ level.to_s.upcase }] [#{ self.class.name }] "
|
44
37
|
|
45
|
-
|
38
|
+
Faye.logger.call(timestamp + banner + message)
|
46
39
|
end
|
47
40
|
|
48
41
|
LOG_LEVELS.each do |level, value|
|
data/lib/faye/mixins/timeouts.rb
CHANGED
@@ -22,8 +22,8 @@ module Faye
|
|
22
22
|
UNSUBSCRIBE = '/meta/unsubscribe'
|
23
23
|
DISCONNECT = '/meta/disconnect'
|
24
24
|
|
25
|
-
META =
|
26
|
-
SERVICE =
|
25
|
+
META = :meta
|
26
|
+
SERVICE = :service
|
27
27
|
|
28
28
|
class << self
|
29
29
|
def expand(name)
|
@@ -50,7 +50,7 @@ module Faye
|
|
50
50
|
|
51
51
|
def parse(name)
|
52
52
|
return nil unless valid?(name)
|
53
|
-
name.split('/')[1..-1]
|
53
|
+
name.split('/')[1..-1].map { |s| s.to_sym }
|
54
54
|
end
|
55
55
|
|
56
56
|
def unparse(segments)
|
data/lib/faye/protocol/client.rb
CHANGED
@@ -16,16 +16,19 @@ module Faye
|
|
16
16
|
NONE = 'none'
|
17
17
|
|
18
18
|
CONNECTION_TIMEOUT = 60.0
|
19
|
+
DEFAULT_RETRY = 5.0
|
19
20
|
|
20
|
-
|
21
|
-
attr_reader :endpoint, :client_id
|
21
|
+
attr_reader :endpoint, :client_id, :retry
|
22
22
|
|
23
23
|
def initialize(endpoint = nil, options = {})
|
24
24
|
info('New client created for ?', endpoint)
|
25
25
|
|
26
26
|
@endpoint = endpoint || RackAdapter::DEFAULT_ENDPOINT
|
27
|
+
@cookies = CookieJar::Jar.new
|
28
|
+
@headers = {}
|
27
29
|
@options = options
|
28
30
|
@disabled = []
|
31
|
+
@retry = @options[:retry] || DEFAULT_RETRY
|
29
32
|
|
30
33
|
select_transport(MANDATORY_CONNECTION_TYPES)
|
31
34
|
|
@@ -46,6 +49,10 @@ module Faye
|
|
46
49
|
@disabled << feature
|
47
50
|
end
|
48
51
|
|
52
|
+
def set_header(name, value)
|
53
|
+
@headers[name.to_s] = value.to_s
|
54
|
+
end
|
55
|
+
|
49
56
|
def state
|
50
57
|
case @state
|
51
58
|
when UNCONNECTED then :UNCONNECTED
|
@@ -181,40 +188,41 @@ module Faye
|
|
181
188
|
# * ext
|
182
189
|
# * id
|
183
190
|
# * timestamp
|
184
|
-
def subscribe(
|
185
|
-
if Array ===
|
186
|
-
return
|
191
|
+
def subscribe(channel, force = false, &block)
|
192
|
+
if Array === channel
|
193
|
+
return channel.each do |channel|
|
187
194
|
subscribe(channel, force, &block)
|
188
195
|
end
|
189
196
|
end
|
190
197
|
|
191
|
-
subscription
|
198
|
+
subscription = Subscription.new(self, channel, block)
|
199
|
+
has_subscribe = @channels.has_subscription?(channel)
|
192
200
|
|
193
|
-
|
194
|
-
@channels.subscribe([
|
195
|
-
|
196
|
-
|
201
|
+
unless force
|
202
|
+
@channels.subscribe([channel], block)
|
203
|
+
if has_subscribe
|
204
|
+
subscription.set_deferred_status(:succeeded)
|
205
|
+
return subscription
|
206
|
+
end
|
197
207
|
end
|
198
208
|
|
199
209
|
connect {
|
200
|
-
info('Client ? attempting to subscribe to ?', @client_id,
|
210
|
+
info('Client ? attempting to subscribe to ?', @client_id, channel)
|
201
211
|
|
202
212
|
send({
|
203
213
|
'channel' => Channel::SUBSCRIBE,
|
204
214
|
'clientId' => @client_id,
|
205
|
-
'subscription' =>
|
215
|
+
'subscription' => channel
|
206
216
|
|
207
217
|
}) do |response|
|
208
|
-
|
209
|
-
|
210
|
-
channels = [response['subscription']].flatten
|
211
|
-
info('Subscription acknowledged for ? to ?', @client_id, channels)
|
212
|
-
@channels.subscribe(channels, block)
|
213
|
-
|
214
|
-
subscription.set_deferred_status(:succeeded)
|
215
|
-
else
|
218
|
+
unless response['successful']
|
216
219
|
subscription.set_deferred_status(:failed, Error.parse(response['error']))
|
220
|
+
next @channels.unsubscribe(channel, block)
|
217
221
|
end
|
222
|
+
|
223
|
+
channels = [response['subscription']].flatten
|
224
|
+
info('Subscription acknowledged for ? to ?', @client_id, channels)
|
225
|
+
subscription.set_deferred_status(:succeeded)
|
218
226
|
end
|
219
227
|
}
|
220
228
|
subscription
|
@@ -230,30 +238,29 @@ module Faye
|
|
230
238
|
# * ext
|
231
239
|
# * id
|
232
240
|
# * timestamp
|
233
|
-
def unsubscribe(
|
234
|
-
if Array ===
|
235
|
-
return
|
241
|
+
def unsubscribe(channel, &block)
|
242
|
+
if Array === channel
|
243
|
+
return channel.each do |channel|
|
236
244
|
unsubscribe(channel, &block)
|
237
245
|
end
|
238
246
|
end
|
239
247
|
|
240
|
-
dead = @channels.unsubscribe(
|
248
|
+
dead = @channels.unsubscribe(channel, block)
|
241
249
|
return unless dead
|
242
250
|
|
243
251
|
connect {
|
244
|
-
info('Client ? attempting to unsubscribe from ?', @client_id,
|
252
|
+
info('Client ? attempting to unsubscribe from ?', @client_id, channel)
|
245
253
|
|
246
254
|
send({
|
247
255
|
'channel' => Channel::UNSUBSCRIBE,
|
248
256
|
'clientId' => @client_id,
|
249
|
-
'subscription' =>
|
257
|
+
'subscription' => channel
|
250
258
|
|
251
259
|
}) do |response|
|
252
|
-
|
260
|
+
next unless response['successful']
|
253
261
|
|
254
|
-
|
255
|
-
|
256
|
-
end
|
262
|
+
channels = [response['subscription']].flatten
|
263
|
+
info('Unsubscription acknowledged for ? from ?', @client_id, channels)
|
257
264
|
end
|
258
265
|
}
|
259
266
|
end
|
@@ -265,10 +272,6 @@ module Faye
|
|
265
272
|
# * id * error
|
266
273
|
# * ext * ext
|
267
274
|
def publish(channel, data)
|
268
|
-
unless Grammar::CHANNEL_NAME =~ channel
|
269
|
-
raise "Cannot publish: '#{channel}' is not a valid channel name"
|
270
|
-
end
|
271
|
-
|
272
275
|
publication = Publication.new
|
273
276
|
connect {
|
274
277
|
info('Client ? queueing published message to ?: ?', @client_id, channel, data)
|
@@ -290,17 +293,17 @@ module Faye
|
|
290
293
|
|
291
294
|
def receive_message(message)
|
292
295
|
pipe_through_extensions(:incoming, message) do |message|
|
293
|
-
|
294
|
-
|
295
|
-
|
296
|
-
|
297
|
-
|
298
|
-
|
299
|
-
|
300
|
-
|
301
|
-
|
302
|
-
deliver_message(message)
|
296
|
+
next unless message
|
297
|
+
|
298
|
+
handle_advice(message['advice']) if message['advice']
|
299
|
+
|
300
|
+
callback = @response_callbacks[message['id']]
|
301
|
+
if callback
|
302
|
+
@response_callbacks.delete(message['id'])
|
303
|
+
callback.call(message)
|
303
304
|
end
|
305
|
+
|
306
|
+
deliver_message(message)
|
304
307
|
end
|
305
308
|
end
|
306
309
|
|
@@ -309,6 +312,8 @@ module Faye
|
|
309
312
|
def select_transport(transport_types)
|
310
313
|
Transport.get(self, transport_types) do |transport|
|
311
314
|
@transport = transport
|
315
|
+
@transport.cookies = @cookies
|
316
|
+
@transport.headers = @headers
|
312
317
|
|
313
318
|
transport.bind :down do
|
314
319
|
if @transport_up.nil? or @transport_up
|
@@ -352,7 +357,7 @@ module Faye
|
|
352
357
|
end
|
353
358
|
|
354
359
|
def deliver_message(message)
|
355
|
-
return unless message
|
360
|
+
return unless message.has_key?('channel') and message.has_key?('data')
|
356
361
|
info('Client ? calling listeners for ? with ?', @client_id, message['channel'], message['data'])
|
357
362
|
@channels.distribute_message(message)
|
358
363
|
end
|
@@ -11,12 +11,9 @@ module Faye
|
|
11
11
|
def remove_extension(extension)
|
12
12
|
return unless @extensions
|
13
13
|
@extensions.delete_if do |ext|
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
else
|
18
|
-
false
|
19
|
-
end
|
14
|
+
next false unless ext == extension
|
15
|
+
extension.removed(self) if extension.respond_to?(:removed)
|
16
|
+
true
|
20
17
|
end
|
21
18
|
end
|
22
19
|
|
@@ -27,19 +24,15 @@ module Faye
|
|
27
24
|
extensions = @extensions.dup
|
28
25
|
|
29
26
|
pipe = lambda do |message|
|
30
|
-
|
31
|
-
|
27
|
+
next callback.call(message) unless message
|
28
|
+
|
29
|
+
extension = extensions.shift
|
30
|
+
next callback.call(message) unless extension
|
31
|
+
|
32
|
+
if extension.respond_to?(stage)
|
33
|
+
extension.__send__(stage, message, pipe)
|
32
34
|
else
|
33
|
-
|
34
|
-
if (!extension)
|
35
|
-
callback.call(message)
|
36
|
-
else
|
37
|
-
if extension.respond_to?(stage)
|
38
|
-
extension.__send__(stage, message, pipe)
|
39
|
-
else
|
40
|
-
pipe.call(message)
|
41
|
-
end
|
42
|
-
end
|
35
|
+
pipe.call(message)
|
43
36
|
end
|
44
37
|
end
|
45
38
|
pipe.call(message)
|
data/lib/faye/protocol/server.rb
CHANGED
@@ -3,8 +3,6 @@ module Faye
|
|
3
3
|
|
4
4
|
include Logging
|
5
5
|
include Extensible
|
6
|
-
|
7
|
-
META_METHODS = %w[handshake connect disconnect subscribe unsubscribe]
|
8
6
|
|
9
7
|
attr_reader :engine
|
10
8
|
|
@@ -18,11 +16,18 @@ module Faye
|
|
18
16
|
end
|
19
17
|
|
20
18
|
def flush_connection(messages)
|
21
|
-
[messages].flatten.
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
19
|
+
client_id = [messages].flatten.first['clientId']
|
20
|
+
return unless client_id
|
21
|
+
info 'Flushing connection for ?', client_id
|
22
|
+
@engine.flush(client_id) if client_id
|
23
|
+
end
|
24
|
+
|
25
|
+
def open_socket(client_id, socket)
|
26
|
+
@engine.open_socket(client_id, socket)
|
27
|
+
end
|
28
|
+
|
29
|
+
def close_socket(client_id)
|
30
|
+
@engine.flush(client_id)
|
26
31
|
end
|
27
32
|
|
28
33
|
def process(messages, local = false, &callback)
|
@@ -63,11 +68,12 @@ module Faye
|
|
63
68
|
|
64
69
|
def make_response(message)
|
65
70
|
response = {}
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
+
|
72
|
+
response['id'] = message['id'] if message['id']
|
73
|
+
response['clientId'] = message['clientId'] if message['clientId']
|
74
|
+
response['channel'] = message['channel'] if message['channel']
|
75
|
+
response['error'] = message['error'] if message['error']
|
76
|
+
|
71
77
|
response['successful'] = !response['error']
|
72
78
|
response
|
73
79
|
end
|
@@ -77,47 +83,51 @@ module Faye
|
|
77
83
|
info 'Handling message: ? (local: ?)', message, local
|
78
84
|
|
79
85
|
channel_name = message['channel']
|
86
|
+
error = message['error']
|
80
87
|
|
81
88
|
return handle_meta(message, local, &callback) if Channel.meta?(channel_name)
|
82
89
|
|
83
|
-
|
84
|
-
|
85
|
-
if message['clientId']
|
86
|
-
response = make_response(message)
|
87
|
-
response['successful'] = !response['error']
|
88
|
-
callback.call([response])
|
89
|
-
else
|
90
|
-
callback.call([])
|
90
|
+
if Grammar::CHANNEL_NAME !~ channel_name
|
91
|
+
error = Faye::Error.channel_invalid(channel_name)
|
91
92
|
end
|
93
|
+
|
94
|
+
@engine.publish(message) unless error
|
95
|
+
|
96
|
+
response = make_response(message)
|
97
|
+
response['error'] = error if error
|
98
|
+
response['successful'] = !response['error']
|
99
|
+
callback.call([response])
|
92
100
|
end
|
93
101
|
|
94
102
|
def handle_meta(message, local, &callback)
|
95
|
-
method
|
103
|
+
method = Channel.parse(message['channel'])[1]
|
104
|
+
client_id = message['clientId']
|
96
105
|
|
97
|
-
unless META_METHODS.include?(method)
|
98
|
-
response = make_response(message)
|
99
|
-
response['error'] = Faye::Error.channel_forbidden(message['channel'])
|
100
|
-
response['successful'] = false
|
101
|
-
return callback.call([response])
|
102
|
-
end
|
103
|
-
|
104
106
|
__send__(method, message, local) do |responses|
|
105
107
|
responses = [responses].flatten
|
106
|
-
responses.each(
|
108
|
+
responses.each { |r| advize(r, message['connectionType']) }
|
107
109
|
callback.call(responses)
|
108
110
|
end
|
109
111
|
end
|
110
112
|
|
111
|
-
def advize(response)
|
113
|
+
def advize(response, connection_type)
|
112
114
|
return unless [Channel::HANDSHAKE, Channel::CONNECT].include?(response['channel'])
|
113
115
|
|
116
|
+
if connection_type == 'eventsource'
|
117
|
+
interval = (@engine.timeout * 1000).floor
|
118
|
+
timeout = 0
|
119
|
+
else
|
120
|
+
interval = (@engine.interval * 1000).floor
|
121
|
+
timeout = (@engine.timeout * 1000).floor
|
122
|
+
end
|
123
|
+
|
114
124
|
advice = response['advice'] ||= {}
|
115
125
|
if response['error']
|
116
126
|
advice['reconnect'] ||= 'handshake'
|
117
127
|
else
|
118
128
|
advice['reconnect'] ||= 'retry'
|
119
|
-
advice['interval'] ||=
|
120
|
-
advice['timeout'] ||=
|
129
|
+
advice['interval'] ||= interval
|
130
|
+
advice['timeout'] ||= timeout
|
121
131
|
end
|
122
132
|
end
|
123
133
|
|
@@ -173,13 +183,18 @@ module Faye
|
|
173
183
|
|
174
184
|
response['successful'] = response['error'].nil?
|
175
185
|
|
176
|
-
if response['successful']
|
177
|
-
@engine.connect(response['clientId'], message['advice']) do |events|
|
178
|
-
callback.call([response] + events)
|
179
|
-
end
|
180
|
-
else
|
186
|
+
if !response['successful']
|
181
187
|
response.delete('clientId')
|
182
|
-
callback.call(response)
|
188
|
+
next callback.call(response)
|
189
|
+
end
|
190
|
+
|
191
|
+
if message['connectionType'] == 'eventsource'
|
192
|
+
message['advice'] ||= {}
|
193
|
+
message['advice']['timeout'] = 0
|
194
|
+
end
|
195
|
+
|
196
|
+
@engine.connect(response['clientId'], message['advice']) do |events|
|
197
|
+
callback.call([response] + events)
|
183
198
|
end
|
184
199
|
end
|
185
200
|
end
|