Package pyamf :: Package remoting
[hide private]
[frames] | no frames]

Source Code for Package pyamf.remoting

  1  # Copyright (c) 2007-2009 The PyAMF Project. 
  2  # See LICENSE for details. 
  3   
  4  """ 
  5  AMF Remoting support. 
  6   
  7  A Remoting request from the client consists of a short preamble, headers, and 
  8  bodies. The preamble contains basic information about the nature of the 
  9  request. Headers can be used to request debugging information, send 
 10  authentication info, tag transactions, etc. Bodies contain actual Remoting 
 11  requests and responses. A single Remoting envelope can contain several 
 12  requests; Remoting supports batching out of the box. 
 13   
 14  Client headers and bodies need not be responded to in a one-to-one manner. That 
 15  is, a body or header may not require a response. Debug information is requested 
 16  by a header but sent back as a body object. The response index is essential for 
 17  the Flash Player to understand the response therefore. 
 18   
 19  @see: U{Remoting Envelope on OSFlash (external) 
 20  <http://osflash.org/documentation/amf/envelopes/remoting>} 
 21  @see: U{Remoting Headers on OSFlash (external) 
 22  <http://osflash.org/amf/envelopes/remoting/headers>} 
 23  @see: U{Remoting Debug Headers on OSFlash (external) 
 24  <http://osflash.org/documentation/amf/envelopes/remoting/debuginfo>} 
 25   
 26  @since: 0.1.0 
 27  """ 
 28   
 29  import copy 
 30   
 31  import pyamf 
 32  from pyamf import util 
 33   
 34  __all__ = ['Envelope', 'Request', 'Response', 'decode', 'encode'] 
 35   
 36  #: Succesful call. 
 37  STATUS_OK = 0 
 38  #: Reserved for runtime errors. 
 39  STATUS_ERROR = 1 
 40  #: Debug information. 
 41  STATUS_DEBUG = 2 
 42   
 43  #: List of available status response codes. 
 44  STATUS_CODES = { 
 45      STATUS_OK:    '/onResult', 
 46      STATUS_ERROR: '/onStatus', 
 47      STATUS_DEBUG: '/onDebugEvents' 
 48  } 
 49   
 50  #: AMF mimetype. 
 51  CONTENT_TYPE = 'application/x-amf' 
 52   
 53  ERROR_CALL_FAILED, = range(1) 
 54  ERROR_CODES = { 
 55      ERROR_CALL_FAILED: 'Server.Call.Failed' 
 56  } 
 57   
 58  APPEND_TO_GATEWAY_URL = 'AppendToGatewayUrl' 
 59  REPLACE_GATEWAY_URL = 'ReplaceGatewayUrl' 
 60  REQUEST_PERSISTENT_HEADER = 'RequestPersistentHeader' 
 61   
