nebulous_stomp 2.0.2 → 3.0.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 (40) hide show
  1. checksums.yaml +4 -4
  2. data/.hgignore +2 -0
  3. data/.hgtags +1 -0
  4. data/README.md +225 -28
  5. data/feature/connection_example.yaml +24 -0
  6. data/feature/feature_test_spec.rb +247 -0
  7. data/feature/gimme.rb +91 -0
  8. data/lib/nebulous_stomp/listener.rb +107 -0
  9. data/lib/nebulous_stomp/message.rb +132 -265
  10. data/lib/nebulous_stomp/msg/body.rb +169 -0
  11. data/lib/nebulous_stomp/msg/header.rb +98 -0
  12. data/lib/nebulous_stomp/param.rb +16 -35
  13. data/lib/nebulous_stomp/redis_handler.rb +19 -29
  14. data/lib/nebulous_stomp/redis_handler_null.rb +12 -11
  15. data/lib/nebulous_stomp/redis_helper.rb +110 -0
  16. data/lib/nebulous_stomp/request.rb +212 -0
  17. data/lib/nebulous_stomp/stomp_handler.rb +30 -96
  18. data/lib/nebulous_stomp/stomp_handler_null.rb +8 -22
  19. data/lib/nebulous_stomp/target.rb +52 -0
  20. data/lib/nebulous_stomp/version.rb +1 -1
  21. data/lib/nebulous_stomp.rb +63 -50
  22. data/md/LICENSE.txt +20 -2
  23. data/md/nebulous_protocol.md +25 -18
  24. data/spec/listener_spec.rb +104 -0
  25. data/spec/message_spec.rb +227 -116
  26. data/spec/nebulous_spec.rb +44 -9
  27. data/spec/param_spec.rb +16 -33
  28. data/spec/redis_handler_null_spec.rb +0 -2
  29. data/spec/redis_handler_spec.rb +0 -2
  30. data/spec/redis_helper_spec.rb +107 -0
  31. data/spec/request_spec.rb +249 -0
  32. data/spec/stomp_handler_null_spec.rb +33 -34
  33. data/spec/stomp_handler_spec.rb +1 -74
  34. data/spec/target_spec.rb +97 -0
  35. metadata +20 -11
  36. data/lib/nebulous_stomp/nebrequest.rb +0 -259
  37. data/lib/nebulous_stomp/nebrequest_null.rb +0 -37
  38. data/spec/nebrequest_null_spec.rb +0 -219
  39. data/spec/nebrequest_spec.rb +0 -239
  40. data/spec/through_test_spec.rb +0 -80
