bunny 0.7.12 → 0.8.0

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.
Files changed (59) hide show
  1. data/.gitignore +2 -2
  2. data/.travis.yml +7 -16
  3. data/CHANGELOG +3 -21
  4. data/Gemfile +2 -4
  5. data/README.textile +31 -9
  6. data/Rakefile +3 -3
  7. data/bunny.gemspec +6 -3
  8. data/examples/{simple_08.rb → simple.rb} +1 -1
  9. data/examples/{simple_ack_08.rb → simple_ack.rb} +1 -1
  10. data/examples/{simple_consumer_08.rb → simple_consumer.rb} +4 -4
  11. data/examples/{simple_fanout_08.rb → simple_fanout.rb} +1 -1
  12. data/examples/{simple_headers_08.rb → simple_headers.rb} +2 -2
  13. data/examples/{simple_publisher_09.rb → simple_publisher.rb} +1 -1
  14. data/examples/{simple_topic_09.rb → simple_topic.rb} +2 -2
  15. data/ext/amqp-0.9.1.json +1 -0
  16. data/ext/config.yml +3 -3
  17. data/ext/qparser.rb +9 -52
  18. data/lib/bunny.rb +15 -33
  19. data/lib/bunny/{channel08.rb → channel.rb} +0 -0
  20. data/lib/bunny/{client09.rb → client.rb} +34 -46
  21. data/lib/bunny/{exchange09.rb → exchange.rb} +16 -15
  22. data/lib/bunny/{queue09.rb → queue.rb} +26 -23
  23. data/lib/bunny/{subscription09.rb → subscription.rb} +11 -6
  24. data/lib/bunny/version.rb +1 -1
  25. data/lib/qrack/client.rb +30 -21
  26. data/lib/qrack/protocol/{protocol08.rb → protocol.rb} +2 -1
  27. data/lib/qrack/protocol/{spec09.rb → spec.rb} +8 -7
  28. data/lib/qrack/{qrack08.rb → qrack.rb} +4 -4
  29. data/lib/qrack/subscription.rb +58 -9
  30. data/lib/qrack/transport/{buffer08.rb → buffer.rb} +8 -0
  31. data/lib/qrack/transport/{frame08.rb → frame.rb} +7 -22
  32. data/spec/spec_09/bunny_spec.rb +10 -8
  33. data/spec/spec_09/connection_spec.rb +8 -3
  34. data/spec/spec_09/exchange_spec.rb +22 -19
  35. data/spec/spec_09/queue_spec.rb +32 -18
  36. metadata +69 -76
  37. checksums.yaml +0 -7
  38. data/examples/simple_09.rb +0 -32
  39. data/examples/simple_ack_09.rb +0 -35
  40. data/examples/simple_consumer_09.rb +0 -55
  41. data/examples/simple_fanout_09.rb +0 -41
  42. data/examples/simple_headers_09.rb +0 -42
  43. data/examples/simple_publisher_08.rb +0 -29
  44. data/examples/simple_topic_08.rb +0 -61
  45. data/ext/amqp-0.8.json +0 -616
  46. data/lib/bunny/channel09.rb +0 -39
  47. data/lib/bunny/client08.rb +0 -480
  48. data/lib/bunny/exchange08.rb +0 -177
  49. data/lib/bunny/queue08.rb +0 -403
  50. data/lib/bunny/subscription08.rb +0 -87
  51. data/lib/qrack/protocol/protocol09.rb +0 -135
  52. data/lib/qrack/protocol/spec08.rb +0 -828
  53. data/lib/qrack/qrack09.rb +0 -20
  54. data/lib/qrack/transport/buffer09.rb +0 -305
  55. data/lib/qrack/transport/frame09.rb +0 -97
  56. data/spec/spec_08/bunny_spec.rb +0 -75
  57. data/spec/spec_08/connection_spec.rb +0 -24
  58. data/spec/spec_08/exchange_spec.rb +0 -170
  59. data/spec/spec_08/queue_spec.rb +0 -239