62 -class RemotingError(pyamf.BaseError):
63 """ 64 Generic remoting error class. 65 """
66
67 -class RemotingCallFailed(RemotingError):
68 """ 69 Raised if Server.Call.Failed received 70 """
71 72 pyamf.add_error_class(RemotingCallFailed, ERROR_CODES[ERROR_CALL_FAILED]) 73
74 -class HeaderCollection(dict):
75 """ 76 Collection of AMF message headers. 77 """
78 - def __init__(self, raw_headers={}):
79 self.required = [] 80 81 for (k, ig, v) in raw_headers: 82 self[k] = v 83 if ig: 84 self.required.append(k)
85
86 - def is_required(self, idx):
87 """ 88 @raise KeyError: Unknown header found. 89 """ 90 if not idx in self: 91 raise KeyError("Unknown header %s" % str(idx)) 92 93 return idx in self.required
94
95 - def set_required(self, idx, value=True):
96 """ 97 @raise KeyError: Unknown header found. 98 """ 99 if not idx in self: 100 raise KeyError("Unknown header %s" % str(idx)) 101 102 if not idx in self.required: 103 self.required.append(idx)
104
105 - def __len__(self):
106 return len(self.keys())
107
108 -class Envelope(object):
109 """ 110 I wrap an entire request, encapsulating headers and bodies. 111 112 There can be more than one request in a single transaction. 113 114 @ivar amfVersion: AMF encoding version. See L{pyamf.ENCODING_TYPES} 115 @type amfVersion: C{int} or C{None} 116 @ivar clientType: Client type. See L{ClientTypes<pyamf.ClientTypes>} 117 @type clientType: C{int} or C{None} 118 @ivar headers: AMF headers, a list of name, value pairs. Global to each 119 request. 120 @type headers: L{HeaderCollection} 121 @ivar bodies: A list of requests/response messages 122 @type bodies: L{list} containing tuples of the key of the request and 123 the instance of the L{Message} 124 """
125 - def __init__(self, amfVersion=None, clientType=None):
126 self.amfVersion = amfVersion 127 self.clientType = clientType 128 self.headers = HeaderCollection() 129 self.bodies = []
130
131 - def __repr__(self):
132 r = "<Envelope amfVersion=%s clientType=%s>\n" % ( 133 self.amfVersion, self.clientType) 134 135 for h in self.headers: 136 r += " " + repr(h) + "\n" 137 138 for request in iter(self): 139 r += " " + repr(request) + "\n" 140 141 r += "</Envelope>" 142 143 return r
144
145 - def __setitem__(self, name, value):
146 if not isinstance(value, Message): 147 raise TypeError("Message instance expected") 148 149 idx = 0 150 found = False 151 152 for body in self.bodies: 153 if name == body[0]: 154 self.bodies[idx] = (name, value) 155 found = True 156 157 idx = idx + 1 158 159 if not found: 160 self.bodies.append((name, value)) 161 162 value.envelope = self
163
164 - def __getitem__(self, name):
165 for body in self.bodies: 166 if name == body[0]: 167 return body[1] 168 169 raise KeyError("'%r'" % (name,))
170
171 - def __iter__(self):
172 for body in self.bodies: 173 yield body[0], body[1] 174 175 raise StopIteration
176
177 - def __len__(self):
178 return len(self.bodies)
179
180 - def iteritems(self):
181 for body in self.bodies: 182 yield body 183 184 raise StopIteration
185
186 - def keys(self):
187 return [body[0] for body in self.bodies]
188
189 - def items(self):
190 return self.bodies
191
192 - def __contains__(self, name):
193 for body in self.bodies: 194 if name == body[0]: 195 return True 196 197 return False
198
199 - def __eq__(self, other):
200 if isinstance(other, Envelope): 201 return self.amfVersion == other.amfVersion and \ 202 self.clientType == other.clientType and \ 203 self.headers == other.headers and \ 204 self.bodies == other.bodies 205 206 if hasattr(other, 'keys') and hasattr(other, 'items'): 207 keys, o_keys = self.keys(), other.keys() 208 209 if len(o_keys) != len(keys): 210 return False 211 212 for k in o_keys: 213 if k not in keys: 214 return False 215 216 keys.remove(k) 217 218 for k, v in other.items(): 219 if self[k] != v: 220 return False 221 222 return True
223
224 -class Message(object):
225 """ 226 I represent a singular request/response, containing a collection of 227 headers and one body of data. 228 229 I am used to iterate over all requests in the L{Envelope}. 230 231 @ivar envelope: The parent envelope of this AMF Message. 232 @type envelope: L{Envelope} 233 @ivar body: The body of the message. 234 @type body: C{mixed} 235 @ivar headers: The message headers. 236 @type headers: C{dict} 237 """
238 - def __init__(self, envelope, body):
239 self.envelope = envelope 240 self.body = body
241
242 - def _get_headers(self):
243 return self.envelope.headers
244 245 headers = property(_get_headers)
246
247 -class Request(Message):
248 """ 249 An AMF Request payload. 250 251 @ivar target: The target of the request 252 @type target: C{basestring} 253 """
254 - def __init__(self, target, body=[], envelope=None):
255 Message.__init__(self, envelope, body) 256 257 self.target = target
258
259 - def __repr__(self):
260 return "<%s target=%s>%s</%s>" % ( 261 type(self).__name__, self.target, self.body, type(self).__name__)
262
263 -class Response(Message):
264 """ 265 An AMF Response. 266 267 @ivar status: The status of the message. Default is L{STATUS_OK}. 268 @type status: Member of L{STATUS_CODES}. 269 """
270 - def __init__(self, body, status=STATUS_OK, envelope=None):
271 Message.__init__(self, envelope, body) 272 273 self.status = status
274
275 - def __repr__(self):
276 return "<%s status=%s>%s</%s>" % ( 277 type(self).__name__, _get_status(self.status), self.body, 278 type(self).__name__ 279 )
280
281 -class BaseFault(object):
282 """ 283 I represent a C{Fault} message (C{mx.rpc.Fault}). 284 285 @ivar level: The level of the fault. 286 @type level: C{str} 287 @ivar code: A simple code describing the fault. 288 @type code: C{str} 289 @ivar details: Any extra details of the fault. 290 @type details: C{str} 291 @ivar description: Text description of the fault. 292 @type description: C{str} 293 294 @see: U{mx.rpc.Fault on Livedocs (external) 295 <http://livedocs.adobe.com/flex/201/langref/mx/rpc/Fault.html>} 296 """ 297 level = None 298
299 - def __init__(self, *args, **kwargs):
300 self.code = kwargs.get('code', '') 301 self.type = kwargs.get('type', '') 302 self.details = kwargs.get('details', '') 303 self.description = kwargs.get('description', '')
304
305 - def __repr__(self):
306 x = '%s level=%s' % (self.__class__.__name__, self.level) 307 308 if self.code not in ('', None): 309 x += ' code=%s' % self.code 310 if self.type not in ('', None): 311 x += ' type=%s' % self.type 312 if self.description not in ('', None): 313 x += ' description=%s' % self.description 314 315 if self.details not in ('', None): 316 x += '\nTraceback:\n%s' % (self.details,) 317 318 return x
319
320 - def raiseException(self):
321 """ 322 Raises an exception based on the fault object. There is no traceback 323 available. 324 """ 325 raise get_exception_from_fault(self), self.description, None
326 327 pyamf.register_class(BaseFault, 328 attrs=['level', 'code', 'type', 'details', 'description']) 329
330 -class ErrorFault(BaseFault):
331 """ 332 I represent an error level fault. 333 """ 334 level = 'error'
335 336 pyamf.register_class(ErrorFault) 337
338 -def _read_header(stream, decoder, strict=False):
339 """ 340 Read AMF L{Message} header. 341 342 @type stream: L{BufferedByteStream<pyamf.util.BufferedByteStream>} 343 @param stream: AMF data. 344 @type decoder: L{amf0.Decoder<pyamf.amf0.Decoder>} 345 @param decoder: AMF decoder instance 346 @type strict: C{bool} 347 @param strict: Use strict decoding policy. Default is C{False}. 348 @raise DecodeError: The data that was read from the stream 349 does not match the header length. 350 351 @rtype: C{tuple} 352 @return: 353 - Name of the header. 354 - A C{bool} determining if understanding this header is 355 required. 356 - Value of the header. 357 """ 358 name_len = stream.read_ushort() 359 name = stream.read_utf8_string(name_len) 360 361 required = bool(stream.read_uchar()) 362 363 data_len = stream.read_ulong() 364 pos = stream.tell() 365 366 data = decoder.readElement() 367 368 if strict and pos + data_len != stream.tell(): 369 raise pyamf.DecodeError( 370 "Data read from stream does not match header length") 371 372 return (name, required, data)
373
374 -def _write_header(name, header, required, stream, encoder, strict=False):
375 """ 376 Write AMF message header. 377 378 @type name: C{str} 379 @param name: Name of the header. 380 @type header: 381 @param header: Raw header data. 382 @type required: L{bool} 383 @param required: Required header. 384 @type stream: L{BufferedByteStream<pyamf.util.BufferedByteStream>} 385 @param stream: AMF data. 386 @type encoder: L{amf0.Encoder<pyamf.amf0.Encoder>} 387 or L{amf3.Encoder<pyamf.amf3.Encoder>} 388 @param encoder: AMF encoder instance. 389 @type strict: C{bool} 390 @param strict: Use strict encoding policy. Default is C{False}. 391 """ 392 stream.write_ushort(len(name)) 393 stream.write_utf8_string(name) 394 395 stream.write_uchar(required) 396 write_pos = stream.tell() 397 398 stream.write_ulong(0) 399 old_pos = stream.tell() 400 encoder.writeElement(header) 401 new_pos = stream.tell() 402 403 if strict: 404 stream.seek(write_pos) 405 stream.write_ulong(new_pos - old_pos) 406 stream.seek(new_pos)
407
408 -def _read_body(stream, decoder, strict=False):
409 """ 410 Read AMF message body. 411 412 @type stream: L{BufferedByteStream<pyamf.util.BufferedByteStream>} 413 @param stream: AMF data. 414 @type decoder: L{amf0.Decoder<pyamf.amf0.Decoder>} 415 @param decoder: AMF decoder instance. 416 @type strict: C{bool} 417 @param strict: Use strict decoding policy. Default is C{False}. 418 @raise DecodeError: Data read from stream does not match body length. 419 420 @rtype: C{tuple} 421 @return: A C{tuple} containing: 422 - ID of the request 423 - L{Request} or L{Response} 424 """ 425 def _read_args(): 426 """ 427 @raise pyamf.DecodeError: Array type required for request body. 428 """ 429 if stream.read(1) != '\x0a': 430 raise pyamf.DecodeError("Array type required for request body") 431 432 x = stream.read_ulong() 433 434 return [decoder.readElement() for i in