@@ -0,0 +1,107 @@
1
+ require_relative 'target'
2
+ require_relative 'stomp_handler'
3
+
4
+
5
+ module NebulousStomp
6
+
7
+
8
+ ##
9
+ # Implements the Request-Response use case; consume Requests from an input queue and send
10
+ # Responses.
11
+ #
12
+ # listener = NebulousStomp::Listener.new(target)
13
+ # listener.consume_messages do |msg|
14
+ # begin
15
+ #
16
+ # case msg.verb
17
+ # when "ping"
18
+ # listener.reply *msg.respond_with_success
19
+ # when "time"
20
+ # listener.reply *msg.respond_with_protocol("timeresponce", Time.now)
21
+ # else
22
+ # listener.reply *msg.respond_with_error("Bad verb #{msg.verb}")
23
+ # end
24
+ #
25
+ # rescue
26
+ # listener.reply *msg.respond_with_error($!)
27
+ # end
28
+ # end
29
+ #
30
+ # loop { sleep 5 }
31
+ #
32
+ class Listener
33
+
34
+ # the queue name we are listening to
35
+ attr_reader :queue
36
+
37
+ # Insert a StompHandler object for test purposes
38
+ attr_writer :stomp_handler
39
+
40
+ ##
41
+ # When creating a Listener, pass the queue name to listen on.
42
+ #
43
+ # This can be something stringlike, or a Target (in which case we listen on the target's
44
+ # receiving queue).
45
+ #
46
+ def initialize(queue)
47
+ case
48
+ when queue.respond_to?(:receive_queue) then @queue = queue.receive_queue
49
+ when queue.respond_to?(:to_s) then @queue = queue.to_s
50
+ else fail ArgumentError, "Unknown object passed as queue"
51
+ end
52
+
53
+ NebulousStomp.logger.debug(__FILE__) { "Listening on #@queue" }
54
+ end
55
+
56
+ ##
57
+ # :call-seq:
58
+ # listener.consume_message(queue) {|msg| ... }
59
+ #
60
+ # Consume messages from the queue, yielding each.
61
+ #
62
+ # Note that we don't block for input here. Just as with the Stomp gem, and with StompHandler,
63
+ # you will need to take your own measures to ensure that your program does not end when it
64
+ # should be waiting for messages to arrive. The simplest solution is something like:
65
+ #
66
+ # loop { sleep 5 }
67
+ #
68
+ # Note also that this method runs inside a Thread, and so does the block you pass to it. By
69
+ # default threads do not report errors, so you must arrange to do that yourself.
70
+ #
71
+ def consume_messages
72
+ stomp_handler.listen(@queue) {|msg| yield msg }
73
+ end
74
+
75
+ ##
76
+ # Send a message in reply
77
+ #
78
+ # Queue must be a queue name; message must be a Message. The usual way to get these is from the
79
+ # Message class, for example by calling `message.respond_with_success`.
80
+ #
81
+ def reply(queue, message)
82
+ NebulousStomp.logger.debug(__FILE__) { "Replying to #{queue}" }
83
+ stomp_handler.send_message(queue, message)
84
+ self
85
+ end
86
+
87
+ ##
88
+ # Disconnect from Stomp.
89
+ #
90
+ # You probably don't need this; Stomp connections are quite short lived.
91
+ #
92
+ def quit
93
+ stomp_handler.stomp_disconnect
94
+ self
95
+ end
96
+
97
+ private
98
+
99
+ def stomp_handler
100
+ @stomp_handler ||= StompHandler.new(Param.get :stompConnectHash)
101
+ end
102
+
103
+ end
104
+
105
+
106
+ end
107
+
@@ -1,177 +1,156 @@
1
1
  require 'json'
2
+ require 'forwardable'
2
3
 
3
4
  require_relative 'stomp_handler'
5
+ require_relative 'msg/header'
6
+ require_relative 'msg/body'
4
7
 
5
8
 
6
9
  module NebulousStomp
7
10
 
8
11
 
9
12
  ##
10
- # A class to encapsulate a Nebulous message (which is built on top of a
11
- # STOMP message)
13
+ # A class to encapsulate a Nebulous message (which is built on top of a STOMP message)
12
14
  #
13
- # Note that this class REPLACES the old NebResponse class from 0.1.0. There
14
- # are differences:
15
- # * response.body -> message.stomp_body
16
- # * response.headers -> message.stomp_headers
17
- # * to_cache now returns a Hash, not a JSON string
15
+ # This class is entirely read-only, except for reply_id, which is set by Request when the
16
+ # message is sent.
17
+ #
18
+ # Much of this class is handled by two helper classes: the message headers are handled by
19
+ # Msg::Header and the message body by Msg::Body. You should look at them, too, for the full
20
+ # picture.
18
21
  #
19
22
  class Message
23
+ extend Forwardable
20
24
 
21
- # Might be nil: parsed from incoming messages; set by StompHandler on send
22
- attr_accessor :reply_id
25
+ def_delegators :@header, :stomp_headers, :reply_to, :in_reply_to, :reply_id, :content_type,
26
+ :reply_id=, :content_is_json?, :headers_for_stomp
23
27
 
24
- # Might be nil: only caught on messages that came directly from STOMP
25
- attr_reader :stomp_headers, :stomp_body
26
-
27
- # The content type of the message
28
- attr_reader :content_type
28
+ def_delegators :@body, :stomp_body, :body, :verb, :params, :desc,
29
+ :body_to_h, :protocol_json, :body_for_stomp
29
30
 
30
- # The Nebulous Protocol
31
- # Note that if you happen to pass an array as @params, it's actually
32
- # writable, which is not ideal.
33
- attr_reader :verb, :params, :desc
34
- attr_reader :reply_to, :in_reply_to
31
+ alias :parameters :params
32
+ alias :description :desc
35
33
 
36
34
 
37
35
  class << self
38
36
 
39
-
40
37
  ##
41
- # Build a Message from its components.
38
+ # :call-seq:
39
+ # Message.in_reply_to(message, args) -> Message
42
40
  #
