| 1 | # Copyright (c) 2007-2009 The PyAMF Project. |
|---|
| 2 | # See LICENSE for details. |
|---|
| 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 | #: Default AMF client type. |
|---|
| 16 | #: @see: L{ClientTypes<pyamf.ClientTypes>} |
|---|
| 17 | DEFAULT_CLIENT_TYPE = pyamf.ClientTypes.Flash6 |
|---|
| 18 | |
|---|
| 19 | #: Default user agent is C{PyAMF/x.x.x}. |
|---|
| 20 | DEFAULT_USER_AGENT = 'PyAMF/%s' % '.'.join(map(lambda x: str(x), |
|---|
| 21 | pyamf.__version__)) |
|---|
| 22 | |
|---|
| 23 | HTTP_OK = 200 |
|---|
| 24 | |
|---|
| 25 | def convert_args(args): |
|---|
| 26 | if args == (tuple(),): |
|---|
| 27 | return [] |
|---|
| 28 | else: |
|---|
| 29 | return [x for x in args] |
|---|
| 30 | |
|---|
| 31 | class ServiceMethodProxy(object): |
|---|
| 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 | |
|---|
| 43 | def __init__(self, service, name): |
|---|
| 44 | self.service = service |
|---|
| 45 | self.name = name |
|---|
| 46 | |
|---|
| 47 | def __call__(self, *args): |
|---|
| 48 | """ |
|---|
| 49 | Inform the proxied service that this function has been called. |
|---|
| 50 | """ |
|---|
| 51 | |
|---|
| 52 | return self.service._call(self, *args) |
|---|
| 53 | |
|---|
| 54 | def __str__(self): |
|---|
| 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 | |
|---|
| 66 | class ServiceProxy(object): |
|---|
| 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 | |
|---|
| 89 | def __getattr__(self, name): |
|---|
| 90 | return ServiceMethodProxy(self, name) |
|---|
| 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 | # XXX nick: What to do about Fault objects here? |
|---|
| 104 | return response.body |
|---|
| 105 | |
|---|
| 106 | return request |
|---|
| 107 | |
|---|
| 108 | def __call__(self, *args): |
|---|
| 109 | """ |
|---|
| 110 | This allows services to be 'called' without a method name. |
|---|
| 111 | """ |
|---|
| 112 | return self._call(ServiceMethodProxy(self, None), *args) |
|---|
| 113 | |
|---|
| 114 | def __str__(self): |
|---|
| 115 | """ |
|---|
| 116 | Returns a string representation of the name of the service. |
|---|
| 117 | """ |
|---|
| 118 | return self._name |
|---|
| 119 | |
|---|
| 120 | class RequestWrapper(object): |
|---|
| 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 | |
|---|
| 140 | def __str__(self): |
|---|
| 141 | return str(self.id) |
|---|
| 142 | |
|---|
| 143 | def setResponse(self, response): |
|---|
| 144 | """ |
|---|
| 145 | A response has been received by the gateway |
|---|
| 146 | """ |
|---|
| 147 | # XXX nick: What to do about Fault objects here? |
|---|
| 148 | self.response = response |
|---|
| 149 | self.result = self.response.body |
|---|
| 150 | |
|---|
| 151 | if isinstance(self.result, remoting.ErrorFault): |
|---|
| 152 | self.result.raiseException() |
|---|
| 153 | |
|---|
| 154 | def _get_result(self): |
|---|
| 155 | """ |
|---|
| 156 | Returns the result of the called remote request. If the request has not |
|---|
| 157 | yet been called, an C{AttributeError} 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 | |
|---|
| 164 | def _set_result(self, result): |
|---|
| 165 | self._result = result |
|---|
| 166 | |
|---|
| 167 | result = property(_get_result, _set_result) |
|---|
| 168 | |
|---|
| 169 | class RemotingService(object): |
|---|
| 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 AMF en/decoding or not. |
|---|
| 199 | @type strict: C{bool} |
|---|
| 200 | """ |
|---|
| 201 | |
|---|
| 202 | def __init__(self, url, amf_version=pyamf.AMF0, client_type=DEFAULT_CLIENT_TYPE, |
|---|
| 203 | referer=None, user_agent=DEFAULT_USER_AGENT, strict=False): |
|---|
| 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 | |
|---|
| 219 | def _setUrl(self, url): |
|---|
| 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 | location = '%s://%s:%s%s' % (self.url[0], hostname, port, self.url[2]) |
|---|
| 262 | |
|---|
| 263 | self.logger.info('Connecting to %s' % location) |
|---|
| 264 | self.logger.debug('Referer: %s' % self.referer) |
|---|
| 265 | self.logger.debug('User-Agent: %s' % self.user_agent) |
|---|
| 266 | |
|---|
| 267 | def addHeader(self, name, value, must_understand=False): |
|---|
| 268 | """ |
|---|
| 269 | Sets a persistent header to send with each request. |
|---|
| 270 | |
|---|
| 271 | @param name: Header name. |
|---|
| 272 | @type name: C{str} |
|---|
| 273 | @param must_understand: Default is C{False}. |
|---|
| 274 | @type must_understand: C{bool} |
|---|
| 275 | """ |
|---|
| 276 | self.headers[name] = value |
|---|
| 277 | self.headers.set_required(name, must_understand) |
|---|
| 278 | |
|---|
| 279 | def addHTTPHeader(self, name, value): |
|---|
| 280 | """ |
|---|
| 281 | Adds a header to the underlying HTTP connection. |
|---|
| 282 | """ |
|---|
| 283 | self.http_headers[name] = value |
|---|
| 284 | |
|---|
| 285 | def removeHTTPHeader(self, name): |
|---|
| 286 | """ |
|---|
| 287 | Deletes an HTTP header. |
|---|
| 288 | """ |
|---|
| 289 | del self.http_headers[name] |
|---|
| 290 | |
|---|
| 291 | def getService(self, name, auto_execute=True): |
|---|
| 292 | """ |
|---|
| 293 | Returns a L{ServiceProxy} for the supplied name. Sets up an object that |
|---|
| 294 | can have method calls made to it that build the AMF requests. |
|---|
| 295 | |
|---|
| 296 | @param auto_execute: Default is C{False}. |
|---|
| 297 | @type auto_execute: C{bool} |
|---|
| 298 | @raise TypeError: C{string} type required for C{name}. |
|---|
| 299 | @rtype: L{ServiceProxy} |
|---|
| 300 | """ |
|---|
| 301 | if not isinstance(name, basestring): |
|---|
| 302 | raise TypeError('string type required') |
|---|
| 303 | |
|---|
| 304 | return ServiceProxy(self, name, auto_execute) |
|---|
| 305 | |
|---|
| 306 | def getRequest(self, id_): |
|---|
| 307 | """ |
|---|
| 308 | Gets a request based on the id. |
|---|
| 309 | |
|---|
| 310 | @raise LookupError: Request not found. |
|---|
| 311 | """ |
|---|
| 312 | for request in self.requests: |
|---|
| 313 | if request.id == id_: |
|---|
| 314 | return request |
|---|
| 315 | |
|---|
| 316 | raise LookupError("Request %s not found" % id_) |
|---|
| 317 | |
|---|
| 318 | def addRequest(self, service, *args): |
|---|
| 319 | """ |
|---|
| 320 | Adds a request to be sent to the remoting gateway. |
|---|
| 321 | """ |
|---|
| 322 | wrapper = RequestWrapper(self, '/%d' % self.request_number, |
|---|
| 323 | service, *args) |
|---|
| 324 | |
|---|
| 325 | self.request_number += 1 |
|---|
| 326 | self.requests.append(wrapper) |
|---|
| 327 | self.logger.debug('Adding request %s%r' % (wrapper.service, args)) |
|---|
| 328 | |
|---|
| 329 | return wrapper |
|---|
| 330 | |
|---|
| 331 | def removeRequest(self, service, *args): |
|---|
| 332 | """ |
|---|
| 333 | Removes a request from the pending request list. |
|---|
| 334 | |
|---|
| 335 | @raise LookupError: Request not found. |
|---|
| 336 | """ |
|---|
| 337 | if isinstance(service, RequestWrapper): |
|---|
| 338 | self.logger.debug('Removing request: %s' % ( |
|---|
| 339 | self.requests[self.requests.index(service)])) |
|---|
| 340 | del self.requests[self.requests.index(service)] |
|---|
| 341 | |
|---|
| 342 | return |
|---|
| 343 | |
|---|
| 344 | for request in self.requests: |
|---|
| 345 | if request.service == service and request.args == args: |
|---|
| 346 | self.logger.debug('Removing request: %s' % ( |
|---|
| 347 | self.requests[self.requests.index(request)])) |
|---|
| 348 | del self.requests[self.requests.index(request)] |
|---|
| 349 | |
|---|
| 350 | return |
|---|
| 351 | |
|---|
| 352 | raise LookupError("Request not found") |
|---|
| 353 | |
|---|
| 354 | def getAMFRequest(self, requests): |
|---|
| 355 | """ |
|---|
| 356 | Builds an AMF request L{Envelope<pyamf.remoting.Envelope>} from a |
|---|
| 357 | supplied list of requests. |
|---|
| 358 | |
|---|
| 359 | @param requests: List of requests |
|---|
| 360 | @type requests: C{list} |
|---|
| 361 | @rtype: L{Envelope<pyamf.remoting.Envelope>} |
|---|
| 362 | """ |
|---|
| 363 | envelope = remoting.Envelope(self.amf_version, self.client_type) |
|---|
| 364 | |
|---|
| 365 | self.logger.debug('AMF version: %s' % self.amf_version) |
|---|
| 366 | self.logger.debug('Client type: %s' % self.client_type) |
|---|
| 367 | |
|---|
| 368 | for request in requests: |
|---|
| 369 | service = request.service |
|---|
| 370 | args = list(request.args) |
|---|
| 371 | |
|---|
| 372 | envelope[request.id] = remoting.Request(str(service), args) |
|---|
| 373 | |
|---|
| 374 | envelope.headers = self.headers |
|---|
| 375 | |
|---|
| 376 | return envelope |
|---|
| 377 | |
|---|
| 378 | def _get_execute_headers(self): |
|---|
| 379 | headers = self.http_headers.copy() |
|---|
| 380 | |
|---|
| 381 | headers.update({ |
|---|
| 382 | 'Content-Type': remoting.CONTENT_TYPE, |
|---|
| 383 | 'User-Agent': self.user_agent |
|---|
| 384 | }) |
|---|
| 385 | |
|---|
| 386 | if self.referer is not None: |
|---|
| 387 | headers['Referer'] = self.referer |
|---|
| 388 | |
|---|
| 389 | return headers |
|---|
| 390 | |
|---|
| 391 | def execute_single(self, request): |
|---|
| 392 | """ |
|---|
| 393 | Builds, sends and handles the response to a single request, returning |
|---|
| 394 | the response. |
|---|
| 395 | |
|---|
| 396 | @param request: |
|---|
| 397 | @type request: |
|---|
| 398 | @rtype: |
|---|
| 399 | """ |
|---|
| 400 | self.logger.debug('Executing single request: %s' % request) |
|---|
| 401 | body = remoting.encode(self.getAMFRequest([request]), strict=self.strict) |
|---|
| 402 | |
|---|
| 403 | self.logger.debug('Sending POST request to %s' % self._root_url) |
|---|
| 404 | self.connection.request('POST', self._root_url, |
|---|
| 405 | body.getvalue(), |
|---|
| 406 | self._get_execute_headers() |
|---|
| 407 | ) |
|---|
| 408 | |
|---|
| 409 | envelope = self._getResponse() |
|---|
| 410 | self.removeRequest(request) |
|---|
| 411 | |
|---|
| 412 | return envelope[request.id] |
|---|
| 413 | |
|---|
| 414 | def execute(self): |
|---|
| 415 | """ |
|---|
| 416 | Builds, sends and handles the responses to all requests listed in |
|---|
| 417 | C{self.requests}. |
|---|
| 418 | """ |
|---|
| 419 | body = remoting.encode(self.getAMFRequest(self.requests), strict=self.strict) |
|---|
| 420 | |
|---|
| 421 | self.logger.debug('Sending POST request to %s' % self._root_url) |
|---|
| 422 | self.connection.request('POST', self._root_url, |
|---|
| 423 | body.getvalue(), |
|---|
| 424 | self._get_execute_headers() |
|---|
| 425 | ) |
|---|
| 426 | |
|---|
| 427 | envelope = self._getResponse() |
|---|
| 428 | |
|---|
| 429 | for response in envelope: |
|---|
| 430 | request = self.getRequest(response[0]) |
|---|
| 431 | response = response[1] |
|---|
| 432 | |
|---|
| 433 | request.setResponse(response) |
|---|
| 434 | |
|---|
| 435 | self.removeRequest(request) |
|---|
| 436 | |
|---|
| 437 | def _getResponse(self): |
|---|
| 438 | """ |
|---|
| 439 | Gets and handles the HTTP response from the remote gateway. |
|---|
| 440 | |
|---|
| 441 | @raise RemotingError: HTTP Gateway reported error status. |
|---|
| 442 | @raise RemotingError: Incorrect MIME type received. |
|---|
| 443 | """ |
|---|
| 444 | self.logger.debug('Waiting for response...') |
|---|
| 445 | http_response = self.connection.getresponse() |
|---|
| 446 | self.logger.debug('Got response status: %s' % http_response.status) |
|---|
| 447 | self.logger.debug('Content-Type: %s' % http_response.getheader('Content-Type')) |
|---|
| 448 | |
|---|
| 449 | if http_response.status != HTTP_OK: |
|---|
| 450 | self.logger.debug('Body: %s' % http_response.read()) |
|---|
| 451 | |
|---|
| 452 | if hasattr(httplib, 'responses'): |
|---|
| 453 | raise remoting.RemotingError("HTTP Gateway reported status %d %s" % ( |
|---|
| 454 | http_response.status, httplib.responses[http_response.status])) |
|---|
| 455 | |
|---|
| 456 | raise remoting.RemotingError("HTTP Gateway reported status %d" % ( |
|---|
| 457 | http_response.status,)) |
|---|
| 458 | |
|---|
| 459 | content_type = http_response.getheader('Content-Type') |
|---|
| 460 | |
|---|
| 461 | if content_type != remoting.CONTENT_TYPE: |
|---|
| 462 | self.logger.debug('Body = %s' % http_response.read()) |
|---|
| 463 | |
|---|
| 464 | raise remoting.RemotingError("Incorrect MIME type received. (got: %s)" % content_type) |
|---|
| 465 | |
|---|
| 466 | content_length = http_response.getheader('Content-Length') |
|---|
| 467 | bytes = '' |
|---|
| 468 | |
|---|
| 469 | self.logger.debug('Content-Length: %s' % content_length) |
|---|
| 470 | self.logger.debug('Server: %s' % http_response.getheader('Server')) |
|---|
| 471 | |
|---|
| 472 | if content_length is None: |
|---|
| 473 | bytes = http_response.read() |
|---|
| 474 | else: |
|---|
| 475 | try: |
|---|
| 476 | bytes = http_response.read(content_length) |
|---|
| 477 | except: |
|---|
| 478 | bytes = http_response.read() |
|---|
| 479 | |
|---|
| 480 | self.logger.debug('Read %d bytes for the response' % len(bytes)) |
|---|
| 481 | |
|---|
| 482 | response = remoting.decode(bytes, strict=self.strict) |
|---|
| 483 | self.logger.debug('Response: %s' % response) |
|---|
| 484 | |
|---|
| 485 | if remoting.APPEND_TO_GATEWAY_URL in response.headers: |
|---|
| 486 | self.original_url += response.headers[remoting.APPEND_TO_GATEWAY_URL] |
|---|
| 487 | |
|---|
| 488 | self._setUrl(self.original_url) |
|---|
| 489 | elif remoting.REPLACE_GATEWAY_URL in response.headers: |
|---|
| 490 | self.original_url = response.headers[remoting.REPLACE_GATEWAY_URL] |
|---|
| 491 | |
|---|
| 492 | self._setUrl(self.original_url) |
|---|
| 493 | |
|---|
| 494 | if remoting.REQUEST_PERSISTENT_HEADER in response.headers: |
|---|
| 495 | data = response.headers[remoting.REQUEST_PERSISTENT_HEADER] |
|---|
| 496 | |
|---|
| 497 | for k, v in data.iteritems(): |
|---|
| 498 | self.headers[k] = v |
|---|
| 499 | |
|---|
| 500 | http_response.close() |
|---|
| 501 | |
|---|
| 502 | return response |
|---|
| 503 | |
|---|
| 504 | def setCredentials(self, username, password): |
|---|
| 505 | """ |
|---|
| 506 | Sets authentication credentials for accessing the remote gateway. |
|---|
| 507 | """ |
|---|
| 508 | self.addHeader('Credentials', dict(userid=unicode(username), |
|---|
| 509 | password=unicode(password)), True) |
|---|