Package SPyRO :: Module SPyRO
[hide private]
[frames] | no frames]

Source Code for Module SPyRO.SPyRO

   1  #coding: iso-8859-1; 
   2  ############################################################################ 
   3  #                                                                          # 
   4  #    This file 'SPyRO.py'                                                  # 
   5  #    is part of 'SPyRO: Simple Python Remote Objects'                      # 
   6  #    Copyright (C) 2004-2005 by Eric Sadit Tellez Avila                    # 
   7  #    Copyright (C) 2005-2006 by Eric Sadit Tellez Avila                    # 
   8  #    Copyright (C) 2007 by Eric Sadit Tellez Avila                         # 
   9  #    Copyright (C) 2008 by Eric Sadit Tellez Avila                         # 
  10  #    sadit@lsc.fie.umich.mx or donsadit@gmail.com                          # 
  11  #                                                                          # 
  12  #    is part of 'Dweba: Distributed Web Application Framework (for Python)'# 
  13  #    Copyright (C) 2008      by Eric Sadit Tellez Avila                    # 
  14  #                                                                          # 
  15  #    This program is free software; you can redistribute it and#or modify  # 
  16  #    it under the terms of the GNU General Public License as published by  # 
  17  #    the Free Software Foundation; either version 2 of the License, or     # 
  18  #    (at your option) any later version.                                   # 
  19  #                                                                          # 
  20  #    This program is distributed in the hope that it will be useful,       # 
  21  #    but WITHOUT ANY WARRANTY; without even the implied warranty of        # 
  22  #    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         # 
  23  #    GNU General Public License for more details.                          # 
  24  #                                                                          # 
  25  #    You should have received a copy of the GNU General Public License     # 
  26  #    along with this program; if not, write to the                         # 
  27  #    Free Software Foundation, Inc.,                                       # 
  28  #    59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.             # 
  29  ############################################################################ 
  30   
  31  import thread 
  32  from threading import Lock 
  33  __thread_data__ = {} 
  34   
