1
2
3
4 """
5 Remoting client implementation.
6
7 @since: 0.1.0
8 """
9
10 import httplib, urlparse
11
12 import pyamf
13 from pyamf import remoting, logging
14
15
16
17 DEFAULT_CLIENT_TYPE = pyamf.ClientTypes.Flash6
18
19
20 DEFAULT_USER_AGENT = 'PyAMF/%s' % '.'.join(map(lambda x: str(x),
21 pyamf.__version__))
22
23 HTTP_OK = 200
24
26 if args == (tuple(),):
27 return []
28 else:
29 return [x for x in args]
30
32 """
33 Serves as a proxy for calling a service method.
34
35 @ivar service: The parent service.
36 @type service: L{ServiceProxy}
37 @ivar name: The name of the method.
38 @type name: C{str} or C{None}
39
40 @see: L{ServiceProxy.__getattr__}
41 """
42
44 self.service = service
45 self.name = name
46
48 """
49 Inform the proxied service that this function has been called.
50 """
51
52 return self.service._call(self, *args)
53
55 """
56 Returns the full service name, including the method name if there is
57 one.
58 """
59 service_name = str(self.service)
60
61 if self.name is not None:
62 service_name = '%s.%s' % (service_name, self.name)
63
64 return service_name
65
67 """
68 Serves as a service object proxy for RPC calls. Generates
69 L{ServiceMethodProxy} objects for method calls.
70
71 @see: L{RequestWrapper} for more info.
72
73 @ivar _gw: The parent gateway
74 @type _gw: L{RemotingService}
75 @ivar _name: The name of the service
76 @type _name: C{str}
77 @ivar _auto_execute: If set to C{True}, when a service method is called,
78 the AMF request is immediately sent to the remote gateway and a
79 response is returned. If set to C{False}, a L{RequestWrapper} is
80 returned, waiting for the underlying gateway to fire the
81 L{execute<RemotingService.execute>} method.
82 """
83
84 - def __init__(self, gw, name, auto_execute=True):
85 self._gw = gw
86 self._name = name
87 self._auto_execute = auto_execute
88
91
92 - def _call(self, method_proxy, *args):
93 """
94 Executed when a L{ServiceMethodProxy} is called. Adds a request to the
95 underlying gateway. If C{_auto_execute} is set to C{True}, then the
96 request is immediately called on the remote gateway.
97 """
98 request = self._gw.addRequest(method_proxy, *args)
99
100 if self._auto_execute:
101 response = self._gw.execute_single(request)
102
103
104 return response.body
105
106 return request
107
109 """
110 This allows services to be 'called' without a method name.
111 """
112 return self._call(ServiceMethodProxy(self, None), *args)
113
115 """
116 Returns a string representation of the name of the service.
117 """
118 return self._name
119
121 """
122 A container object that wraps a service method request.
123
124 @ivar gw: The underlying gateway.
125 @type gw: L{RemotingService}
126 @ivar id: The id of the request.
127 @type id: C{str}
128 @ivar service: The service proxy.
129 @type service: L{ServiceProxy}
130 @ivar args: The args used to invoke the call.
131 @type args: C{list}
132 """
133
134 - def __init__(self, gw, id_, service, *args):
135 self.gw = gw
136 self.id = id_
137 self.service = service
138 self.args = args
139
142
153
155 """
156 Returns the result of the called remote request. If the request has not
157 yet been called, an exception is raised.
158 """
159 if not hasattr(self, '_result'):
160 raise AttributeError("'RequestWrapper' object has no attribute 'result'")
161
162 return self._result
163
166
167 result = property(_get_result, _set_result)
168
170 """
171 Acts as a client for AMF calls.
172
173 @ivar url: The url of the remote gateway. Accepts C{http} or C{https}
174 as valid schemes.
175 @type url: C{str}
176 @ivar requests: The list of pending requests to process.
177 @type requests: C{list}
178 @ivar request_number: A unique identifier for tracking the number of
179 requests.
180 @ivar amf_version: The AMF version to use.
181 See L{ENCODING_TYPES<pyamf.ENCODING_TYPES>}.
182 @type amf_version: C{int}
183 @ivar referer: The referer, or HTTP referer, identifies the address of the
184 client. Ignored by default.
185 @type referer: C{str}
186 @ivar client_type: The client type. See L{ClientTypes<pyamf.ClientTypes>}.
187 @type client_type: C{int}
188 @ivar user_agent: Contains information about the user agent (client)
189 originating the request. See L{DEFAULT_USER_AGENT}.
190 @type user_agent: C{str}
191 @ivar connection: The underlying connection to the remoting server.
192 @type connection: C{httplib.HTTPConnection} or C{httplib.HTTPSConnection}
193 @ivar headers: A list of persistent headers to send with each request.
194 @type headers: L{HeaderCollection<pyamf.remoting.HeaderCollection>}
195 @ivar http_headers: A dict of HTTP headers to apply to the underlying
196 HTTP connection.
197 @type http_headers: L{dict}
198 @ivar strict: Whether to use strict en/decoding or not.
199 @type strict: C{bool}
200 """
201
204 self.logger = logging.instance_logger(self)
205 self.original_url = url
206 self.requests = []
207 self.request_number = 1
208
209 self.user_agent = user_agent
210 self.referer = referer
211 self.amf_version = amf_version
212 self.client_type = client_type
213 self.headers = remoting.HeaderCollection()
214 self.http_headers = {}
215 self.strict = strict
216
217 self._setUrl(url)
218
220 """
221 @param url: Gateway URL.
222 @type url: C{str}
223 @raise ValueError: Unknown scheme.
224 """
225 self.url = urlparse.urlparse(url)
226 self._root_url = urlparse.urlunparse(['', ''] + list(self.url[2:]))
227
228 port = None
229 hostname = None
230
231 if hasattr(self.url, 'port'):
232 if self.url.port is not None:
233 port = self.url.port
234 else:
235 if ':' not in self.url[1]:
236 hostname = self.url[1]
237 port = None
238 else:
239 sp = self.url[1].split(':')
240
241 hostname, port = sp[0], sp[1]
242 port = int(port)
243
244 if hostname is None:
245 if hasattr(self.url, 'hostname'):
246 hostname = self.url.hostname
247
248 if self.url[0] == 'http':
249 if port is None:
250 port = httplib.HTTP_PORT
251
252 self.connection = httplib.HTTPConnection(hostname, port)
253 elif self.url[0] == 'https':
254 if port is None:
255 port = httplib.HTTPS_PORT
256
257 self.connection = httplib.HTTPSConnection(hostname, port)
258 else:
259 raise ValueError('Unknown scheme')
260
261 self.logger.info('Creating connection to %s://%s:%s' % (self.url[0],
262 hostname, port))
263 self.logger.debug('Referer: %s' % self.referer)
264 self.logger.debug('User-Agent: %s' % self.user_agent)
265
267 """
268 Sets a persistent header to send with each request.
269
270 @param name: Header name.
271 @type name: C{str}
272 @param must_understand: Default is C{False}.
273 @type must_understand: C{bool}
274 """
275 self.headers[name] = value
276 self.headers.set_required(name, must_understand)
277
279 """
280 Adds a header to the underlying HTTP connection.
281 """
282 self.http_headers[name] = value
283
285 """
286 Deletes an HTTP header.
287 """
288 del self.http_headers[name]
289
291 """
292 Returns a L{ServiceProxy} for the supplied name. Sets up an object that
293 can have method calls made to it that build the AMF requests.
294
295 @param auto_execute: Default is C{False}.
296 @type auto_execute: C{bool}
297 @raise TypeError: C{string} type required for C{name}.
298 @rtype: L{ServiceProxy}
299 """
300 if not isinstance(name, basestring):
301 raise TypeError('string type required')
302
303 return ServiceProxy(self, name, auto_execute)
304
306 """
307 Gets a request based on the id.
308
309 @raise LookupError: Request not found.
310 """
311 for request in self.requests:
312 if request.id == id_:
313 return request
314
315 raise LookupError("Request %s not found" % id_)
316
318 """
319 Adds a request to be sent to the remoting gateway.
320 """
321 wrapper = RequestWrapper(self, '/%d' % self.request_number,
322 service, *args)
323
324 self.request_number += 1
325 self.requests.append(wrapper)
326 self.logger.debug('Adding request %s%r' % (wrapper.service, args))
327
328 return wrapper
329
331 """
332 Removes a request from the pending request list.
333
334 @raise LookupError: Request not found.
335 """
336 if isinstance(service, RequestWrapper):
337 self.logger.debug('Removing request: %s' % (
338 self.requests[self.requests.index(service)]))
339 del self.requests[self.requests.index(service)]
340
341 return
342
343 for request in self.requests:
344 if request.service == service and request.args == args:
345 self.logger.debug('Removing request: %s' % (
346 self.requests[self.requests.index(request)]))
347 del self.requests[self.requests.index(request)]
348
349 return
350
351 raise LookupError("Request not found")
352
354 """
355 Builds an AMF request L{Envelope<pyamf.remoting.Envelope>} from a
356 supplied list of requests.
357
358 @param requests: List of requests
359 @type requests: C{list}
360 @rtype: L{Envelope<pyamf.remoting.Envelope>}
361 """
362 envelope = remoting.Envelope(self.amf_version, self.client_type)
363
364 self.logger.debug('AMF version: %s' % self.amf_version)
365 self.logger.debug('Client type: %s' % self.client_type)
366
367 for request in requests:
368 service = request.service
369 args = list(request.args)
370
371 envelope[request.id] = remoting.Request(str(service), args)
372
373 envelope.headers = self.headers
374
375 return envelope
376
378 headers = self.http_headers.copy()
379
380 headers.update({
381 'Content-Type': remoting.CONTENT_TYPE,
382 'User-Agent': self.user_agent
383 })
384
385 if self.referer is not None:
386 headers['Referer'] = self.referer
387
388 return headers
389
391 """
392 Builds, sends and handles the response to a single request, returning
393 the response.
394
395 @param request:
396 @type request:
397 @rtype:
398 """
399 self.logger.debug('Executing single request: %s' % request)
400 body = remoting.encode(self.getAMFRequest([request]), strict=self.strict)
401
402 self.logger.debug('Sending POST request to %s' % self._root_url)
403 self.connection.request('POST', self._root_url,
404 body.getvalue(),
405 self._get_execute_headers()
406 )
407
408 envelope = self._getResponse()
409 self.removeRequest(request)
410
411 return envelope[request.id]
412
414 """
415 Builds, sends and handles the responses to all requests listed in
416 C{self.requests}.
417 """
418 body = remoting.encode(self.getAMFRequest(self.requests), strict=self.strict)
419
420 self.logger.debug('Sending POST request to %s' % self._root_url)
421 self.connection.request('POST', self._root_url,
422 body.getvalue(),
423 self._get_execute_headers()
424 )
425
426 envelope = self._getResponse()
427
428 for response in envelope:
429 request = self.getRequest(response[0])
430 response = response[1]
431
432 request.setResponse(response)
433
434 self.removeRequest(request)
435
496
498 """
499 Sets authentication credentials for accessing the remote gateway.
500 """
501 self.addHeader('Credentials', dict(userid=unicode(username),
502 password=unicode(password)), True)
503