@@ -1,177 +0,0 @@
1
- # encoding: utf-8
2
-
3
- module Bunny
4
-
5
- =begin rdoc
6
-
7
- === DESCRIPTION:
8
-
9
- *Exchanges* are the routing and distribution hub of AMQP. All messages that Bunny sends
10
- to an AMQP broker/server _have_ to pass through an exchange in order to be routed to a
11
- destination queue. The AMQP specification defines the types of exchange that you can create.
12
-
13
- At the time of writing there are four (4) types of exchange defined -
14
-
15
- * <tt>:direct</tt>
16
- * <tt>:fanout</tt>
17
- * <tt>:topic</tt>
18
- * <tt>:headers</tt>
19
-
20
- AMQP-compliant brokers/servers are required to provide default exchanges for the _direct_ and
21
- _fanout_ exchange types. All default exchanges are prefixed with <tt>'amq.'</tt>, for example -
22
-
23
- * <tt>amq.direct</tt>
24
- * <tt>amq.fanout</tt>
25
- * <tt>amq.topic</tt>
26
- * <tt>amq.match</tt> or <tt>amq.headers</tt>
27
-
28
- If you want more information about exchanges, please consult the documentation for your
29
- target broker/server or visit the {AMQP website}[http://www.amqp.org] to find the version of the
30
- specification that applies to your target broker/server.
31
-
32
- =end
33
-
34
- class Exchange
35
-
36
- attr_reader :client, :type, :name, :opts, :key
37
-
38
- def initialize(client, name, opts = {})
39
- # check connection to server
40
- raise Bunny::ConnectionError, 'Not connected to server' if client.status == :not_connected
41
-
42
- @client, @name, @opts = client, name, opts
43
-
44
- # set up the exchange type catering for default names
45
- if name =~ /^amq\.(.+)$/
46
- predeclared = true
47
- new_type = $1
48
- # handle 'amq.match' default
49
- new_type = 'headers' if new_type == 'match'
50
- @type = new_type.to_sym
51
- else
52
- @type = opts[:type] || :direct
53
- end
54
-
55
- @key = opts[:key]
56
- @client.exchanges[@name] ||= self
57
-
58
- # ignore the :nowait option if passed, otherwise program will hang waiting for a
59
- # response that will not be sent by the server
60
- opts.delete(:nowait)
61
-
62
- unless predeclared or name == ''
63
- opts = { :exchange => name, :type => type, :nowait => false }.merge(opts)
64
-
65
- client.send_frame(Qrack::Protocol::Exchange::Declare.new(opts))
66
-
67
- method = client.next_method
68
-
69
- client.check_response(method, Qrack::Protocol::Exchange::DeclareOk, "Error declaring exchange #{name}: type = #{type}")
70
- end
71
- end
72
-
73
- =begin rdoc
74
-
75
- === DESCRIPTION:
76
-
77
- Requests that an exchange is deleted from broker/server. Removes reference from exchanges
78
- if successful. If an error occurs raises _Bunny_::_ProtocolError_.
79
-
80
- ==== Options:
81
-
82
- * <tt>:if_unused => true or false (_default_)</tt> - If set to _true_, the server will only
83
- delete the exchange if it has no queue bindings. If the exchange has queue bindings the
84
- server does not delete it but raises a channel exception instead.
85
- * <tt>:nowait => true or false (_default_)</tt> - Ignored by Bunny, always _false_.
86
-
87
- ==== Returns:
88
-
89
- <tt>:delete_ok</tt> if successful
90
- =end
91
-
92
- def delete(opts = {})
93
- # ignore the :nowait option if passed, otherwise program will hang waiting for a
94
- # response that will not be sent by the server
95
- opts.delete(:nowait)
96
-
97
- client.send_frame(Qrack::Protocol::Exchange::Delete.new({ :exchange => name, :nowait => false }.merge(opts)))
98
-
99
- method = client.next_method
100
-
101
- client.check_response(method, Qrack::Protocol::Exchange::DeleteOk, "Error deleting exchange #{name}")
102
-
103
- client.exchanges.delete(name)
104
-
105
- # return confirmation
106
- :delete_ok
107
- end
108
-
109
- =begin rdoc
110
-
111
- === DESCRIPTION:
112
-
113
- Publishes a message to a specific exchange. The message will be routed to queues as defined
114
- by the exchange configuration and distributed to any active consumers when the transaction,
115
- if any, is committed.
116
-
117
- ==== OPTIONS:
118
-
119
- * <tt>:key => 'routing_key'</tt> - Specifies the routing key for the message. The routing key is
120
- used for routing messages depending on the exchange configuration.
121
- * <tt>:content_type => 'content/type'</tt> - Specifies the content type to use for the message.
122
- * <tt>:mandatory => true or false (_default_)</tt> - Tells the server how to react if the message
123
- cannot be routed to a queue. If set to _true_, the server will return an unroutable message
124
- with a Return method. If this flag is zero, the server silently drops the message.
125
- * <tt>:immediate => true or false (_default_)</tt> - Tells the server how to react if the message
126
- cannot be routed to a queue consumer immediately. If set to _true_, the server will return an
127
- undeliverable message with a Return method. If set to _false_, the server will queue the message,
128
- but with no guarantee that it will ever be consumed.
129
- * <tt>:persistent => true or false (_default_)</tt> - Tells the server whether to persist the message
130
- If set to _true_, the message will be persisted to disk and not lost if the server restarts.
131
- If set to _false_, the message will not be persisted across server restart. Setting to _true_
132
- incurs a performance penalty as there is an extra cost associated with disk access.
133
-
134
- ==== RETURNS:
135
-
136
- nil
137
-
138
- =end
139
-
140
- def publish(data, opts = {})
141
- opts = opts.dup
142
- out = []
143
-
144
- # Set up options
145
- routing_key = opts.delete(:key) || key
146
- mandatory = opts.delete(:mandatory)
147
- immediate = opts.delete(:immediate)
148
- delivery_mode = opts.delete(:persistent) ? 2 : 1
149
- content_type = opts.delete(:content_type) || 'application/octet-stream'
150
-
151
- out << Qrack::Protocol::Basic::Publish.new({ :exchange => name,
152
- :routing_key => routing_key,
153
- :mandatory => mandatory,
154
- :immediate => immediate })
155
- data = data.to_s
156
- out << Qrack::Protocol::Header.new(
157
- Qrack::Protocol::Basic,
158
- data.bytesize, {
159
- :content_type => content_type,
160
- :delivery_mode => delivery_mode,
161
- :priority => 0
162
- }.merge(opts)
163
- )
164
-
165
- limit = @client.frame_max - 8
166
- i = 0
167
- while i < data.bytesize
168
- out << Qrack::Transport::Body.new(data.byteslice(i, limit))
169
- i += limit
170
- end
171
-
172
- client.send_frame(*out)
173
- end
174
-
175
- end
176
-
177
- end
@@ -1,403 +0,0 @@
1
- # encoding: utf-8
2
-
3
- module Bunny
4
-
5
- =begin rdoc
6
-
7
- === DESCRIPTION:
8
-
9
- Queues store and forward messages. Queues can be configured in the server or created at runtime.
10
- Queues must be attached to at least one exchange in order to receive messages from publishers.
11
-
12
- =end
13
-
14
- class Queue < Qrack::Queue
15
-
16
- def initialize(client, name, opts = {})
17
- # check connection to server
18
- raise Bunny::ConnectionError, 'Not connected to server' if client.status == :not_connected
19
-
20
- @client = client
21
- @opts = opts
22
- @delivery_tag = nil
23
- @subscription = nil
24
-
25
- # Queues without a given name are named by the server and are generally
26
- # bound to the process that created them.
27
- if !name
28
- opts = {
29
- :passive => false,
30
- :durable => false,
31
- :exclusive => true,
32
- :auto_delete => true
33
- }.merge(opts)
34
- end
35
-
36
- # ignore the :nowait option if passed, otherwise program will hang waiting for a
37
- # response that will not be sent by the server
38
- opts.delete(:nowait)
39
-
40
- opts = { :queue => name || '', :nowait => false }.merge(opts)
41
-
42
- client.send_frame(Qrack::Protocol::Queue::Declare.new(opts))
43
-
44
- method = client.next_method
45
-
46
- client.check_response(method, Qrack::Protocol::Queue::DeclareOk, "Error declaring queue #{name}")
47
-
48
- @name = method.queue
49
- client.queues[@name] = self
50
- end
51
-
52
- # @return [Bunny::Consumer] Default consumer associated with this queue (if any), or nil
53
- # @note Default consumer is the one registered with the convenience {Bunny::Queue#subscribe} method. It has no special properties of any kind.
54
- # @see Queue#subscribe
55
- # @see Bunny::Consumer
56
- # @api public
57
- def default_consumer
58
- @default_consumer
59
- end
60
-
61
-
62
- # @return [Class]
63
- # @private
64
- def self.consumer_class
65
- # Bunny::Consumer
66
- Bunny::Subscription
67
- end # self.consumer_class
68
-
69
- =begin rdoc
70
-
71
- === DESCRIPTION:
72
-
73
- Acknowledges one or more messages delivered via the _Deliver_ or _Get_-_Ok_ methods. The client can
74
- ask to confirm a single message or a set of messages up to and including a specific message.
75
-
76
- ==== OPTIONS:
77
-
78
- * <tt>:delivery_tag</tt>
79
- * <tt>:multiple => true or false (_default_)</tt> - If set to _true_, the delivery tag is treated
80
- as "up to and including", so that the client can acknowledge multiple messages with a single
81
- method. If set to _false_, the delivery tag refers to a single message. If the multiple field
82
- is _true_, and the delivery tag is zero, tells the server to acknowledge all outstanding messages.
83
-
84
- =end
85
-
86
- def ack(opts={})
87
- # Set delivery tag
88
- if delivery_tag.nil? and opts[:delivery_tag].nil?
89
- raise Bunny::AcknowledgementError, "No delivery tag received"
90
- else
91
- self.delivery_tag = opts[:delivery_tag] if delivery_tag.nil?
92
- end
93
-
94
- opts = {:delivery_tag => delivery_tag, :multiple => false}.merge(opts)
95
-
96
- client.send_frame(Qrack::Protocol::Basic::Ack.new(opts))
97
-
98
- # reset delivery tag
99
- self.delivery_tag = nil
100
- end
101
-
102
- =begin rdoc
103
-
104
- === DESCRIPTION:
105
-
106
- Binds a queue to an exchange. Until a queue is bound it will not receive any messages. Queues are
107
- bound to the direct exchange '' by default. If error occurs, a _Bunny_::_ProtocolError_ is raised.
108
-
109
- * <tt>:key => 'routing key'* <tt>:key => 'routing_key'</tt> - Specifies the routing key for
110
- the binding. The routing key is used for routing messages depending on the exchange configuration.
111
- * <tt>:nowait => true or false (_default_)</tt> - Ignored by Bunny, always _false_.
112
-
113
- ==== RETURNS:
114
-
115
- <tt>:bind_ok</tt> if successful.
116
-
117
- =end
118
-
119
- def bind(exchange, opts = {})
120
- exchange = exchange.respond_to?(:name) ? exchange.name : exchange
121
-
122
- # ignore the :nowait option if passed, otherwise program will hang waiting for a
123
- # response that will not be sent by the server
124
- opts.delete(:nowait)
125
-
126
- opts = {
127
- :queue => name,
128
- :exchange => exchange,
129
- :routing_key => opts.delete(:key),
130
- :nowait => false
131
- }.merge(opts)
132
-
133
- client.send_frame(Qrack::Protocol::Queue::Bind.new(opts))
134
-
135
- method = client.next_method
136
-
137
- client.check_response(method, Qrack::Protocol::Queue::BindOk, "Error binding queue: #{name} to exchange: #{exchange}")
138
-
139
- # return message
140
- :bind_ok
141
- end
142
-
143
- =begin rdoc
144
-
145
- === DESCRIPTION:
146
-
147
- Requests that a queue is deleted from broker/server. When a queue is deleted any pending messages
148
- are sent to a dead-letter queue if this is defined in the server configuration. Removes reference
149
- from queues if successful. If an error occurs raises _Bunny_::_ProtocolError_.
150
-
151
- ==== Options:
152
-
153
- * <tt>:if_unused => true or false (_default_)</tt> - If set to _true_, the server will only
154
- delete the queue if it has no consumers. If the queue has consumers the server does not
155
- delete it but raises a channel exception instead.
156
- * <tt>:if_empty => true or false (_default_)</tt> - If set to _true_, the server will only
157
- delete the queue if it has no messages. If the queue is not empty the server raises a channel
158
- exception.
159
- * <tt>:nowait => true or false (_default_)</tt> - Ignored by Bunny, always _false_.
160
-
161
- ==== Returns:
162
-
163
- <tt>:delete_ok</tt> if successful
164
- =end
165
-
166
- def delete(opts = {})
167
- # ignore the :nowait option if passed, otherwise program will hang waiting for a
168
- # response that will not be sent by the server
169
- opts.delete(:nowait)
170
-
171
- opts = { :queue => name, :nowait => false }.merge(opts)
172
-
173
- client.send_frame(Qrack::Protocol::Queue::Delete.new(opts))
174
-
175
- method = client.next_method
176
-
177
- client.check_response(method, Qrack::Protocol::Queue::DeleteOk, "Error deleting queue #{name}")
178
-
179
- client.queues.delete(name)
180
-
181
- # return confirmation
182
- :delete_ok
183
- end
184
-
185
- =begin rdoc
186
-
187
- === DESCRIPTION:
188
-
189
- Gets a message from a queue in a synchronous way. If error occurs, raises _Bunny_::_ProtocolError_.
190
-
191
- ==== OPTIONS:
192
-
193
- * <tt>:ack => false (_default_) or true</tt> - If set to _false_, the server does not expect an
194
- acknowledgement message from the client. If set to _true_, the server expects an acknowledgement
195
- message from the client and will re-queue the message if it does not receive one within a time specified
196
- by the server.
197
-
198
- ==== RETURNS:
199
-
200
- Hash <tt>{:header, :payload, :delivery_details}</tt>. <tt>:delivery_details</tt> is
201
- a hash <tt>{:consumer_tag, :delivery_tag, :redelivered, :exchange, :routing_key}</tt>.
202
-
203
- If the queue is empty the returned hash will contain the values -
204
-
205
- :header => nil
206
- :payload => :queue_empty
207
- :delivery_details => nil
208
-
209
- N.B. If a block is provided then the hash will be passed into the block and the return value
210
- will be nil.
211
-
212
- =end
213
-
214
- def pop(opts = {}, &blk)
215
-
216
- # do we want to have to provide an acknowledgement?
217
- ack = opts.delete(:ack)
218
-
219
- opts = {
220
- :queue => name,
221
- :consumer_tag => name,
222
- :no_ack => !ack,
223
- :nowait => true
224
- }.merge(opts)
225
-
226
- client.send_frame(Qrack::Protocol::Basic::Get.new(opts))
227
-
228
- method = client.next_method
229
-
230
- if method.is_a?(Qrack::Protocol::Basic::GetEmpty) then
231
- queue_empty = true
232
- elsif !method.is_a?(Qrack::Protocol::Basic::GetOk)
233
- raise Bunny::ProtocolError, "Error getting message from queue #{name}"
234
- end
235
-
236
- if !queue_empty
237
- # get delivery tag to use for acknowledge
238
- self.delivery_tag = method.delivery_tag if ack
239
-
240
- header = client.next_payload
241
-
242
- # If maximum frame size is smaller than message payload body then message
243
- # will have a message header and several message bodies
244
- msg = ''
245
- while msg.length < header.size
246
- msg << client.next_payload
247
- end
248
-
249
- msg_hash = {:header => header, :payload => msg, :delivery_details => method.arguments}
250
-
251
- else
252
- msg_hash = {:header => nil, :payload => :queue_empty, :delivery_details => nil}
253
- end
254
-
255
- # Pass message hash to block or return message hash
256
- blk ? blk.call(msg_hash) : msg_hash
257
- end
258
-
259
- =begin rdoc
260
-
261
- === DESCRIPTION:
262
-
263
- Removes all messages from a queue. It does not cancel consumers. Purged messages are deleted
264
- without any formal "undo" mechanism. If an error occurs raises _Bunny_::_ProtocolError_.
265
-
266
- ==== Options:
267
-
268
- * <tt>:nowait => true or false (_default_)</tt> - Ignored by Bunny, always _false_.
269
-
270
- ==== Returns:
271
-
272
- <tt>:purge_ok</tt> if successful
273
- =end
274
-
275
- def purge(opts = {})
276
- # ignore the :nowait option if passed, otherwise program will hang waiting for a
277
- # response that will not be sent by the server
278
- opts.delete(:nowait)
279
-
280
- opts = { :queue => name, :nowait => false }.merge(opts)
281
-
282
- client.send_frame(Qrack::Protocol::Queue::Purge.new(opts))
283
-
284
- method = client.next_method
285
-
286
- client.check_response(method, Qrack::Protocol::Queue::PurgeOk, "Error purging queue #{name}")
287
-
288
- # return confirmation
289
- :purge_ok
290
- end
291
-
292
- =begin rdoc
293
-
294
- === DESCRIPTION:
295
-
296
- Returns hash {:message_count, :consumer_count}.
297
-
298
- =end
299
-
300
- def status
301
- client.send_frame(Qrack::Protocol::Queue::Declare.new(:queue => name, :passive => true))
302
- method = client.next_method
303
- {:message_count => method.message_count, :consumer_count => method.consumer_count}
304
- end
305
-
306
- def subscribe(opts = {}, &blk)
307
- raise RuntimeError.new("This queue already has default consumer. Please instantiate Bunny::Consumer directly and call its #consume method to register additional consumers.") if @default_consumer && ! opts[:consumer_tag]
308
-
309
- # Create a subscription.
310
- @default_consumer = self.class.consumer_class.new(client, self, opts)
311
- @default_consumer.consume(&blk)
312
- end
313
-
314
- =begin rdoc
315
-
316
- === DESCRIPTION:
317
-
318
- Cancels a consumer. This does not affect already delivered messages, but it does mean
319
- the server will not send any more messages for that consumer.
320
-
321
- ==== OPTIONS:
322
-
323
- * <tt>:consumer_tag => '_tag_'</tt> - Specifies the identifier for the consumer.
324
- * <tt>:nowait => true or false (_default_)</tt> - Ignored by Bunny, always _false_.
325
-
326
- ==== Returns:
327
-
328
- <tt>:unsubscribe_ok</tt> if successful
329
-
330
- =end
331
-
332
- def unsubscribe(opts = {})
333
- # Default consumer_tag from subscription if not passed in
334
- consumer_tag = @default_consumer ? @default_consumer.consumer_tag : opts[:consumer_tag]
335
-
336
- # Must have consumer tag to tell server what to unsubscribe
337
- raise Bunny::UnsubscribeError,
338
- "No consumer tag received" if !consumer_tag
339
-
340
- # Cancel consumer
341
- client.send_frame(Qrack::Protocol::Basic::Cancel.new(:consumer_tag => consumer_tag,:nowait => false))
342
-
343
- method = client.next_method
344
-
345
- client.check_response(method, Qrack::Protocol::Basic::CancelOk, "Error unsubscribing from queue #{name}")
346
-
347
- # Reset subscription
348
- @default_consumer = nil
349
-
350
- # Return confirmation
351
- :unsubscribe_ok
352
- end
353
-
354
- =begin rdoc
355
-
356
- === DESCRIPTION:
357
-
358
- Removes a queue binding from an exchange. If error occurs, a _Bunny_::_ProtocolError_ is raised.
359
-
360
- ==== OPTIONS:
361
- * <tt>:key => 'routing key'* <tt>:key => 'routing_key'</tt> - Specifies the routing key for
362
- the binding.
363
- * <tt>:nowait => true or false (_default_)</tt> - Ignored by Bunny, always _false_.
364
-
365
- ==== RETURNS:
366
-
367
- <tt>:unbind_ok</tt> if successful.
368
-
369
- =end
370
-
371
- def unbind(exchange, opts = {})
372
- exchange = exchange.respond_to?(:name) ? exchange.name : exchange
373
-
374
- # ignore the :nowait option if passed, otherwise program will hang waiting for a
375
- # response that will not be sent by the server
376
- opts.delete(:nowait)
377
-
378
- opts = {
379
- :queue => name,
380
- :exchange => exchange,
381
- :routing_key => opts.delete(:key),
382
- :nowait => false
383
- }.merge(opts)
384
-
385
- client.send_frame(Qrack::Protocol::Queue::Unbind.new(opts))
386
-
387
- method = client.next_method
388
-
389
- client.check_response(method, Qrack::Protocol::Queue::UnbindOk, "Error unbinding queue #{name}")
390
-
391
- # return message
392
- :unbind_ok
393
- end
394
-
395
- private
396
-
397
- def exchange
398
- @exchange ||= Bunny::Exchange.new(client, '', {:type => :direct, :key => name})
399
- end
400
-
401
- end
402
-
403
- end