43
- # Note that we assume a content type of JSON. But if we are building a
44
- # message by hand in Ruby, this is only reasonable.
41
+ # Build a Message that replies to an existing Message
45
42
  #
46
- # You must pass a verb; you can pass nil for all the other values.
43
+ # * msg - the Nebulous::Message that you are replying to
44
+ # * args - hash as per Message.new
47
45
  #
48
- # * replyTo - the queue to reply to if this is a Request
49
- # * inReplyTo - the reply ID if this is a Response
50
- # * verb, params, desc - The Protocol; the message to pass
46
+ # See also #respond, #respond_with_protocol, etc, etc. (which you are probably better off
47
+ # calling, to be honest).
51
48
  #
52
- def from_parts(replyTo, inReplyTo, verb, params, desc)
53
- raise ArgumentError, "missing parts" unless verb
54
-
55
- NebulousStomp.logger.debug(__FILE__){ "New message from parts" }
56
-
57
- p =
58
- case params
59
- when NilClass then nil
60
- when Array then params.dup
61
- else params.to_s
62
- end
63
-
64
- self.new( replyTo: replyTo.to_s,
65
- inReplyTo: inReplyTo.to_s,
66
- verb: verb.to_s,
67
- params: p,
68
- desc: desc.nil? ? nil : desc.to_s,
69
- contentType: 'application/json' )
70
-
71
- end
49
+ # Note that this method absolutely enforces the protocol with regard to the content type and
50
+ # (of course) the id of the message it is replying to; for example, even if you pass a
51
+ # different content type it will take the content type of the msg in preference. If you want
52
+ # something weirder, you will have to use Message.new.
53
+ #
54
+ def in_reply_to(msg, args)
55
+ fail ArgumentError, 'bad message' unless msg.kind_of? Message
56
+ fail ArgumentError, 'bad hash' unless args.kind_of? Hash
57
+ fail ArgumentError, 'message has no reply ID' unless msg.reply_id
72
58
 
59
+ NebulousStomp.logger.debug(__FILE__){ "New message in reply to #{msg}" }
73
60
 
74
- ##
75
- # Build a Message that replies to an existing Message
76
- #
77
- # * msg - the Nebulous::Message that you are replying to
78
- # * verb, params, desc - the new message Protocol
79
- #
80
- def in_reply_to(msg, verb, params=nil, desc=nil, replyTo=nil)
81
- raise ArgumentError, 'bad message' unless msg.kind_of? Message
82
-
83
- NebulousStomp.logger.debug(__FILE__){ "New message reply" }
84
-
85
- p =
86
- case params
87
- when NilClass then nil
88
- when Array then params.dup
89
- else params.to_s
90
- end
91
-
92
- m = msg.clone
93
- self.new( replyTo: replyTo.to_s,
94
- verb: verb.to_s,
95
- params: p,
96
- desc: desc.to_s,
97
- inReplyTo: m.reply_id,
98
- contentType: m.content_type )
61
+ hash = { inReplyTo: msg.reply_id,
62
+ contentType: msg.content_type }
99
63
 
64
+ self.new(args.merge hash)
100
65
  end
101
66
 
102
-
103
67
  ##
104
- # Build a Message from a (presumably incoming) STOMP message
68
+ # :call-seq:
69
+ # Message.from_stomp(stompmessage) -> Message
70
+ #
71
+ # Build a Message from a (presumably incoming) STOMP message; stompmessage must be a
72
+ # Stomp::Message.
105
73
  #
106
74
  def from_stomp(stompMsg)
107
- raise ArgumentError, 'not a stomp message' \
108
- unless stompMsg.kind_of? Stomp::Message
109
-
75
+ fail ArgumentError, 'not a stomp message' unless stompMsg.kind_of? Stomp::Message
110
76
  NebulousStomp.logger.debug(__FILE__){ "New message from STOMP" }
111
77
 
112
78
  s = Marshal.load( Marshal.dump(stompMsg) )
113
- obj = self.new( stompHeaders: s.headers,
114
- stompBody: s.body )
115
-
79
+ self.new(stompHeaders: s.headers, stompBody: s.body)
116
80
  end
117
81
 
118
-
119
82
  ##
