OLD | NEW |
(Empty) | |
| 1 # Copyright (c) 2006,2007 Mitch Garnaat http://garnaat.org/ |
| 2 # |
| 3 # Permission is hereby granted, free of charge, to any person obtaining a |
| 4 # copy of this software and associated documentation files (the |
| 5 # "Software"), to deal in the Software without restriction, including |
| 6 # without limitation the rights to use, copy, modify, merge, publish, dis- |
| 7 # tribute, sublicense, and/or sell copies of the Software, and to permit |
| 8 # persons to whom the Software is furnished to do so, subject to the fol- |
| 9 # lowing conditions: |
| 10 # |
| 11 # The above copyright notice and this permission notice shall be included |
| 12 # in all copies or substantial portions of the Software. |
| 13 # |
| 14 # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS |
| 15 # OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABIL- |
| 16 # ITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT |
| 17 # SHALL THE AUTHOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, |
| 18 # WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, |
| 19 # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS |
| 20 # IN THE SOFTWARE. |
| 21 |
| 22 """ |
| 23 SQS Message |
| 24 |
| 25 A Message represents the data stored in an SQS queue. The rules for what is all
owed within an SQS |
| 26 Message are here: |
| 27 |
| 28 http://docs.amazonwebservices.com/AWSSimpleQueueService/2008-01-01/SQSDevelo
perGuide/Query_QuerySendMessage.html |
| 29 |
| 30 So, at it's simplest level a Message just needs to allow a developer to store by
tes in it and get the bytes |
| 31 back out. However, to allow messages to have richer semantics, the Message clas
s must support the |
| 32 following interfaces: |
| 33 |
| 34 The constructor for the Message class must accept a keyword parameter "queue" wh
ich is an instance of a |
| 35 boto Queue object and represents the queue that the message will be stored in.
The default value for |
| 36 this parameter is None. |
| 37 |
| 38 The constructor for the Message class must accept a keyword parameter "body" whi
ch represents the |
| 39 content or body of the message. The format of this parameter will depend on the
behavior of the |
| 40 particular Message subclass. For example, if the Message subclass provides dict
ionary-like behavior to the |
| 41 user the body passed to the constructor should be a dict-like object that can be
used to populate |
| 42 the initial state of the message. |
| 43 |
| 44 The Message class must provide an encode method that accepts a value of the same
type as the body |
| 45 parameter of the constructor and returns a string of characters that are able to
be stored in an |
| 46 SQS message body (see rules above). |
| 47 |
| 48 The Message class must provide a decode method that accepts a string of characte
rs that can be |
| 49 stored (and probably were stored!) in an SQS message and return an object of a t
ype that is consistent |
| 50 with the "body" parameter accepted on the class constructor. |
| 51 |
| 52 The Message class must provide a __len__ method that will return the size of the
encoded message |
| 53 that would be stored in SQS based on the current state of the Message object. |
| 54 |
| 55 The Message class must provide a get_body method that will return the body of th
e message in the |
| 56 same format accepted in the constructor of the class. |
| 57 |
| 58 The Message class must provide a set_body method that accepts a message body in
the same format |
| 59 accepted by the constructor of the class. This method should alter to the inter
nal state of the |
| 60 Message object to reflect the state represented in the message body parameter. |
| 61 |
| 62 The Message class must provide a get_body_encoded method that returns the curren
t body of the message |
| 63 in the format in which it would be stored in SQS. |
| 64 """ |
| 65 |
| 66 import base64 |
| 67 import StringIO |
| 68 from boto.sqs.attributes import Attributes |
| 69 from boto.exception import SQSDecodeError |
| 70 import boto |
| 71 |
| 72 class RawMessage: |
| 73 """ |
| 74 Base class for SQS messages. RawMessage does not encode the message |
| 75 in any way. Whatever you store in the body of the message is what |
| 76 will be written to SQS and whatever is returned from SQS is stored |
| 77 directly into the body of the message. |
| 78 """ |
| 79 |
| 80 def __init__(self, queue=None, body=''): |
| 81 self.queue = queue |
| 82 self.set_body(body) |
| 83 self.id = None |
| 84 self.receipt_handle = None |
| 85 self.md5 = None |
| 86 self.attributes = Attributes(self) |
| 87 |
| 88 def __len__(self): |
| 89 return len(self.encode(self._body)) |
| 90 |
| 91 def startElement(self, name, attrs, connection): |
| 92 if name == 'Attribute': |
| 93 return self.attributes |
| 94 return None |
| 95 |
| 96 def endElement(self, name, value, connection): |
| 97 if name == 'Body': |
| 98 self.set_body(self.decode(value)) |
| 99 elif name == 'MessageId': |
| 100 self.id = value |
| 101 elif name == 'ReceiptHandle': |
| 102 self.receipt_handle = value |
| 103 elif name == 'MD5OfMessageBody': |
| 104 self.md5 = value |
| 105 else: |
| 106 setattr(self, name, value) |
| 107 |
| 108 def encode(self, value): |
| 109 """Transform body object into serialized byte array format.""" |
| 110 return value |
| 111 |
| 112 def decode(self, value): |
| 113 """Transform seralized byte array into any object.""" |
| 114 return value |
| 115 |
| 116 def set_body(self, body): |
| 117 """Override the current body for this object, using decoded format.""" |
| 118 self._body = body |
| 119 |
| 120 def get_body(self): |
| 121 return self._body |
| 122 |
| 123 def get_body_encoded(self): |
| 124 """ |
| 125 This method is really a semi-private method used by the Queue.write |
| 126 method when writing the contents of the message to SQS. |
| 127 You probably shouldn't need to call this method in the normal course of
events. |
| 128 """ |
| 129 return self.encode(self.get_body()) |
| 130 |
| 131 def delete(self): |
| 132 if self.queue: |
| 133 return self.queue.delete_message(self) |
| 134 |
| 135 def change_visibility(self, visibility_timeout): |
| 136 if self.queue: |
| 137 self.queue.connection.change_message_visibility(self.queue, |
| 138 self.receipt_handle, |
| 139 visibility_timeout) |
| 140 |
| 141 class Message(RawMessage): |
| 142 """ |
| 143 The default Message class used for SQS queues. This class automatically |
| 144 encodes/decodes the message body using Base64 encoding to avoid any |
| 145 illegal characters in the message body. See: |
| 146 |
| 147 http://developer.amazonwebservices.com/connect/thread.jspa?messageID=49680%E
C%88%90 |
| 148 |
| 149 for details on why this is a good idea. The encode/decode is meant to |
| 150 be transparent to the end-user. |
| 151 """ |
| 152 |
| 153 def encode(self, value): |
| 154 return base64.b64encode(value) |
| 155 |
| 156 def decode(self, value): |
| 157 try: |
| 158 value = base64.b64decode(value) |
| 159 except: |
| 160 boto.log.warning('Unable to decode message') |
| 161 return value |
| 162 return value |
| 163 |
| 164 class MHMessage(Message): |
| 165 """ |
| 166 The MHMessage class provides a message that provides RFC821-like |
| 167 headers like this: |
| 168 |
| 169 HeaderName: HeaderValue |
| 170 |
| 171 The encoding/decoding of this is handled automatically and after |
| 172 the message body has been read, the message instance can be treated |
| 173 like a mapping object, i.e. m['HeaderName'] would return 'HeaderValue'. |
| 174 """ |
| 175 |
| 176 def __init__(self, queue=None, body=None, xml_attrs=None): |
| 177 if body == None or body == '': |
| 178 body = {} |
| 179 Message.__init__(self, queue, body) |
| 180 |
| 181 def decode(self, value): |
| 182 try: |
| 183 msg = {} |
| 184 fp = StringIO.StringIO(value) |
| 185 line = fp.readline() |
| 186 while line: |
| 187 delim = line.find(':') |
| 188 key = line[0:delim] |
| 189 value = line[delim+1:].strip() |
| 190 msg[key.strip()] = value.strip() |
| 191 line = fp.readline() |
| 192 except: |
| 193 raise SQSDecodeError('Unable to decode message', self) |
| 194 return msg |
| 195 |
| 196 def encode(self, value): |
| 197 s = '' |
| 198 for item in value.items(): |
| 199 s = s + '%s: %s\n' % (item[0], item[1]) |
| 200 return s |
| 201 |
| 202 def __getitem__(self, key): |
| 203 if key in self._body: |
| 204 return self._body[key] |
| 205 else: |
| 206 raise KeyError(key) |
| 207 |
| 208 def __setitem__(self, key, value): |
| 209 self._body[key] = value |
| 210 self.set_body(self._body) |
| 211 |
| 212 def keys(self): |
| 213 return self._body.keys() |
| 214 |
| 215 def values(self): |
| 216 return self._body.values() |
| 217 |
| 218 def items(self): |
| 219 return self._body.items() |
| 220 |
| 221 def has_key(self, key): |
| 222 return key in self._body |
| 223 |
| 224 def update(self, d): |
| 225 self._body.update(d) |
| 226 self.set_body(self._body) |
| 227 |
| 228 def get(self, key, default=None): |
| 229 return self._body.get(key, default) |
| 230 |
| 231 class EncodedMHMessage(MHMessage): |
| 232 """ |
| 233 The EncodedMHMessage class provides a message that provides RFC821-like |
| 234 headers like this: |
| 235 |
| 236 HeaderName: HeaderValue |
| 237 |
| 238 This variation encodes/decodes the body of the message in base64 automatical
ly. |
| 239 The message instance can be treated like a mapping object, |
| 240 i.e. m['HeaderName'] would return 'HeaderValue'. |
| 241 """ |
| 242 |
| 243 def decode(self, value): |
| 244 try: |
| 245 value = base64.b64decode(value) |
| 246 except: |
| 247 raise SQSDecodeError('Unable to decode message', self) |
| 248 return MHMessage.decode(self, value) |
| 249 |
| 250 def encode(self, value): |
| 251 value = MHMessage.encode(self, value) |
| 252 return base64.b64encode(value) |
| 253 |
OLD | NEW |