35 -def getrequest():
36 """ Returns the spyro's request dictionary that 37 rules the current thread or spyro request. If this thread is not 38 ruled by an spyro request, it will raise a KeyError exception""" 39 return __thread_data__[thread.get_ident()]
40
41 -def _setrequest(data):
42 __thread_data__[thread.get_ident()] = data
43
44 -def _delrequest():
45 del __thread_data__[thread.get_ident()]
46 47 import warn 48 warn = warn.Warn("SPyRO.SPyRO") 49 import os 50 import sys 51 import socket, SocketServer 52 import gzip 53 try: 54 from cStringIO import StringIO 55 except ImportError: 56 from StringIO import StringIO 57 58 from SocketServer import ThreadingTCPServer 59 import Queue 60 import httplib 61 import time 62 63 import shttp 64 import shttp.http as http 65 66 from shttp import uri 67 from shttp.uri import URI 68 import policies 69 # from policies import AuthError, AuthorizationRequest, AuthPasswordRequest 70 from policies import AuthError 71 # AuthPasswordRequest 72 from codepools import CodePool, ServerObjectPool, ModulePool 73 74 try: 75 import OpenSSL 76 import OpenSSL.SSL as SSL 77 except ImportError, err: 78 warn.debug("Unable to use HTTPS as transport layer ", err) 79 SSL = None 80 81 import args as Args 82 83 sleep_min_sec = 0.05 84 sleep_max_sec = 100.0 85 86 SendByReference = 'REF' 87 SendByValue = 'VAL' 88
89 -class RemoteException(Exception): pass
90 -class AttributeIsCallable(Exception): pass
91 92 import types 93 from types import DictType 94
95 -def _set_booleans():
96 BooleanType = types.IntType 97 __builtins__['True'] = 1 98 __builtins__['False'] = 0
99 100 try: 101 BooleanType = types.BooleanType 102 True 103 except: 104 warn.warn("An old version of python or jython was detected") 105 _set_booleans() 106 107 _HIBRID_SEND_VALUE_tuple = (types.StringType, 108 types.NoneType, 109 types.IntType, 110 BooleanType, 111 types.LongType, types.FloatType, 112 types.UnicodeType, 113 #types.ComplexType, 114 )
115 -def defaultoverture(httpreq, environment):
116 """ By default pickle is disabled, to enable you must overload 'overture' or 117 set to None 118 119 Any other prelude action must be done in this method. The overture is provided 120 to control the execution before start it. The authorities (from policies) are 121 checked when a protocol is up and loading. This method must be set to control 122 access and tune settings before any protocol and authority. 123 """ 124 q = environment['QUERY_STRING'].lower() 125 if 'pickle' in q: 126 raise "The pickle marshaler is not allowed on this SPyRO server"
127 128 __doc__ = """ 129 Simple Python Remote Objects 130 Implements Simple Remote Objects (attributes and methods). 131 132 Provides the complete suite: server and client services, and RemoteObject 133 wrappers to objects. 134 135 Implement new formats are quite simple. Just create a class or module with 136 the methods or functions loads and dumps and register your interface 137 with the |registerFormat| function. Thread safe operation is responsibility 138 of the new add-on. 139 140 141 Important Note: 142 Since SPyRO |Server| is multithreaded, but |CommunicationCliente| had synchronous 143 protocol, it will not be able to manage many request (every action over a 144 object is a request) on the same connection. It is necessary use a |Client| 145 object peer concurrent thread, in the best case, may be one or two are 146 necessary or use |TClient| (uses a pool of |Client|'s transparentely). 147 148 """ 149 150 import formats 151 ORBFormats = formats.ORBFormats() 152 ORBFormats.init() 153 154
155 -class TransportProtocolMixIn:
156 """ Common methods in the ProtocolHTTP* server and clients"""
157 - def _choose_format(self, format, default_format):
158 if default_format and not format: 159 format = default_format 160 if format == self.format: 161 return 162 self.format = format 163 self.broker = ORBFormats.getFormat(format)
164
165 - def _load_request(self, x):
166 # warn.debug('x'*30,'LOAD_REQUEST\n',x) 167 # restoring datatypes 168 # if type(x['auth']) is DictType: 169 # x['auth'] = AuthorizationRequest().setstate(x['auth']) 170 #return Request().setstate(x) 171 return x
172
173 - def _load_result(self, x):
174 # warn.debug('x'*3,' LOAD_RESULT\n',x) 175 return Response(**x)
176 177
178 -class ProtocolHTTPReader(TransportProtocolMixIn):
179 """ Protocol used by clients, it runs under HTTP transmition layer """
180 - def __init__(self, address, location, compress, usingssl=False):
181 """ Initialize a client protocol manager. 182 183 Using |address| as a tuple (host,port) and |location| 184 Set the remote object location to perform the remote operations. 185 In |location| the format of the broker is attached concatenating 186 a '?' and a name of the format to the normal location. 187 |compress| Use gzip compression in messages 188 |usingssl| If True, the connection will be done using https 189 190 For example, to request a remote server that is listening on 191 /path/SPyRO to use the registered XMLRPC format /path/SPyRO?XMLRPC""" 192 self.address = address 193 self.usingssl = usingssl 194 if not usingssl: 195 self.conn = httplib.HTTPConnection(address[0], address[1]) 196 else: 197 self.conn = httplib.HTTPSConnection(address[0], address[1]) 198 self.location = location 199 self.__closed = False 200 self.format = None 201 self._choose_format(uri.query_string(location), None) 202 self.compress = compress
203
204 - def __del__(self):
205 # if not self.__closed: self.close() 206 pass
207
208 - def close(self):
209 # self.__closed = True 210 self.conn.close()
211
212 - def __request(self, msg, contenttype = "application/xml"):
213 """ Send the message |msg|""" 214 # if self.compress: headers['Accept-Encoding'] = 'x-gzip' 215 # self.send_request(self.location, msg, headers=headers) 216 conn = self.conn 217 conn.putrequest('POST',self.location) 218 conn.putheader('Host', "%s:%s"%(self.address[0],self.address[1])) 219 conn.putheader('Content-Type', contenttype) 220 if self.compress: 221 conn.putheader('Accept-Encoding', 'x-gzip') 222 # warn.debug("\n\n============================> SEND COMPRESS") 223 # fout = StringIO() 224 # gz = gzip.GzipFile(fileobj=fout,mode="w") 225 # gz.write(msg) 226 # msg = fout.getvalue() 227 # gz.close() 228 # fout.close() 229 conn.putheader('Content-Length', len(msg)) 230 conn.endheaders() 231 conn.send(msg)
232
233 - def get_result(self):
234 """ Read a message and returns. It uses a specific format """ 235 res = self.conn.getresponse() 236 gzipheader = res.getheader("content-encoding") 237 if gzipheader and gzipheader.find("gzip") > 0: 238 f = gzip.GzipFile(fileobj=StringIO(res.read())) 239 msg = f.read() 240 f.close() 241 else: 242 msg = res.read() 243 x = self.broker.load_result(msg) 244 return self._load_result(x)
245
246 - def _fixauth(self, msg):
247 try: 248 msg['auth'] = msg['auth'].__dict__ 249 except: 250 pass
251
252 - def send_control(self, msg):
253 self._fixauth(msg) 254 self.__request(self.broker.dump_control(msg))
255
256 - def send_setAttr(self, msg):
257 """ send a setAttr request """ 258 self._fixauth(msg) 259 self.__request(self.broker.dump_setAttr(msg))
260
261 - def send_getAttr(self, msg):
262 """ send a getAttr request """ 263 self._fixauth(msg) 264 self.__request(self.broker.dump_getAttr(msg))
265
266 - def send_callMethod(self, msg):
267 """ send a callMethod request """ 268 self._fixauth(msg) 269 self.__request(self.broker.dump_callMethod(msg))
270
271 -class ProtocolHTTPShared(TransportProtocolMixIn):
272 """ An HTTP Server to manage SPyRO requests, attached to another HTTP server """ 273
274 - def __init__(self, rfile, wfile, env, default_format = "MARSHAL"):
275 """ Perform connection using the opened socket |reqsocket|""" 276 self.format = None 277 self.default_format = default_format 278 self.broker = None 279 self.__closed = False 280 self.rfile = rfile 281 self.wfile = wfile 282 self.env = env
283
284 - def get_request(self, format):
285 """ Read the request and return it as a Request object instance""" 286 self._choose_format(format, self.default_format) 287 try: 288 x = self.rfile.read(int(self.env['CONTENT_LENGTH'])) 289 except: 290 if self.env['REQUEST_METHOD'] == 'POST': 291 x = self.rfile.read() 292 else: 293 x = '' 294 x = self.broker.load_request(x) 295 return self._load_request(x)
296
297 - def send_data(self, res):
298 """ Common code between send_result and send_error """ 299 http.webmethodwrite(self, res, self.broker.contenttype())
300
301 - def send_result(self, msg):
302 """ Encode and send the message (msg is a Response object) """ 303 x = self.broker.dump_result(msg) 304 self.send_data(x)
305 306 # def send_error(self, errormsg, code=None): 307 # """ Send an error (errormsg is an error string) """ 308 # if code is None: code = Response.ErrorExceptionThrowed 309 # x = newResponse(code, str(errormsg), SendByValue) 310 # self.send_data( self.broker.dump_error(x) ) 311
312 - def close(self):
313 """ Close the server """ 314 if not self.__closed: 315 self.__closed = True
316
317 - def __del__(self):
318 if not self.__closed: self.close()
319 320 ############################################################################## 321 ############################################################################## 322 ##### The request class 323 ############################################################################## 324 ############################################################################## 325
326 -class Request:
327 """ Object to request to the remote object server 328 329 Values of the variables: 330 # Say bye 331 ReqBye = 'BYE' 332 # Request to Get Remote Attr 333 ReqGetAttr = 'GET' 334 # Set an attribute 335 ReqSetAttr = 'SET' 336 # Del an object 337 ReqDelObj = 'DEL' 338 # Request to Call a remote method 339 ReqCallMethod = 'CALL' 340 """ 341 # Say bye 342 ReqBye = 'BYE' 343 # Request to Get Remote Attr 344 ReqGetAttr = 'GET' 345 # Set an attribute 346 ReqSetAttr = 'SET' 347 # Del an object 348 ReqDelObj = 'DEL' 349 # Request to Call a remote method 350 ReqCallMethod = 'CALL'
351
352 -def newRequest(objname, attrname, reqtype, auth):
353 return {'objname' : objname, 'attrname' : attrname, 'reqtype' : reqtype, 'auth' : auth}
354
355 -def newRequestSet(objname, attrname, value, local_server, auth):
356 """ Creates a new Request for a |setattr| operation, to set the 357 attrname to value""" 358 req = newRequest(objname, attrname, Request.ReqSetAttr, auth) 359 req['value'] = value 360 req['local_server'] = local_server 361 return req
362
363 -def newRequestGet(objname, attrname, rettype, auth):
364 """ Creates a new Request for a |getattr| operation """ 365 req = newRequest(objname, attrname, Request.ReqGetAttr, auth) 366 req['rettype'] = rettype 367 return req
368
369 -def newRequestDel(objname, auth):
370 """ Creates a new Request for a DEL object operation """ 371 return newRequest(objname, None, Request.ReqDelObj, auth)
372
373 -def newRequestBye():
374 """ Creates a new Request for a BYE object operation """ 375 return newRequest(None, None, Request.ReqBye, None)
376
377 -def newRequestCall(objname, attrname, args, kwargs, rettype, local_server, 378 auth, extra):
379 """ A SPyRO request, call mode 380 |objname| Object name 381 |attrname| The methodname 382 |args| Positional arguments, tuple 383 |kwargs| Keyword arguments, dictionary 384 |rettype| The return type, SendByReference or SendByValue 385 |local_server| If Request to call a remote method and exists port we call 386 the method with the pass the arguments by reference the server receives the 387 name or id in the remote peer (the caller) and a port to connect. The IP 388 address is obtained by the socket 389 |auth| The authentication object 390 |extra| Additional info to append to message, for example routed calls 391 """ 392 req = newRequest(objname, attrname, Request.ReqCallMethod, auth) 393 req['args'] = args 394 req['kwargs'] = kwargs 395 req['rettype'] = rettype 396 req['local_server'] = local_server 397 if extra: 398 req['extra'] = extra 399 return req
400 401 ############################################################################## 402 ############################################################################## 403 ##### The response class set 404 ############################################################################## 405 ############################################################################## 406
407 -def ObjectWrapper_GuessType(xobject, xserver):
408 """ Function to wrap object guessing the more convenient type to send 409 (send by reference or by value) """ 410 if type(xobject) in _HIBRID_SEND_VALUE_tuple: 411 return ObjectWrapper_ByValue(xobject) 412 else: 413 return ObjectWrapper_ByReference(xobject, xserver)
414
415 -def ObjectWrapper_ByReference(xobject, xserver, xauthority = None):
416 """ Wrap objects to send objects to Reference """ 417 xobjname = '!' + str(id(xobject)) 418 xserver.setsend(xobjname, xobject, xauthority) 419 return (xobjname, SendByReference)
420
421 -def ObjectWrapper_ByValue(xobject):
422 """ Wrap objects to send objects to Reference """ 423 return (xobject, SendByValue)
424
425 -def ObjectWrapper_Unpack(objarg):
426 return objarg
427
428 -def ObjectWrapper_GetObject(objarg):
429 return objarg[0]
430
431 -def ObjectWrapper_GetType(objarg):
432 return objarg[1]
433
434 -def newResponse(errcode, retvalue, rettype):
435 """ Create and returns a server side response from the arguments """ 436 return {'errcode': errcode, 'retvalue' : retvalue, 'rettype': rettype}
437 438
439 -class Response:
440 """ Objects to send as response at requests. 441 The request codes are the following: 442 Successful = 0 443 # Unknown Object, no object registered to share in server, retvalue is None 444 ErrorUnknownObject = 1 445 # Object doesn't have specified attribute, retvalue = None 446 ErrorUnknownAttribute = 2 # NO USAR 447 # An Exception Throw, exception is returned in retvalue 448 ErrorExceptionThrowed = 3 449 # An error on protocol 450 ErrorUnknownRequestType = 4 451 # The object is callable, we don't wanna send it, we want to call it 452 ObjectIsCallable = 5 453 # The object or the method is not accesible with the authentication or is not valid 454 ErrorInTheAuthentication = 6 455 """ 456 # No error, successful call 457 Successful = 0 458 # Unknown Object, no object registered to share in server, retvalue is None 459 ErrorUnknownObject = 1 460 # Object doesn't have specified attribute, retvalue = None 461 ErrorUnknownAttribute = 2 # NO USAR 462 # An Exception Throw, exception is returned in retvalue 463 ErrorExceptionThrowed = 3 464 # An error on protocol 465 ErrorUnknownRequestType = 4 466 # The object is callable, we don't wanna send it, we want to call it 467 ObjectIsCallable = 5 468 # The object or the method is not accesible with the authentication or is not valid 469 ErrorInTheAuthentication = 6 470 471 ErrorNames = ['Successful', 'ErrorUnknownObject','ErrorUnknownAttribute', 472 'ErrorExceptionThrowed', 'ErrorUnknownRequestType','ObjectIsCallable','ErrorInTheAuthentication'] 473
474 - def __init__(self, errcode = None, retvalue = None, rettype = None):
475 """ Initialize the response with an error code |errcode| and the 476 return value |retvalue|""" 477 self.errcode = errcode 478 self.retvalue = retvalue 479 self.rettype = rettype
480
481 - def errname(self):
482 """ Returns the name of the errcode """ 483 return Response.ErrorNames[self.errcode]
484
485 - def prepareToCall(self, remoteobj):
486 """ Returns the returned value of calling |methodname| with arguments 487 |args|. Can raise remote exceptions or locals due to remote errors """ 488 #self.attrname = self.retvalue.attrname 489 #print "**********> PrepareToCall: ",self.retvalue 490 self.attrname = self.retvalue[1] #'attrname' 491 #self.remoteobj = remoteobj 492 self.comm,self.objname,self.objauth = remoteobj.__xxxconnxxx__ 493 # preparing cache of callable objects 494 remoteobj.__dict__[self.attrname] = self.__call__
495
496 - def __call__(self, *args, **kwargs):
497 """ Internal function, this is the real __call__ method """ 498 #comm, objname, auth = self.remoteobj.__xxxconnxxx__ 499 #return comm.callMethodGuess(objname, self.attrname, args, kwargs, 500 # SendByValue, comm.local_server, 501 # auth = auth) 502 return self.comm.callMethodGuess(self.objname, self.attrname, 503 args, kwargs, SendByValue, 504 self.comm.local_server, 505 auth = self.objauth)
506 507
508 -class BasicServer:
509 """ The base of spyro's communication servers """
510 - def __init__(self, address = None, location = '/spyro?XMLRPC', priorities = None, defaultFormat = None, 511 transportProtocol = ProtocolHTTPShared, objectpool = None, overture=defaultoverture):
512 """ |address| The address where the Server is listening 513 |location| Location or path in the web server 514 |priorities| Priorities of the protocols 515 |defaultFormat| The default message format where none is 516 specified 517 |transportProtocol| The class to manage the transport layer 518 |objectpool| The object pool manager , if None then 519 a new SeverObjectPool instance is used 520 """ 521 self.server_address = address 522 self.transportProtocol = transportProtocol 523 self.priorities = priorities 524 if defaultFormat is None: defaultFormat = '' 525 self.defaultFormat = defaultFormat 526 self.location = location 527 # Open connections 528 # self.connections = {} 529 self.commclients = {} 530 # Number of registered returns 531 self.lockconn = Lock() 532 if not objectpool: 533 objectpool = ServerObjectPool() 534 self.setobj = objectpool.setobj 535 self.getobj = objectpool.getobj 536 self.delobj = objectpool.delobj 537 self.setsend = objectpool.setsend
538
539 - def __getstate__(self):
540 """ To pickle functions """ 541 return {}
542
543 - def regconn(self, conn):
544 """ Register a client connection """ 545 # self.lockconn.acquire() 546 # self.connections[conn] = 1 547 # self.lockconn.release() 548 pass
549
550 - def delconn(self, conn):
551 """ Removes a client connection """ 552 pass
553 # self.lockconn.acquire() 554 # del self.connections[conn] 555 # self.lockconn.release() 556
557 - def __setitem__(self,k,v):
558 """Register an object. """ 559 self.setobj(k,v)
560
561 - def set(self,k,v):
562 """ Register an object |v| with the ID |k|. Use |setobj| instead""" 563 warn.stack("This method is deprecated") 564 raise "This method is deprecated"
565
566 - def close(self):
567 """ Close server """ 568 pass
569 570 ###################################################################### 571 ### Object's execution block 572 ######################################################################
573 - def getcomm(self, local_server):
574 """ Returns the CommunicationClient associated to the address that 575 made the |request| through the |serv| """ 576 # addr=(xxxself.request.getpeername()[0],l[0]) 577 addr=(local_server[2],local_server[0]) 578 try: 579 comm = self.commclients[addr] 580 except KeyError: 581 comm = CommunicationClientThreaded(addr, local_server[1], local_server=self,poolSize=5) 582 self.commclients[addr] = comm 583 return comm
584 585 #def __del__(self): 586 # warn.log("DEL PEERCONNECTION", self.__dict__)
587 - def getargument(self, comm, arg):
588 """ Get the argument asociated to |arg| using the |comm| 589 It manages references and values """ 590 argobj, argtype = ObjectWrapper_Unpack(arg) 591 if argtype == SendByReference: 592 return comm[argobj] 593 else: 594 return argobj
595
596 - def handlecall(self, obj, atr, objname, methodname, args, kwargs, local_server):
597 """ handles the execution of methods. called by handle. 598 If the object has the attribute __spyro__ it must be an object 599 with the |__contains__| method and every element is a method 600 that must be handled by pass (call methods with the request 601 method as the first argument, other arguments are passed as normal) 602 603 If the object has the attribute '__spyro_formats__' it must be a 604 dictionary (object with the __getitem__ method) of { methodname: argumentformats } 605 606 Where |methodname| must be the name of methods where the arguments need to be checked 607 |argumentformats| is the format as listed in the SPyRO.args module 608 """ 609 # methodname = request.attrname 610 try: 611 hasspyro = methodname in getattr(obj,'__spyro__') 612 except AttributeError: 613 hasspyro = False 614 try: 615 fmt_args = getattr(obj,'__spyro_formats__') 616 fmtcomp = None 617 try: 618 comp = fmt_args['!compiled!'] 619 # print repr(comp),"<","l" * 70, ">" 620 try: 621 fmtcomp = comp[methodname] 622 except (KeyError, AttributeError): 623 fmtcomp = None 624 except KeyError: 625 fmt_args['!compiled!'] = {} 626 if fmtcomp is None: 627 try: 628 fmtcomp = Args.Arguments(fmt_args[methodname]) 629 fmt_args['!compiled!'][methodname] = fmtcomp 630 except KeyError: 631 fmtcomp = None 632 if not(fmtcomp is None): 633 fmtcomp.check(args, kwargs) 634 except AttributeError: 635 pass 636 if hasspyro: 637 warn.debug("A new api to __spyro__ was developed... check your implementation please") 638 return atr(self, objname, attrname, args, kwargs) 639 if local_server: 640 comm = self.getcomm(local_server) 641 args = list(args) 642 for index, arg in enumerate(args): 643 args[index] = self.getargument(comm, arg) 644 # args = tuple(_args) 645 for k,v in kwargs.items(): # keyword arguments 646 kwargs[k] = self.getargument(comm, v) 647 if not (type(kwargs) is DictType): kwargs = {} 648 return atr(*args, **kwargs)
649
650 - def can_del(self, authobj, req):
651 if authobj == None: return True 652 if authobj.can_del(req): return True 653 raise AuthError, "Can't del object %(objname)s permission denied"%req
654
655 - def can_set(self, authobj, req):
656 if authobj == None: return True 657 if authobj.can_set(req): return True 658 raise AuthError, "Can't set attr '%(attrname)s' in the object '%(objname)s'"%req
659
660 - def can_follow_objname(self, authobj, req):
661 if authobj == None: return True 662 if authobj.can_follow_objname(req): return True 663 raise AuthError, "Can't follow using dot names in the object '%(objname)s'"%req
664
665 - def can_get(self, authobj, req):
666 if authobj == None: return True 667 if authobj.can_get(req): return True 668 raise AuthError, "Can't get attr '%(attrname)s' in the object '%(objname)s'"%req
669
670 - def can_call(self, authobj, req):
671 if authobj == None: return True 672 if authobj.can_call(req): return True 673 raise AuthError, "Can't call in the object '%(objname)s'"%req
674
675 - def can_register(self, authobj, req):
676 if authobj == None: return True 677 if authobj.can_register(req): return True 678 raise AuthError, "Can't register objects, permission denied"
679
680 - def handle(self, rfile, wfile, environment, httprequest, overture=None, request_rewrite=None):
681 """ Handles an spyro request reading the request from rfile, using the broker specified in environment['QUERY_STRING'] 682 It writes data using wfile and the broker. 683 |rfile| Read file 684 |wfile| Write file 685 |environment| Environment of the Request 686 |httprequest| http request object 687 Optional arguments: 688 |overture| Overture to be executed before anything. If overture throws an exception 689 solve_request will not be executed. Prototype 690 overture(httprequest,environment) 691 |request_rewrite| If it's different of None it will be executed after the read of the 692 request and the generated dictionary will be sent and perhaps modified. 693 Prototype 694 request_rewrite(requestdict,httprequest,environment) 695 """ 696 if overture: overture(httprequest, environment) 697 #self.rfile = rfile 698 #self.wfile = wfile 699 #self.environment = environment 700 broker = self.transportProtocol(rfile, wfile, environment) 701 self.regconn(self) 702 req = broker.get_request(environment['QUERY_STRING']) 703 try: 704 local_server = req['local_server'] 705 if local_server is None: raise KeyError 706 req['local_server'] = (local_server[0], local_server[1], httprequest.request.getpeername()[0]) 707 except KeyError: 708 pass 709 if request_rewrite: request_rewrite(req, self, environment) 710 res = self.handle_request(**req) 711 broker.send_result( res ) 712 broker.close()
713
714 - def handle_request(self, *args, **kwargs):
715 try: 716 res = self._handle_request(*args, **kwargs) 717 except AuthError, error: 718 res = newResponse(Response.ErrorInTheAuthentication,str(error),SendByValue) 719 except (Exception, types.StringType, types.UnicodeType, types.NoneType, BooleanType, 720 types.LongType, types.FloatType, types.TupleType, types.ListType, types.DictType): 721 etype, evalue, etraceback = sys.exc_info() 722 error = "%s %s" % (etype, evalue) 723 warn.warn(error) 724 res = newResponse(Response.ErrorExceptionThrowed,str(error),SendByValue) 725 except: 726 _delrequest() 727 raise 728 _delrequest() 729 return res
730
731 - def _handle_request(self, objname, attrname, args = (), kwargs = {}, auth = None, extra = None, local_server = None, 732 reqtype=Request.ReqCallMethod, rettype=SendByValue, value=None, req=None):
733 if req is None: req = {'objname' : objname, 'attrname' : attrname, 'args' : args, 'kwargs' : kwargs, 734 'auth' : auth, 'local_server' : local_server, 'reqtype' : reqtype, 'rettype' : rettype, 735 'value' : value, 'req' : None 736 } 737 _setrequest(req) 738 ########################## 739 ### Say bye 740 ########################## 741 if Request.ReqBye == reqtype: 742 return newResponse(Response.Successful, None, SendByValue) 743 ########################## 744 ### Object name resolution 745 ########################## 746 try: 747 obj, authobj = self.getobj(objname) 748 except KeyError: 749 try: 750 # Resolve object name: attribute of objects 751 objpath = objname.split('.') 752 obj, authobj = self.getobj(objpath.pop(0)) 753 if not self.can_follow_objname(authobj, req): 754 raise AttributeError, "UnknownObject because: Forbidden access to object's attribute" 755 for oname in objpath: 756 obj = getattr(obj, oname) # authobj is inherited 757 except AttributeError: 758 return newResponse(Response.ErrorUnknownObject, 'Unknown Object: %s'%repr(objname), SendByValue) 759 req['obj'] = obj 760 ############################################# 761 ### Deleting objects (generated by users) 762 ############################################# 763 if Request.ReqDelObj == reqtype and self.can_del(authobj, req): 764 if objname == '!': 765 warn.debug("-DEL-OBJ-", objname) 766 #serv.delobj(objname) 767 return newResponse(Response.Successful, '', SendByValue) 768 ############################################# 769 ### The 'set' action 770 ############################################# 771 if Request.ReqSetAttr == reqtype and self.can_set(authobj, req): 772 if local_server != None: 773 value=self.getargument(self.getcomm(local_server), value) 774 setattr(obj, attrname, value) 775 return newResponse(Response.Successful, None, SendByValue) 776 val=None 777 ############################################# 778 ### The 'get' action 779 ############################################# 780 atr=getattr(obj, attrname) 781 if Request.ReqGetAttr == reqtype and self.can_get(authobj, req): 782 if callable(atr): 783 return newResponse(Response.ObjectIsCallable,(objname,attrname), SendByValue) 784 #print "x"*30,">", repr(atr) 785 val = atr 786 ############################################# 787 ### The 'call' action 788 ############################################# 789 elif Request.ReqCallMethod == reqtype and self.can_call(authobj, req): 790 val = self.handlecall(obj, atr, objname, attrname, args, kwargs, local_server) 791 else: 792 return newResponse(Response.ErrorUnknownRequestType, 'Unknown Request Type: %s'%repr(reqtype)) 793 if rettype == SendByReference and self.can_register(authobj, req): 794 res = newResponse(Response.Successful, self.setsend("?%s"%id(val),val), SendByReference) 795 else: 796 res = newResponse(Response.Successful, val, SendByValue) 797 return res
798
799 -class CommunicationServerShared(BasicServer):
800 - def __init__(self, address, 801 location = "/SPyRO?XMLRPC", 802 priorities = None, 803 defaultFormat = None, 804 overture = defaultoverture, 805 objectpool = None, 806 protocolServer = ProtocolHTTPShared):
807 """ |address| The address where the Server is listening 808 |location| Location or path in the web server 809 |priorities| Priorities of the protocols 810 |defaultFormat| The default message format where none is 811 specified 812 |overture| A function to be called before anything in the connection process 813 See PeerConnection.solve_request (the overture argument) to more information 814 If it's None, the overture will be skipped. 815 |objectpool| The object pool that dispatches the connections 816 |protocolServer| The class of the Procotol used in the Server side, by default 817 ProtocolHTTPShared is Used 818 """ 819 BasicServer.__init__(self, address, location, priorities, 820 defaultFormat, protocolServer, 821 objectpool = objectpool, overture = overture)
822
823 - def solve_request(self, httpreq, environment):
824 #x = PeerConnection() 825 self.handle(httpreq.rfile, httpreq.wfile, environment, httpreq, overture = self.overture)
826
827 -class CommunicationServer(BasicServer):
828 """ Starts a standalone SPyRO Server """
829 - def __init__(self, address, location = "/SPyRO?XMLRPC", 830 priorities = None, defaultFormat = None, 831 objectpool = None, 832 protocolServer = ProtocolHTTPShared, 833 overture = defaultoverture, 834 **httpoptions):
835 """ |address| The address where the Server is listening 836 |location| Location or path in the web server 837 |priorities| Priorities of the protocols 838 |defaultFormat| The default message format when None is 839 specified 840 |overture| A function to be called before anything in the connection process 841 See PeerConnection.solve_request (the overture argument) to more information 842 If it's None, the overture will be skipped. 843 |protocolServer| The class of the Procotol used in the Server side, by default 844 ProtocolHTTPShared is Used 845 """ 846 # This class must start an HTTP server and link to a CommunicationServerShared 847 BasicServer.__init__(self, address, location, priorities, 848 defaultFormat, protocolServer, 849 objectpool = objectpool, 850 overture = overture) 851 self.httpserver = http.HTTPServer(address, **httpoptions) 852 self.httpserver.register_urihandler(location.split('?')[0], self.request_handler)
853
854 - def serve_forever(self):
855 self.httpserver.serve_forever()
856
857 - def request_handler(self, httpreq, environment):
858 """ SPyRO Handler """ 859 self.handle(httpreq.rfile, httpreq.wfile, environment, httpreq)
860 861 # def handle_request(self): 862 # self.httpserver.handle_request() 863
864 - def close(self):
865 self.httpserver.close()
866 867 ServerShare = CommunicationServerShared 868 Server = CommunicationServer 869 870 ############################################################################## 871 ############################################################################## 872 ##### The client class set 873 ############################################################################## 874 ############################################################################## 875
876 -def default_socket_error_handler(comm, _socket, addr, error):
877 """ The default, fool, error handler in Client connections """ 878 errno, errstr = error 879 if errno == 111: 880 seconds = sleep_min_sec 881 connected = False 882 while True: 883 try: 884 _socket.connect(addr) 885 break 886 except socket.error, _error: 887 if _error[0] != 111: 888 default_socket_error_handler(comm, _socket, addr, _error) 889 break 890 # WISH: Try to find another server or port and connect, 891 # sleep if we can't find more servers 892 warn.debug("SPyRO Client to %s: %s"%(addr, error), 893 "trying in %f seconds"%(seconds)) 894 time.sleep(seconds) 895 if seconds > sleep_max_sec: 896 seconds = sleep_min_sec 897 else: 898 seconds = seconds + seconds 899 else: 900 warn.warn(error) 901 raise error 902 warn.debug("Done")
903
904 -class CommClientMixIn:
905 """ The mixin to CommunicationClient{,Threaded}"""
906 - def get(self, name, auth = None):
907 """ Get a local representation of the remote object with ID |name|. Deprecated. Use |getobj| instead""" 908 warn.stack("This is not an error, but this method is deprecated") 909 return RemoteObject(self, name, auth)
910
911 - def __getitem__(self,name):
912 """ Get a local representation of the remote object with ID |name|.""" 913 return RemoteObject(self, name, None)
914
915 - def getobj(self, name, auth = None):
916 """ Get a local representation of the remote object with ID |name|""" 917 return RemoteObject(self, name, auth)
918
919 - def getconn(self):
920 """ Get a communication object """ 921 return self
922 #raise "Don't use this method (getconn) directly" 923
924 - def putconn(self, conn):
925 """ Return a communication Object """ 926 pass
927 #raise "Don't use this method (putconn) directly" 928
929 - def handleRequest(self, conn, request, send_method):
930 """ Common code to getAttr, setAttr and callMethod """ 931 #conn = self.getconn() 932 try: 933 send_method(request) 934 response = conn.broker.get_result() 935 except socket.error: 936 warn.debug("Unable to connect remote peer in SPyRO.handleRequest") 937 conn._do_bye = False 938 self.putconn(conn) 939 conn.close() 940 raise 941 except: 942 warn.warn("Unexpected exception in SPyRO.CommunicationClient*") 943 conn._do_bye = False 944 conn.close() 945 self.putconn(conn) 946 raise 947 self.putconn(conn) 948 if response.errcode == Response.Successful: 949 if response.rettype == SendByReference: 950 response.retvalue = self[response.retvalue] 951 return response 952 if response.errcode == Response.ObjectIsCallable: 953 raise AttributeIsCallable, response 954 if response.errcode == Response.ErrorExceptionThrowed: 955 #warn.stack("RemoteException", response.retvalue) 956 #warn.stack("RemoteException", type(response)) 957 raise RemoteException, response.retvalue 958 if response.errcode == Response.ErrorInTheAuthentication: 959 raise AuthError(response.retvalue) 960 raise RuntimeError, "Remote send code %d (%s)\n%s\n"%(response.errcode, 961 response.errname(), request)
962
963 - def getAttr(self, objname, attrname, rettype = SendByValue, 964 auth = None):
965 """ Get an attribute |attrname| of the object |objname| with the method 966 to send the object |rettype|, the default is SendByValue 967 |auth| The authorizing information to access the object 968 """ 969 request=newRequestGet(objname, attrname, rettype, auth) 970 conn = self.getconn() 971 return self.handleRequest(conn, request, conn.broker.send_getAttr).retvalue
972
973 - def setAttr(self, objname, attrname, value, sendtype=SendByValue, 974 local_server = None, auth = None):
975 """ Set the attribute |attrname| to |value| in the object |objname|. 976 The mode to send the value is |sendtype|. 977 The |local_server| is used to send by reference 978 |auth| The authorizing information to access the object 979 """ 980 if local_server: 981 if sendtype == SendByValue: 982 value = ObjectWrapper_ByValue(value, sendtype) 983 elif sendtype == SendByReference: 984 value = ObjectWrapper_ByReference(value, local_server) 985 else: 986 value = ObjectWrapper_GuessType(value, local_server) 987 local_server = self.get_request_local_server(local_server) 988 request = newRequestSet(objname, attrname, value, local_server, 989 auth) 990 conn = self.getconn() 991 return self.handleRequest(conn, request, conn.broker.send_setAttr).retvalue
992
993 - def byeRequest(self):
994 """ Send a bye request """ 995 conn = self.getconn() 996 self.handleRequest(conn, newRequestBye(), conn.broker.send_control)
997
998 - def delObject(self, objname, auth = None):
999 """ Get an attribute |attrname| of the object |objname| with the method 1000 to send the object |rettype|, the default is SendByValue. 1001 |auth| The authorizing information to access the object 1002 """ 1003 request=newRequestDel(objname, auth) 1004 conn = self.getconn() 1005 return self.handleRequest(conn, request, conn.broker.send_control).retvalue
1006
1007 - def callMethod(self, objname, attrname, args = (), kwargs = {}, 1008 rettype = SendByValue, local_server = None, auth = None, extra = None):
1009 """ Call a method. Every argument must be a ObjectWrapper 1010 with all the necessary information. 1011 |objname| The object's name that has the method 1012 |attrname| The name of the object 1013 |args| The positional arguments of the call 1014 |kwargs| The keyword arguments of the call 1015 |rettype| The return type of the result 1016 |local_server| the local server to send arguments by reference 1017 |auth| The authorizing information to access the object 1018 |extra| Extra general information to be passed in the message 1019 """ 1020 request = newRequestCall(objname, attrname, args, kwargs, 1021 rettype, local_server, auth, extra) 1022 conn = self.getconn() 1023 return self.handleRequest(conn, request, conn.broker.send_callMethod).retvalue
1024
1025 - def get_request_local_server(self, local_server):
1026 """ Return a local_server tuple to make the request """ 1027 return (local_server.server_address[1],local_server.location)
1028
1029 - def callMethodGuess(self, objname, attrname, args = (), kwargs = {}, 1030 rettype = SendByValue, local_server = None, 1031 auth = None, extra = None):
1032 """ Call a method, Guessing the best type to send arguments 1033 |objname| The object's name that has the method 1034 |attrname| The name of the object 1035 |args| The positional arguments of the call 1036 |kwargs| The keyword arguments of the call 1037 |rettype| The return type of the result 1038 |local_server| the local server to send arguments by reference 1039 |auth| The authorizing information to access the object 1040 |extra| Additional information to be added to the message 1041 """ 1042 if local_server: 1043 warn.debug("Objects references as arguments, GC problem") 1044 xargs = [] 1045 for arg in args: 1046 xargs.append(ObjectWrapper_GuessType(arg, local_server)) 1047 args = tuple(xargs) 1048 for k,v in kwargs.items(): 1049 kwargs[k] = ObjectWrapper_GuessType(v, local_server) 1050 local_server=self.get_request_local_server(local_server) 1051 return self.callMethod(objname, attrname, args, kwargs, rettype, 1052 local_server, auth = auth, extra = extra)
1053 1054
1055 -class CommunicationClientBase(CommClientMixIn):
1056 """ A base class to any CommunicationServer client. Represents a 1057 communication channel 1058 with the server """
1059 - def __init__(self, serv_addr, path, local_server=None, 1060 socket_error_handler = default_socket_error_handler, 1061 compress = False, usingssl = False):
1062 """ Initialize client. 1063 |serv_addr| The remote server address (host,port) 1064 |path| Location of the server (to apache or another general purpose 1065 http servers). It describes the encoder protocol using the 1066 query_string '?' notation. 1067 |local_server| If is set to a CommunicationServer, when a method is 1068 called the arguments are passed by reference and the remote 1069 server 1070 will try to connect to this server (port and location). 1071 The IP address is the used to contact the remote server. 1072 |socket_error_handler| The error handler, called when an exception 1073 is arised in the connection, parsing, or interpreting messages. 1074 |compress| Compress the messages between peers 1075 |usingssl| If True, every connection is performed with the https protocol 1076 """ 1077 self.socket_error_handler = socket_error_handler 1078 self.path = path 1079 self.local_server = local_server 1080 self.__closed = False 1081 self.broker = ProtocolHTTPReader(serv_addr, path, compress, usingssl = usingssl) 1082 self._do_bye = True
1083
1084 - def __getstate__(self):
1085 """ To pickle functions """ 1086 return {'path': self.path }
1087
1088 - def close(self):
1089 """ Close the client """ 1090 self.__closed = True 1091 if self._do_bye: 1092 self.byeRequest() 1093 self.broker.close()
1094
1095 - def __del__(self):
1096 """ called for the garbage collection system. Close the object 1097 if it is not closed yet. """ 1098 if not self.__closed: self.close()
1099
1100 -class CommunicationClient(CommunicationClientBase):
1101 """ A Client to CommunicationServer. Represents a communication channel 1102 with the server """
1103 - def __init__(self, serv_addr, path, local_server=None, 1104 socket_error_handler = default_socket_error_handler, 1105 compress = False, 1106 usingssl = False):
1107 CommunicationClientBase.__init__(self, serv_addr, path, local_server, 1108 socket_error_handler, 1109 compress = compress, 1110 usingssl = usingssl) 1111 self.connlock = Lock()
1112
1113 - def __getstate__(self):
1114 """ To pickle functions """ 1115 return CommunicationClientBase.__getstate__(self)
1116
1117 - def getconn(self):
1118 self.connlock.acquire() 1119 return self
1120
1121 - def putconn(self, conn):
1122 self.connlock.release()
1123 1124 Client = CommunicationClient 1125
1126 -class CommunicationClientThreaded(CommClientMixIn):
1127 """ The thread safe CommunicationClient. It uses several 1128 CommunicationClient to manage threading """
1129 - def __init__(self, serv_addr, path, local_server = None, poolSize=0, 1130 socket_error_handler = default_socket_error_handler, 1131 compress = False, 1132 usingssl = False):
1133 """ Create the threaded client. 1134 |serv_addr| The server address (host,port). 1135 |path| The path where is listening the remote host, it has the 1136 protocol using query_string '?' notation. 1137 |poolSize| The maximum size of the pool of CommunicationClient objects 1138 |local_server| If is set to a CommunicationServer, when a method is 1139 called the arguments are passed by reference and the remote server 1140 will try to connect to this server (port and location). 1141 The IP address is the used to contact the remote server. 1142 |socket_error_handler| The socket error handler. 1143 |compress| If True it uses gzip compression to content 1144 |usingssl| If True it uses the https protocol to perform connections 1145 """ 1146 self.queue = Queue.Queue(poolSize) 1147 self.path = path 1148 self.local_server = local_server 1149 self.ehandler = socket_error_handler 1150 self.address = serv_addr 1151 self.__closed = False 1152 self.compress = compress 1153 self.usingssl = usingssl
1154
1155 - def __getstate__(self):
1156 """ To pickle functions """ 1157 return {'path': self.path, 'address': self.address}
1158
1159 - def getconn(self):
1160 try: 1161 comm=self.queue.get_nowait() 1162 except Queue.Empty: 1163 comm=CommunicationClientBase(self.address, self.path, 1164 self.local_server, 1165 socket_error_handler = self.ehandler, 1166 compress = self.compress, 1167 usingssl = self.usingssl) 1168 return comm
1169
1170 - def putconn(self, conn):
1171 try: 1172 self.queue.put(conn) 1173 except Queue.Full: 1174 pass
1175
1176 - def __del__(self):
1177 if not self.__closed: self.close()
1178
1179 - def close(self):
1180 while not self.__closed: 1181 try: 1182 comm = self.queue.get_nowait() 1183 comm.close() 1184 except Queue.Empty: 1185 break 1186 self.__closed = True
1187 1188 TClient = CommunicationClientThreaded 1189
1190 -class CallClient(Client):
1191 """ Special Client thay always call methods, no attribute operations are allowed"""
1192 - def getAttr(self,objname, attrname, rettype = SendByValue, auth = None):
1193 raise AttributeIsCallable, Response(Response.ObjectIsCallable, retvalue = (objname, attrname), rettype = rettype)
1194 1195
1196 -class TCallClient(TClient):
1197 """ Special TClient to always call methods, no attribute operations"""
1198 - def getAttr(self,objname, attrname, rettype = SendByValue, auth = None):
1199 raise AttributeIsCallable, Response(Response.ObjectIsCallable, retvalue = (objname, attrname), rettype = retttype)
1200 1201 ############################################################################## 1202 ############################################################################## 1203 ##### Remote Object Class and Helper methods 1204 ############################################################################## 1205 ############################################################################## 1206
1207 -class FakeIterator:
1208 """ FakeIterator """
1209 - def __init__(self, obj):
1210 self.obj = obj 1211 self.pos = -1 1212 self.len = len(obj) - 1
1213
1214 - def next(self):
1215 if self.pos < self.len: 1216 self.pos = self.pos + 1 1217 return self.obj[self.pos] 1218 else: 1219 raise StopIteration
1220
1221 - def __call__(self):
1222 return self
1223 1224
1225 -class RemoteObject:
1226 """ Local representation of a RemoteObject. The remote object needs to be 1227 registered in the server. """
1228 - def __init__(self, connection, objectname, auth):
1229 self.__dict__['__xxxconnxxx__'] = (connection, objectname, auth)
1230
1231 - def __getstate__(self):
1232 return {}
1233
1234 - def __getattr__(self, attrname):
1235 """ Returns the remote attribute named |attribute| as local object. 1236 Can raise remote exceptions or locals due remote errors """ 1237 if attrname == '__iter__': 1238 #return lambda: (self[i] for i in range(0,len(self))) 1239 return FakeIterator(self) 1240 conn,objname,auth = self.__xxxconnxxx__ 1241 try: 1242 return conn.getAttr(objname, attrname, SendByValue, 1243 auth = auth) 1244 except AttributeIsCallable, response: 1245 r = response.args[0] 1246 r.prepareToCall(self) 1247 return r
1248
1249 - def __del__(self):
1250 conn, objname, auth = self.__xxxconnxxx__ 1251 if objname[0] == '!': 1252 warn.debug("-USR-DEL-OBJ-",objname) 1253 conn.delObject(objname, auth)
1254
1255 - def __setattr__(self, attrname, value):
1256 """ __setattr__('name', value) <==> x.name = value. Where |x| is 1257 a remote object """ 1258 conn,objname, auth = self.__xxxconnxxx__ 1259 return conn.setAttr(objname, attrname, value, SendByValue, None, 1260 auth = auth)
1261
1262 - def __repr__(self):
1263 """ Internal representation """ 1264 return "<SPyRO.RemoteObject name: %s, local id: %s>"%(getobjname(self), 1265 hex(id(self)))
1266 1267 # def __eq__(self, arg): 1268 # comm, objname, auth = self.__xxxconnxxx__ 1269 # return comm.callMethodGuess(objname, '__eq__', (arg), {}, 1270 # SendByValue, None, auth = auth) 1271 1272 # def __cmp__(self, arg): 1273 # comm, objname, auth = self.__xxxconnxxx__ 1274 # return comm.callMethodGuess(objname, '__cmp__', (arg), {}, 1275 # SendByValue, None, auth = auth) 1276
1277 -def getobject(address, location, name, local_server = None, poolSize=0):
1278 """ Can be used as a shortcut when only we need one object in address """ 1279 comm=CommunicationClientThreaded(address, location, local_server, poolSize) 1280 return comm.get(name)
1281
1282 -def getconn(remoteobject):
1283 """ Return the connection of the remote object""" 1284 return remoteobject.__xxxconnxxx__[0]
1285
1286 -def getobjname(remoteobject):
1287 """ Returns the name of the object """ 1288 return remoteobject.__xxxconnxxx__[1]
1289