120
- # To build a Nebmessage from a record in the Redis cache
83
+ # :call-seq:
84
+ # Message.from_cache(hash) -> Message
85
+ #
86
+ # To build a Nebmessage from a record in the Redis cache.
121
87
  #
122
- # See #to_cache for details of the hash that Redis should be storing
88
+ # See #to_h for details of the hash that Redis should be storing
123
89
  #
124
90
  def from_cache(json)
125
- raise ArgumentError, "That can't be JSON, it's not a string" \
126
- unless json.kind_of? String
127
-
91
+ fail ArgumentError, "That can't be JSON, it's not a string" unless json.kind_of? String
128
92
  NebulousStomp.logger.debug(__FILE__){ "New message from cache" }
129
93
 
130
- # Note that the message body at this point, for a JSON message, is
131
- # actually encoded to JSON *twice* - the second time was when the cache
132
- # hash as a whole was encoded for store in Redis. the JSON gem copes
133
- # with this so long as the whole string is not double-encoded.
94
+ # Note that the message body at this point, for a JSON message, is actually encoded to JSON
95
+ # *twice* - the second time was when the cache hash as a whole was encoded for store in
96
+ # Redis. the JSON gem copes with this so long as the whole string is not double-encoded.
134
97
  hash = JSON.parse(json, :symbolize_names => true)
135
- raise ArgumentError, 'Empty cache entry' if hash == {}
98
+ fail ArgumentError, 'Empty cache entry' if hash == {}
136
99
 
137
- # So now if the content type is JSON then the body is still JSON now.
138
- # It's only the rest of the cache hash that is a now a hash. Confused?
139
- # Now join us for this weeks' episode...
100
+ # So now if the content type is JSON then the body is still JSON now. It's only the rest of
101
+ # the cache hash that is a now a hash. Confused? Now join us for this weeks' episode...
140
102
  self.new( hash.clone )
141
103
 
142
104
  rescue JSON::ParserError => err
143
- raise ArgumentError, "Bad JSON: #{err.message}"
105
+ fail ArgumentError, "Bad JSON: #{err.message}"
144
106
  end
145
107
 
146
- end
147
- ##
148
-
149
-
150
- alias :parameters :params
151
- alias :description :desc
152
-
153
-
154
- def to_s
155
- "<Message[#{@reply_id}] to:#{@reply_to} r-to:#{@in_reply_to} " \
156
- << "v:#{@verb}>"
157
-
158
- end
108
+ end # class << self
159
109
 
160
110
 
161
111
  ##
162
- # true if the content type appears to be JSON-y
112
+ # Create a new message,
113
+ #
114
+ # There are three ways that a message could get created:
115
+ #
116
+ # 1. The user could create one directly.
117
+ #
118
+ # 2. A message could be created from an incoming STOMP message, in which case we should
119
+ # call Message.from_stomp to create it.
163
120
  #
164
- def content_is_json?
165
- @content_type =~ /json$/i ? true : false
121
+ # 3. A message could be created because we have retreived it from the Redis cache, in which
122
+ # case we should call Message.from_cache to create it (and, note, it will originally
123
+ # have been created in one of the other two ways...)
124
+ #
125
+ # The full list of useful hash keys is (as per Message.from_cache, #to_h):
126
+ #
127
+ # * :body -- the message body
128
+ # * :contentType -- Stomp content type string
129
+ # * :description / :desc -- part of The Protocol
130
+ # * :inReplyTo -- message ID that message is a response to
131
+ # * :parameters / :params -- part of The Protocol
132
+ # * :replyId -- the 'unique' ID of this Nebulous message
133
+ # * :replyTo -- for a request, the queue to be used for the response
134
+ # * :stompBody -- for a message from Stomp, the raw Stomp message body
135
+ # * :stompHeaders -- for a message from Stomp, the raw Stomp Headers string
136
+ # * :verb -- part of The Protocol
137
+ #
138
+ def initialize(hash)
139
+ @header = Msg::Header.new(hash)
140
+ @body = Msg::Body.new(content_is_json?, hash)
166
141
  end
167
142
 
143
+ def to_s
144
+ "<Message[#{reply_id}] to:#{reply_to} r-to:#{in_reply_to} v:#{verb}>"
145
+ end
168
146
 
169
147
  ##
170
- # Output a hash for serialization to the cache.
148
+ # Output a hash for serialization to the Redis cache.
171
149
  #
172
150
  # Currently this looks like:
173
151
  # { stompHeaders: @stomp_headers,
174
152
  # stompBody: @stomp_body,
153
+ # body: @body
175
154
  # verb: @verb,
176
155
  # params: @params,
177
156
  # desc: @desc,
@@ -180,186 +159,74 @@ module NebulousStomp
180
159
  # inReplyTo: @in_reply_to,
181
160
  # contentType: @content_type }
182
161
  #
183
- def to_cache
184
- { stompHeaders: @stomp_headers,
185
- stompBody: @stomp_body,
186
- verb: @verb,
187
- params: @params.kind_of?(Enumerable) ? @params.dup : @params,
188
- desc: @desc,
189
- replyTo: @reply_to,
190
- replyId: @reply_id,
191
- inReplyTo: @in_reply_to,
192
- contentType: @content_type }
193
-
194
- end
195
-
196
-
197
- ##
198
- # Return the hash of additional headers for the Stomp gem
162
+ # Note that if :stompBody is set then :body will be nil. This is to attempt to reduce
163
+ # duplication of what might be a rather large string.
199
164
  #
200
- def headers_for_stomp
201
- headers = { "content-type" => @content_type,
202
- "neb-reply-id" => @reply_id }
203
-
204
- headers["neb-reply-to"] = @reply_to if @reply_to
205
- headers["neb-in-reply-to"] = @in_reply_to if @in_reply_to
206
-
207
- headers
208
- end
209
-
210
-
211
- ##
212
- # Return a body object for the Stomp gem
213
- #
214
- def body_for_stomp
215
- hash = protocol_hash
216
-
217
- if content_is_json?
218
- hash.to_json
219
- else
220
- hash.map {|k,v| "#{k}: #{v}" }.join("\n") << "\n\n"
221
- end
222
- end
223
-
224
-
225
- ##
226
- # :call-seq:
227
- # message.body_to_h -> (Hash || nil)
228
- #
229
- # If the body is in JSON, return a hash.
230
- # If body is nil, or is not JSON, then return nil; don't raise an exception
231
- #
232
- def body_to_h
233
- x = StompHandler.body_to_hash(stomp_headers, stomp_body, @content_type)
234
- x == {} ? nil : x
165
+ def to_h
166
+ @header.to_h.merge @body.to_h
235
167
  end
236
168
 
169
+ alias :to_cache :to_h # old name
237
170
 
238
171
  ##
239
- # Return the message body formatted for The Protocol, in JSON.
240
- #
241
- # Raise an exception if the message body doesn't follow the protocol.
172
+ # :call-seq:
173
+ # message.respond_with_protocol(verb, params=[], desc="") -> queue, Message
242
174
  #
243
- # (We use this as the key for the Redis cache)
175
+ # Repond with a message using The Protocol.
244
176
  #
245
- def protocol_json
246
- raise NebulousError, "no protocol in this message!" unless @verb
247
- protocol_hash.to_json
177
+ def respond_with_protocol(verb, params=[], desc="")
178
+ fail NebulousError, "Don't know which queue to reply to" unless reply_to
179
+
180
+ hash = {verb: verb, params: params, desc: desc}
181
+ [ reply_to, Message.in_reply_to(self, hash) ]
248
182
  end
249
183
 
250
-
251
184
  ##
252
- # Make a new 'success verb' message in response to this one
253
- #
254
- # returns [queue, message] so you can just pass it to
255
- # stomphandler.send_message.
185
+ # :call-seq:
186
+ # message.respond_with_protocol(body) -> queue, Message
256
187
  #
257
- def respond_success
258
- raise NebulousError, "Don''t know who to reply to" unless @reply_to
188
+ # Repond with a message body (presumably a custom one that's non-Protocol).
189
+ #
190
+ def respond(body)
191
+ fail NebulousError, "Don't know which queue to reply to" unless reply_to
259
192
 
260
- NebulousStomp.logger.info(__FILE__) do
261
- "Responded to #{self} with 'success' verb"
262
- end
193
+ # Easy to do by mistake, pain in the arse to work out what's going on if you do
194
+ fail ArgumentError, "Respond takes a body, not a message" if body.is_a? Message
263
195
 
264
- [ @reply_to, Message.in_reply_to(self, 'success') ]
196
+ mess = Message.in_reply_to(self, body: body)
197
+ [ reply_to, mess ]
265
198
  end
266
199
 
267
-
268
200
  ##
269
- # Make a new 'error verb' message in response to this one
270
- #
271
- # err can be a string or an exception
201
+ # :call-seq:
202
+ # message.respond_with_success -> queue, Message
272
203
  #
273
- # returns [queue, message] so you can just pass it to
274
- # stomphandler.send_message.
204
+ # Make a new 'success verb' message in response to this one.
275
205
  #
276
- def respond_error(err,fields=[])
277
- raise NebulousError, "Don''t know who to reply to" unless @reply_to
278
-
279
- NebulousStomp.logger.info(__FILE__) do
280
- "Responded to #{self} with 'error': #{err}"
281
- end
282
-
283
- reply = Message.in_reply_to(self, 'error', fields, err.to_s)
284
-
285
- [ @reply_to, reply ]
206
+ def respond_with_success
207
+ fail NebulousError, "Don't know which queue to reply to" unless reply_to
208
+ respond_with_protocol('success')
286
209
  end
287
210
 
288
-
289
- private
290
-
211
+ alias :respond_success :respond_with_success # old name
291
212
 
292
213
  ##
293
- # Create a record -- note that you can't call this directly on the class;
294
- # you have to call Message.from_parts, .from_stomp, .from_cache or
295
- # .in_reply_to.
214
+ # :call-seq:
215
+ # message.respond_with_error(error, fields=[]) -> queue, Message
296
216
  #
297
- def initialize(hash)
298
- @stomp_headers = hash[:stompHeaders]
299
- @stomp_body = hash[:stompBody]
300
-
301
- @verb = hash[:verb]
302
- @params = hash[:params]
303
- @desc = hash[:desc]
304
- @reply_to = hash[:replyTo]
305
- @reply_id = hash[:replyId]
306
- @in_reply_to = hash[:inReplyTo]
307
- @content_type = hash[:contentType]
308
-
309
- fill_from_message
310
- end
311
-
312
-
313
- ##
314
- # Return The Protocol of the message as a hash.
217
+ # Make a new 'error verb' message in response to this one.
315
218
  #
316
- def protocol_hash
317
- h = {verb: @verb}
318
- h[:parameters] = @params unless @params.nil?
319
- h[:description] = @desc unless @desc.nil?
320
-
321
- h
322
- end
323
-
324
-
325
- ##
326
- # Fill all the other attributes, if you can, from @stomp_headers and
327
- # @stomp_body.
219
+ # Error can be a string or an exception. Fields is an arbitrary array of values, designed as a
220
+ # list of the parameter keys with problems; but of course you can use it for whatever you like.
328
221
  #
329
- def fill_from_message
330
-
331
- if @stomp_headers
332
- @content_type ||= @stomp_headers['content-type']
333
- @reply_id ||= @stomp_headers['neb-reply-id']
334
- @reply_to ||= @stomp_headers['neb-reply-to']
335
- @in_reply_to ||= @stomp_headers['neb-in-reply-to']
336
- end
337
-
338
- # decode the body, which should either be a JSON string or a series of
339
- # text fields. And use the body to set Protocol attributes.
340
- if @stomp_body && !@stomp_body.empty?
341
-
342
- raise "body is not a string, something is very wrong here!" \
343
- unless @stomp_body.kind_of? String
344
-
345
- h = StompHandler.body_to_hash( @stomp_headers,
346
- @stomp_body,
347
- @content_type )
348
-
349
- @verb ||= h["verb"]
350
- @params ||= h["parameters"] || h["params"]
351
- @desc ||= h["description"] || h["desc"]
352
-
353
- # Assume that if verb is missing, the other two are just part of a
354
- # response which has nothing to do with the protocol
355
- @params = @desc = nil unless @verb
356
- end
357
-
358
- self
222
+ def respond_with_error(err, fields=[])
223
+ fail NebulousError, "Don't know which queue to reply to" unless reply_to
224
+ respond_with_protocol('error', Array(fields).flatten.map(&:to_s), err.to_s)
359
225
  end
360
226
 
361
- end
362
- ##
227
+ alias :respond_error :respond_with_error # old name
228
+
229
+ end # Message
363
230
 
364
231
 
365
232
  end