commit-gnue
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

[gnue] r8471 - in trunk: gnue-appserver/src gnue-common/src/rpc/drivers


From: reinhard
Subject: [gnue] r8471 - in trunk: gnue-appserver/src gnue-common/src/rpc/drivers gnue-common/src/rpc/drivers/hessian
Date: Thu, 18 May 2006 08:35:44 -0500 (CDT)

Author: reinhard
Date: 2006-05-18 08:35:42 -0500 (Thu, 18 May 2006)
New Revision: 8471

Added:
   trunk/gnue-common/src/rpc/drivers/hessian/
   trunk/gnue-common/src/rpc/drivers/hessian/ClientAdapter.py
   trunk/gnue-common/src/rpc/drivers/hessian/ServerAdapter.py
   trunk/gnue-common/src/rpc/drivers/hessian/SimpleHessianServer.py
   trunk/gnue-common/src/rpc/drivers/hessian/__init__.py
   trunk/gnue-common/src/rpc/drivers/hessian/hessianlib.py
   trunk/gnue-common/src/rpc/drivers/hessian/typeconv.py
Modified:
   trunk/gnue-appserver/src/geasRpcServer.py
Log:
Added "hessian" RPC driver contributed by lekma.


Modified: trunk/gnue-appserver/src/geasRpcServer.py
===================================================================
--- trunk/gnue-appserver/src/geasRpcServer.py   2006-05-18 12:46:51 UTC (rev 
8470)
+++ trunk/gnue-appserver/src/geasRpcServer.py   2006-05-18 13:35:42 UTC (rev 
8471)
@@ -242,6 +242,16 @@
                 'allowed_hosts': gConfig ('allowed_hosts')}
       self.setTransports ({'pyro': params})
 
+    elif rpctype == 'hessian':
+      port = gConfig ("rpcport")
+      print o(u_("Exporting our services via %(rpctype)s (port %(port)s) ...") 
% \
+               {"rpctype": rpctype, "port": port})
+      params = {'port': int (port),
+                'allowed_hosts': gConfig ('allowed_hosts'),
+                'bindto': gConfig ('bindto'),
+                'servertype': gConfig ('servertype')}
+      self.setTransports ({'hessian': params})
+
     elif rpctype == "sockets":
       # Sockets not working yet
       print o(u_("Exporting our services via sockets (EXPERIMENTAL!) ..."))

Added: trunk/gnue-common/src/rpc/drivers/hessian/ClientAdapter.py
===================================================================
--- trunk/gnue-common/src/rpc/drivers/hessian/ClientAdapter.py  2006-05-18 
12:46:51 UTC (rev 8470)
+++ trunk/gnue-common/src/rpc/drivers/hessian/ClientAdapter.py  2006-05-18 
13:35:42 UTC (rev 8471)
@@ -0,0 +1,331 @@
+# GNU Enterprise Common Library - RPC Interface - Hessian client
+#
+# Copyright 2001-2006 Free Software Foundation
+#
+# This file is part of GNU Enterprise.
+#
+# GNU Enterprise is free software; you can redistribute it
+# and/or modify it under the terms of the GNU General Public
+# License as published by the Free Software Foundation; either
+# version 2, or (at your option) any later version.
+#
+# GNU Enterprise is distributed in the hope that it will be
+# useful, but WITHOUT ANY WARRANTY; without even the implied
+# warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
+# PURPOSE. See the GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public
+# License along with program; see the file COPYING. If not,
+# write to the Free Software Foundation, Inc., 59 Temple Place
+# - Suite 330, Boston, MA 02111-1307, USA.
+#
+# $Id$
+
+import hessianlib
+import socket
+import weakref
+
+from gnue.common.apps import errors
+from gnue.common.rpc import client
+from gnue.common.rpc.drivers import Base
+from gnue.common.utils import http
+
+import typeconv
+
+# =============================================================================
+# hessian transport class using a persistent connection
+# =============================================================================
+
+class PersistentTransport (hessianlib.Transport):
+  """
+  Handles a persistent HTTP connection to an Hessian server. The connection
+  will be established on the first request, and reused by all later requests.
+  The Hessian server is supposed to grant persistent connections via HTTP.
+  """
+
+  user_agent = "GNUe Hessian"
+  
+  # ---------------------------------------------------------------------------
+  # Constructor
+  # ---------------------------------------------------------------------------
+
+  def __init__ (self):
+
+    self.__connection = None
+
+
+  # ---------------------------------------------------------------------------
+  # Send a request to the given host and return it's response
+  # ---------------------------------------------------------------------------
+
+  def request (self, host, handler, request_body, verbose = 0):
+    """
+    Send an hessian-request to the given host and return the server's 
response. If
+    there's no persistent HTTP/1.1 connection available already it will be
+    established.
+
+    @param host: target host to be contacted. This host must include a
+      port-number, e.g. foobar.com:4711
+    @param handler: target RPC handler, usually '/Hessian'
+    @param request_body: Hessian request body as created by a hessianlib.dumps 
()
+    @param verbose: if set to 1 the underlying L{http.HTTPConnection} will
+      print additional debug messages
+
+    @returns: tuple with the server's response to the request (as returned by
+      the L{hessianlib.Unmarshaller}
+
+    @raises AccessDeniedError: if this client is not allowed to access the
+      RPC services at the given host
+    @raises ProtocolError: if the server response was another one than 200 (OK)
+    """
+
+    if not self.__connection:
+      self.__connection = http.HTTPConnection (host)
+
+    self.__connection.set_debuglevel (verbose)
+    self.__connection.request ("POST", handler, request_body)
+
+    response = self.__connection.getresponse ()
+    if response:
+      if response.status == 403:
+        raise client.AccessDeniedError, host
+
+      elif response.status != 200:
+        raise hessianlib.ProtocolError (host + handler, response.status,
+            response.reason, response.msg)
+
+      data = response.read ()
+
+      u = hessianlib.Unmarshaller ()
+      u.loads (data)
+
+      result = u.close ()
+    else:
+      result = None
+
+    return result
+
+
+  # ---------------------------------------------------------------------------
+  # Close the HTTP connection
+  # ---------------------------------------------------------------------------
+
+  def close (self):
+    """
+    Close the transport connection (if still open).
+    """
+    
+    if self.__connection:
+      self.__connection.close ()
+      self.__connection = None
+
+
+
+# =============================================================================
+# Hessian client adapter
+# =============================================================================
+
+class ClientAdapter (Base.Client):
+  """
+  Implements an Hessian client adapter using persistent HTTP connections as
+  transport.
+  """
+
+  _default_transport = "http"
+  _default_port      = 7654
+
+  # ---------------------------------------------------------------------------
+  # Initialize object
+  # ---------------------------------------------------------------------------
+
+  def __init__ (self, params):
+    """
+    @param params: parameter dictionary for initialization of the adapter
+    """
+
+    Base.Client.__init__ (self, params)
+
+    self._transport = PersistentTransport ()
+    self._verbose   = params.get ("loglevel", 0)
+    self.__remote   = "%s:%s" % (self._host, self._port)
+    self.__objectProxies = weakref.WeakValueDictionary ()
+
+
+  # ---------------------------------------------------------------------------
+  # Run a procedure on the server
+  # ---------------------------------------------------------------------------
+
+  def _callMethod_ (self, method, *args, **params):
+    """
+    Execute a method on the Hessian server and return it's result.
+
+    @param method: name of the method to be executed
+    @param args: tuple with all positional arguments
+    @param params: dictionary with all keyword arguments - Hessian does B{NOT}
+      support keyword arguments
+
+    @return: result of the remote procedure call
+
+    @raises RemoteError: if an exception occurred while executing the method on
+      the server, this exception will carry that exception
+    @raises AdminError: if an exception occurs in the socket-layer
+    @raises InvalidParameter: if an argument cannot be converted into a RPC
+      data-type, or a result cannot be converted into a native python type
+    """
+    
+    assert gEnter (9)
+
+    checktype (method, basestring)
+
+    if not self._transport:
+      assert gLeave (9)
+      return
+
+    __args = tuple ([typeconv.python_to_rpc (arg, self.__unwrapProxy) \
+        for arg in args])
+    try:
+      request = hessianlib.dumps (__args, method)
+      result = self._transport.request (self.__remote, "/Hessian", request,
+                                        self._verbose)
+
+      # If the result is a tuple with only one item, we're only interessted in
+      # that single item
+      if len (result) == 1:
+        result = result [0]
+
+    except hessianlib.Fault, e:
+      # If we got a Fault object, transform it into a RemoteError
+      (exType, exName, exMessage, exDetail) = e.message.split (u"\x91")
+      raise errors.RemoteError, (exType, exName, exMessage, exDetail)
+
+    except socket.error:
+      raise errors.AdminError, errors.getException () [2]
+
+    result = typeconv.rpc_to_python (result, self.__wrapProxy,
+        client.InvalidParameter)
+    assert gLeave (9, result)
+    return result
+
+
+  # ---------------------------------------------------------------------------
+  # Nice string representation
+  # ---------------------------------------------------------------------------
+
+  def __str__ (self):
+    return "Hessian client adapter for %s" % self.__remote
+
+
+  # ---------------------------------------------------------------------------
+  # Wrap an ObjectProxy instance around a server object
+  # ---------------------------------------------------------------------------
+
+  def __wrapProxy (self, item):
+
+    if item ["__id__"] in self.__objectProxies:
+      return self.__objectProxies [item ["__id__"]]
+    else:
+      result = ObjectProxy (self, item)
+      self.__objectProxies [item ["__id__"]] = result
+      return result
+
+
+  # ---------------------------------------------------------------------------
+  # Unwrap an ObjectProxy instance so it can be sent through hessian
+  # ---------------------------------------------------------------------------
+
+  def __unwrapProxy (self, proxy):
+
+    return proxy._storedItem
+
+
+  # ---------------------------------------------------------------------------
+  # Close the underlying transport connection
+  # ---------------------------------------------------------------------------
+
+  def destroy (self):
+    """
+    Close the transport connection
+    """
+
+    self._transport.close ()
+    self._transport = None
+
+
+# =============================================================================
+# Proxy class for objects living on the server
+# =============================================================================
+
+class ObjectProxy (Base.ServerProxy):
+  """
+  A proxy class providing an execution context of server side objects.
+  """
+  
+  # ---------------------------------------------------------------------------
+  # Constructor
+  # ---------------------------------------------------------------------------
+
+  def __init__ (self, adapter, item):
+
+    Base.ServerProxy.__init__ (self, adapter)
+    self._storedItem = item
+
+
+  # ---------------------------------------------------------------------------
+  # Remove the object from the server's object store
+  # ---------------------------------------------------------------------------
+
+  def __del__ (self):
+    """
+    Remove the object from the server's object store. Further access to this
+    object will lead to an exception
+    """
+
+    self._adapter._callMethod_ ("_destroy", self._storedItem)
+
+
+  # ---------------------------------------------------------------------------
+  # Attribute access
+  # ---------------------------------------------------------------------------
+
+  def __getattr__ (self, name):
+
+    result = ObjectProxyMethod (self._adapter, name, self._storedItem)
+    self.__dict__ [name] = result
+
+    return result
+
+
+
+# =============================================================================
+# Callable environment for object proxies
+# =============================================================================
+
+class ObjectProxyMethod (Base.ProxyMethod):
+  """
+  Provide a callable environment for an L{ObjectProxy}. This will call the 
+  "_call" method at the remote server, giving the id-dictionary and the
+  method-name as first and second argument.
+  """
+
+  # ---------------------------------------------------------------------------
+  # Constructor
+  # ---------------------------------------------------------------------------
+
+  def __init__ (self, adapter, methodname, item):
+
+    Base.ProxyMethod.__init__ (self, adapter, methodname)
+    self._storeItem = item
+
+
+  # ---------------------------------------------------------------------------
+  # Call a method
+  # ---------------------------------------------------------------------------
+
+  def __call__ (self, *args, **params):
+
+    # Add the id-dictionary and the methodname to the rpc-call
+    nargs = tuple ([self._storeItem, self._methodname] + list (args))
+    return self._adapter._callMethod_ ("_call", *nargs, **params)
+
+  def __str__ (self):
+    return "ObjectProxyMethod '%s' of %s" % (self._methodname, self._storeItem)


Property changes on: trunk/gnue-common/src/rpc/drivers/hessian/ClientAdapter.py
___________________________________________________________________
Name: svn:keywords
   + Id

Added: trunk/gnue-common/src/rpc/drivers/hessian/ServerAdapter.py
===================================================================
--- trunk/gnue-common/src/rpc/drivers/hessian/ServerAdapter.py  2006-05-18 
12:46:51 UTC (rev 8470)
+++ trunk/gnue-common/src/rpc/drivers/hessian/ServerAdapter.py  2006-05-18 
13:35:42 UTC (rev 8471)
@@ -0,0 +1,401 @@
+# GNU Enterprise Common Library - RPC Interface - hessian ServerAdpater
+#
+# Copyright 2001-2006 Free Software Foundation
+#
+# This file is part of GNU Enterprise
+#
+# GNU Enterprise is free software; you can redistribute it
+# and/or modify it under the terms of the GNU General Public
+# License as published by the Free Software Foundation; either
+# version 2, or (at your option) any later version.
+#
+# GNU Enterprise is distributed in the hope that it will be
+# useful, but WITHOUT ANY WARRANTY; without even the implied
+# warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
+# PURPOSE. See the GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public
+# License along with program; see the file COPYING. If not,
+# write to the Free Software Foundation, Inc., 59 Temple Place
+# - Suite 330, Boston, MA 02111-1307, USA.
+#
+# $Id$
+
+import os
+import hessianlib
+import SocketServer
+import typeconv
+import datetime
+
+from SimpleHessianServer import SimpleHessianServer
+from gnue.common.rpc import server
+from gnue.common.rpc.drivers import Base
+from gnue.common.apps import errors
+from gnue.common.utils import http
+
+
+# =============================================================================
+# Exceptions
+# =============================================================================
+
+class ObjectNotFoundError (errors.SystemError):
+  def __init__ (self, item):
+    msg = u_("Element of type '%(type)s' with id '%(id)s' not found in store")\
+          % {"type": item ["__rpc_datatype__"], "id": item ["__id__"]}
+    errors.SystemError.__init__ (self, msg)
+
+
+# =============================================================================
+# Class implementing an Hessian server adapter
+# =============================================================================
+
+class ServerAdapter (Base.Server):
+  """
+  Implementation of a Hessian server supporting HTTP/1.1 persistent
+  connections. It supports both forking and threading per connection and one
+  can use the 'servertype' parameter set to 'forking' or 'threading' to select
+  this behavior. NOTE: 'forking' is not supported by all platforms; in this
+  case a threading server will be selected automatically.
+
+  @ivar _tpcServer: the TCPServer of the Hessian-Server
+  """
+
+  # ---------------------------------------------------------------------------
+  # Constructor
+  # ---------------------------------------------------------------------------
+
+  def __init__ (self, service, parameters):
+    """
+    @param service: python object to be served
+    @param parameters: dictionary of server specific parameters
+    """
+
+    Base.Server.__init__ (self, service, parameters)
+
+    stype = parameters.get ("servertype", "forking")
+
+    # In order to use a forking server make sure it's supported by the OS
+    if stype == "forking" and hasattr (os, "fork"):
+      serverClass = ForkingHessianServer
+      self.__type = "forking"
+    else:
+      serverClass = ThreadingHessianServer
+      self.__type = "threading"
+
+    self._tcpServer = serverClass ((self._bindto, self._port),
+        logRequests = parameters.get ("loglevel", 0), adapter = self)
+
+    # Store with all valid objects created by the server
+    self._clientPerObject = {}
+    self._objectPerClient = {}
+
+    # Register the python object to be served as well as the introspection
+    # functions (system.list_methods,system.list_help, system.list_signatures)
+    self._tcpServer.register_instance (self.instance)
+    self._tcpServer.register_introspection_functions ()
+
+    # Register functions for object support: calling and removing
+    self._tcpServer.register_function (self._call)
+    self._tcpServer.register_function (self._destroy)
+
+
+  # ---------------------------------------------------------------------------
+  # Dispatch a method with the given parameters
+  # ---------------------------------------------------------------------------
+
+  def call (self, client, method, parameters):
+    """
+    Dispatch a method with a given set of parameters
+
+    @param method: method to be dispatched
+    @param parameters: tuple of parameters to call method with. These
+      parameters are given in RPC types.
+
+    @returns: result of the method given in RPC types
+    """
+
+    assert gEnter (9)
+
+    checktype (method, basestring)
+    checktype (parameters, tuple)
+    
+    params = typeconv.rpc_to_python (parameters, self._fetchFromStore,
+        server.InvalidParameter, client)
+    result = self._tcpServer._dispatch (method, params)
+
+    result = typeconv.python_to_rpc (result, self._updateStore, client)
+    assert gLeave (9, result)
+    return result
+
+
+  # ---------------------------------------------------------------------------
+  # Call a procedure of a given stored object
+  # ---------------------------------------------------------------------------
+
+  def _call (self, storedObject, method, *parameters):
+
+    assert gEnter (9)
+    result = getattr (storedObject, method) (*parameters)
+    assert gLeave (9, result)
+    return result
+
+
+  # ---------------------------------------------------------------------------
+  # Remove an object from the object store
+  # ---------------------------------------------------------------------------
+
+  def _destroy (self, item):
+
+    assert gEnter (9)
+
+    if hasattr (item, "_destroy"):
+      item._destroy ()
+
+    itemId = str (id (item))
+    client = self._clientPerObject.get (itemId)
+
+    if itemId in self._clientPerObject:
+      del self._clientPerObject [itemId]
+
+    if client in self._objectPerClient:
+      if itemId in self._objectPerClient [client]:
+        del self._objectPerClient [client][itemId]
+
+    assert gLeave (9)
+
+
+  # ---------------------------------------------------------------------------
+  # Add an object to the store or update it's reference
+  # ---------------------------------------------------------------------------
+
+  def _updateStore (self, item, client):
+
+    gEnter (9)
+
+    # The itemId must be stored as string, because 64 bit numbers cannot be
+    # transported with hessian
+    itemId = str (id (item))
+    result = {"__id__": itemId, "__rpc_datatype__": "object"}
+    self._objectPerClient.setdefault (client, {}) [itemId] = item
+    self._clientPerObject [itemId] = client
+
+    assert gLeave (9, result)
+
+    return result
+
+
+  # ---------------------------------------------------------------------------
+  # Fetch a real object from the store, identified by it's id-dictionary
+  # ---------------------------------------------------------------------------
+
+  def _fetchFromStore (self, item, client):
+
+    try:
+      itemId = item ["__id__"]
+      return self._objectPerClient [client][itemId]
+
+    except KeyError:
+      raise ObjectNotFoundError, item
+
+
+  # ---------------------------------------------------------------------------
+  # Clear all object of a given client
+  # ---------------------------------------------------------------------------
+
+  def _clearClientObjects (self, client):
+
+    assert gEnter (9)
+
+    for item in self._objectPerClient.get (client, {}).values ():
+      self._destroy (item)
+
+    assert gLeave (9)
+
+
+  # ---------------------------------------------------------------------------
+  # Nice string representation
+  # ---------------------------------------------------------------------------
+
+  def __repr__ (self):
+    return "<%s Hessian server serving '%s' at %d>" % \
+        (self.__type, self.instance, id (self))
+
+
+  # ---------------------------------------------------------------------------
+  # Start the server
+  # ---------------------------------------------------------------------------
+
+  def _serve_ (self):
+
+    self._tcpServer.serve_forever ()
+
+
+
+# =============================================================================
+# Hessian Request handler
+# =============================================================================
+
+class HessianRequestHandler (http.HTTPRequestHandler):
+  """
+  Handle Hessian requests sent via HTTP connections.
+
+  @cvar protocol_version: Set to 'HTTP/1.1' so we do have persistent
+    connections.
+  """
+
+  # Make sure to support persistent connections
+  protocol_version = "HTTP/1.1"
+
+
+  # ---------------------------------------------------------------------------
+  # log all requests at debug level 9
+  # ---------------------------------------------------------------------------
+
+  def log_request (self, code = '-', size = '-'):
+    """
+    Log all requests at debug level 9.
+    """
+
+    assert gDebug (9, "'%s' %s %s" % (self.requestline, code, size))
+     
+
+  # ---------------------------------------------------------------------------
+  # Process a POST request
+  # ---------------------------------------------------------------------------
+
+  def do_POST (self):
+    """
+    Process a Hessian request. Exceptions are reported by L{hessianlib.Fault}
+    instances. Such an instance carries a string consisting of the group, name,
+    message and traceback of the exception, separated by the unicode character
+    u'0x91' (see L{errors.getException}). The underlying connection will be
+    closed only if stated by the headers (e.g. 'Connection: close')
+    """
+
+    try:
+      data = self.rfile.read (int (self.headers ["content-length"]))
+
+      params, method = hessianlib.loads (data)
+
+      response = self.server.serverAdapter.call (self.client_address, method,
+          params)
+      response = (response,)
+
+      response = hessianlib.dumps (response, methodresponse = 1)
+
+    except:
+      stack  = u"\x91".join (errors.getException ())
+      response = hessianlib.Fault (1, stack)
+      response = hessianlib.dumps (response, methodresponse = 1)
+
+    # Add the following data to the send-queue, but don't write it to the
+    # socket
+    self.send_response (200, flush = False)
+    self.send_header ("Content-length", str (len (response)), flush = False)
+    self.end_headers (flush = False)
+
+    # Add the response to the send-queue and finally flush everything to the
+    # socket.
+    self.write (response, flush = True)
+
+    # If a shutdown of the connection is requested do so, although we assume to
+    # have a persistent connection.
+    if self.close_connection:
+      self.connection.shutdown (1)
+
+
+
+# =============================================================================
+# Hessian TCP server
+# =============================================================================
+
+class HessianServer (SimpleHessianServer):
+  """
+  A TCP server implementing a Hessian server. This class verifies each
+  connection against a list of 'allowed hosts' and if it is not listed, an
+  error 403 (Forbidden) is returned to the client. If the owning
+  L{ServerAdapter} does not provide such a list, all clients are accepted.
+  These verification takes place *before* a new process for the connection is
+  forked or a new thread is started.
+
+  @ivar allow_reuse_address: if True, the port can be reused even if it's in
+    TIME_WAIT state
+  """
+
+  allow_reuse_address = True
+
+  # ---------------------------------------------------------------------------
+  # Constructor
+  # ---------------------------------------------------------------------------
+
+  def __init__ (self, addr, requestHandler = HessianRequestHandler,
+      logRequests = False, adapter = None):
+
+    SimpleHessianServer.__init__ (self, addr, requestHandler, logRequests)
+    self.serverAdapter = adapter
+
+
+  # ---------------------------------------------------------------------------
+  # Verify a given request
+  # ---------------------------------------------------------------------------
+
+  def verify_request (self, request, client_address):
+    """
+    Verify if the given client connection is allowed to use the services.
+
+    @param request: the already opened socket object
+    @param client_address: tuple with the address and port of the client
+
+    @returns: True, if the client is accepted, False if it is not allowed to
+      use the services. In the latter case a '403' error response will be sent
+      to the socket.
+    """
+
+    assert gEnter (9)
+
+    allowed = self.serverAdapter and self.serverAdapter.allowed_hosts
+    for host in allowed:
+      if client_address [0][:len (host)] == host:
+        assert gLeave (9, True)
+        return True
+
+    request.send ("HTTP/1.1 403 Forbidden\r\n\r\n")
+    self.close_request (request)
+
+    assert gLeave (9, False)
+    return False
+
+
+  # ---------------------------------------------------------------------------
+  # A connection to a given socket has been closed
+  # ---------------------------------------------------------------------------
+
+  def close_request (self, request):
+
+    if self.serverAdapter:
+      self.serverAdapter._clearClientObjects (request.getpeername ())
+
+    SimpleHessianServer.close_request (self, request)
+
+
+
+# =============================================================================
+# A forking Hessian server
+# =============================================================================
+
+class ForkingHessianServer (SocketServer.ForkingMixIn, HessianServer):
+  """
+  A Hessian server which forks a new process per connection.
+  """
+  pass
+
+
+# =============================================================================
+# A threading Hessian server
+# =============================================================================
+
+class ThreadingHessianServer (SocketServer.ThreadingMixIn, HessianServer):
+  """
+  A Hessian server which starts a new thread per connection.
+  """
+  pass


Property changes on: trunk/gnue-common/src/rpc/drivers/hessian/ServerAdapter.py
___________________________________________________________________
Name: svn:keywords
   + Id

Added: trunk/gnue-common/src/rpc/drivers/hessian/SimpleHessianServer.py
===================================================================
--- trunk/gnue-common/src/rpc/drivers/hessian/SimpleHessianServer.py    
2006-05-18 12:46:51 UTC (rev 8470)
+++ trunk/gnue-common/src/rpc/drivers/hessian/SimpleHessianServer.py    
2006-05-18 13:35:42 UTC (rev 8471)
@@ -0,0 +1,442 @@
+# Credits: SimpleHessianServer.py was inspired by and based on
+# SimpleXMLRPCServer.py written by Brian Quinlan (address@hidden)
+
+import hessianlib
+from hessianlib import Fault
+import SocketServer
+import BaseHTTPServer
+import sys
+import os
+
+__version__ = "0.2"
+
+
+def resolve_dotted_attribute(obj, attr, allow_dotted_names=True):
+    """resolve_dotted_attribute(a, 'b.c.d') => a.b.c.d
+
+    Resolves a dotted attribute name to an object.  Raises
+    an AttributeError if any attribute in the chain starts with a '_'.
+
+    If the optional allow_dotted_names argument is false, dots are not
+    supported and this function operates similar to getattr(obj, attr).
+    """
+
+    if allow_dotted_names:
+        attrs = attr.split(".")
+    else:
+        attrs = [attr]
+
+    for i in attrs:
+        if i.startswith("_"):
+            raise AttributeError(
+                "attempt to access private attribute '%s'" % i
+                )
+        else:
+            obj = getattr(obj,i)
+    return obj
+
+def list_public_methods(obj):
+    """Returns a list of attribute strings, found in the specified
+    object, which represent callable attributes"""
+
+    return [member for member in dir(obj)
+                if not member.startswith("_") and
+                    callable(getattr(obj, member))]
+
+def remove_duplicates(lst):
+    """remove_duplicates([2,2,2,1,3,3]) => [3,1,2]
+
+    Returns a copy of a list without duplicates. Every list
+    item must be hashable and the order of the items in the
+    resulting list is not defined.
+    """
+    u = {}
+    for x in lst:
+        u[x] = 1
+
+    return u.keys()
+
+class SimpleHessianDispatcher:
+    """Mix-in class that dispatches Hessian requests.
+
+    This class is used to register Hessian method handlers
+    and then to dispatch them. There should never be any
+    reason to instantiate this class directly.
+    """
+
+    def __init__(self):
+        self.funcs = {}
+        self.instance = None
+
+    def register_instance(self, instance, allow_dotted_names=False):
+        """Registers an instance to respond to Hessian requests.
+
+        Only one instance can be installed at a time.
+
+        If the registered instance has a _dispatch method then that
+        method will be called with the name of the Hessian method and
+        its parameters as a tuple
+        e.g. instance._dispatch('add',(2,3))
+
+        If the registered instance does not have a _dispatch method
+        then the instance will be searched to find a matching method
+        and, if found, will be called. Methods beginning with an '_'
+        are considered private and will not be called by
+        SimpleHessianServer.
+
+        If a registered function matches a Hessian request, then it
+        will be called instead of the registered instance.
+
+        If the optional allow_dotted_names argument is true and the
+        instance does not have a _dispatch method, method names
+        containing dots are supported and resolved, as long as none of
+        the name segments start with an '_'.
+
+            *** SECURITY WARNING: ***
+
+            Enabling the allow_dotted_names options allows intruders
+            to access your module's global variables and may allow
+            intruders to execute arbitrary code on your machine.  Only
+            use this option on a secure, closed network.
+
+        """
+
+        self.instance = instance
+        self.allow_dotted_names = allow_dotted_names
+
+    def register_function(self, function, name = None):
+        """Registers a function to respond to Hessian requests.
+
+        The optional name argument can be used to set a Unicode name
+        for the function.
+        """
+
+        if name is None:
+            name = function.__name__
+        self.funcs[name] = function
+
+    def register_introspection_functions(self):
+        """Registers the Hessian introspection methods in the system
+        namespace.
+        """
+
+        self.funcs.update({"system.listMethods": self.system_listMethods,
+                           "system.methodSignature": 
self.system_methodSignature,
+                           "system.methodHelp": self.system_methodHelp})
+
+    def register_multicall_functions(self):
+        """Registers the Hessian multicall method in the system
+        namespace."""
+
+        self.funcs.update({"system.multicall": self.system_multicall})
+
+    def _marshaled_dispatch(self, data, dispatch_method = None):
+        """Dispatches an Hessian method from marshalled (Hessian) data.
+
+        Hessian methods are dispatched from the marshalled (Hessian) data
+        using the _dispatch method and the result is returned as
+        marshalled data. For backwards compatibility, a dispatch
+        function can be provided as an argument (see comment in
+        SimpleHessianRequestHandler.do_POST) but overriding the
+        existing method through subclassing is the prefered means
+        of changing method dispatch behavior.
+        """
+
+        params, method = hessianlib.loads(data)
+
+        # generate response
+        try:
+            if dispatch_method is not None:
+                response = dispatch_method(method, params)
+            else:
+                response = self._dispatch(method, params)
+            # wrap response in a singleton tuple
+            response = (response,)
+            response = hessianlib.dumps(response, methodresponse=1)
+        except Fault, fault:
+            response = hessianlib.dumps(fault)
+        except:
+            # report exception back to server
+            response = hessianlib.dumps(
+                hessianlib.Fault(1, "%s:%s" % (sys.exc_type, sys.exc_value))
+                )
+
+        return response
+
+    def system_listMethods(self):
+        """system.listMethods() => ['add', 'subtract', 'multiple']
+
+        Returns a list of the methods supported by the server."""
+
+        methods = self.funcs.keys()
+        if self.instance is not None:
+            # Instance can implement _listMethod to return a list of
+            # methods
+            if hasattr(self.instance, "_listMethods"):
+                methods = remove_duplicates(
+                        methods + self.instance._listMethods()
+                    )
+            # if the instance has a _dispatch method then we
+            # don't have enough information to provide a list
+            # of methods
+            elif not hasattr(self.instance, "_dispatch"):
+                methods = remove_duplicates(
+                        methods + list_public_methods(self.instance)
+                    )
+        methods.sort()
+        return methods
+
+    def system_methodSignature(self, method_name):
+        """system.methodSignature('add') => [double, int, int]
+
+        Returns a list describing the signature of the method. In the
+        above example, the add method takes two integers as arguments
+        and returns a double result.
+
+        This server does NOT support system.methodSignature."""
+
+        return "signatures not supported"
+
+    def system_methodHelp(self, method_name):
+        """system.methodHelp('add') => "Adds two integers together"
+
+        Returns a string containing documentation for the specified method."""
+
+        method = None
+        if self.funcs.has_key(method_name):
+            method = self.funcs[method_name]
+        elif self.instance is not None:
+            # Instance can implement _methodHelp to return help for a method
+            if hasattr(self.instance, "_methodHelp"):
+                return self.instance._methodHelp(method_name)
+            # if the instance has a _dispatch method then we
+            # don't have enough information to provide help
+            elif not hasattr(self.instance, "_dispatch"):
+                try:
+                    method = resolve_dotted_attribute(
+                                self.instance,
+                                method_name,
+                                self.allow_dotted_names
+                                )
+                except AttributeError:
+                    pass
+
+        # Note that we aren't checking that the method actually
+        # be a callable object of some kind
+        if method is None:
+            return ""
+        else:
+            import pydoc
+            return pydoc.getdoc(method)
+
+    def system_multicall(self, call_list):
+        """system.multicall([{'methodName': 'add', 'params': [2, 2]}, ...]) => 
\
+[[4], ...]
+
+        Allows the caller to package multiple Hessian calls into a single
+        request.
+        """
+
+        results = []
+        for call in call_list:
+            method_name = call["methodName"]
+            params = call["params"]
+
+            try:
+                # XXX A marshalling error in any response will fail the entire
+                # multicall. If someone cares they should fix this.
+                results.append([self._dispatch(method_name, params)])
+            except Fault, fault:
+                results.append(
+                    {"code": fault.code,
+                     "message": fault.message}
+                    )
+            except:
+                results.append(
+                    {"code": 1,
+                     "message": "%s:%s" % (sys.exc_type, sys.exc_value)}
+                    )
+        return results
+
+    def _dispatch(self, method, params):
+        """Dispatches the Hessian method.
+
+        Hessian calls are forwarded to a registered function that
+        matches the called Hessian method name. If no such function
+        exists then the call is forwarded to the registered instance,
+        if available.
+
+        If the registered instance has a _dispatch method then that
+        method will be called with the name of the Hessian method and
+        its parameters as a tuple
+        e.g. instance._dispatch('add',(2,3))
+
+        If the registered instance does not have a _dispatch method
+        then the instance will be searched to find a matching method
+        and, if found, will be called.
+
+        Methods beginning with an '_' are considered private and will
+        not be called.
+        """
+
+        func = None
+        try:
+            # check to see if a matching function has been registered
+            func = self.funcs[method]
+        except KeyError:
+            if self.instance is not None:
+                # check for a _dispatch method
+                if hasattr(self.instance, "_dispatch"):
+                    return self.instance._dispatch(method, params)
+                else:
+                    # call instance method directly
+                    try:
+                        func = resolve_dotted_attribute(
+                            self.instance,
+                            method,
+                            self.allow_dotted_names
+                            )
+                    except AttributeError:
+                        pass
+
+        if func is not None:
+            return func(*params)
+        else:
+            raise Exception("method '%s' is not supported" % method)
+
+class SimpleHessianRequestHandler(BaseHTTPServer.BaseHTTPRequestHandler):
+    """Simple Hessian request handler class.
+
+    Handles all HTTP POST requests and attempts to decode them as
+    Hessian requests.
+    """
+    server_version = "SimpleHessian/%s" % __version__
+
+    def do_POST(self):
+        """Handles the HTTP POST request.
+
+        Attempts to interpret all HTTP POST requests as Hessian calls,
+        which are forwarded to the server's _dispatch method for handling.
+        """
+
+        try:
+            # get arguments
+            data = self.rfile.read(int(self.headers["content-length"]))
+            # In previous versions of SimpleHessianServer, _dispatch
+            # could be overridden in this class, instead of in
+            # SimpleHessianDispatcher. To maintain backwards compatibility,
+            # check to see if a subclass implements _dispatch and dispatch
+            # using that method if present.
+            response = self.server._marshaled_dispatch(
+                    data, getattr(self, "_dispatch", None)
+                )
+        except: # This should only happen if the module is buggy
+            # internal error, report as HTTP server error
+            self.send_response(500)
+            self.end_headers()
+        else:
+            # got a valid Hessian RPC response
+            self.send_response(200)
+            self.send_header("Content-length", str(len(response)))
+            self.end_headers()
+            self.wfile.write(response)
+
+            # shut down the connection
+            self.wfile.flush()
+            self.connection.shutdown(1)
+
+    def log_request(self, code="-", size="-"):
+        """Selectively log an accepted request."""
+
+        if self.server.logRequests:
+            BaseHTTPServer.BaseHTTPRequestHandler.log_request(self, code, size)
+
+class SimpleHessianServer(SocketServer.TCPServer, SimpleHessianDispatcher):
+    """Simple Hessian server.
+
+    Simple Hessian server that allows functions and a single instance
+    to be installed to handle requests. The default implementation
+    attempts to dispatch Hessian calls to the functions or instance
+    installed in the server. Override the _dispatch method inhereted
+    from SimpleHessianDispatcher to change this behavior.
+    """
+
+    def __init__(self, addr, requestHandler=SimpleHessianRequestHandler,
+                 logRequests=0):
+        self.logRequests = logRequests
+
+        SimpleHessianDispatcher.__init__(self)
+        SocketServer.TCPServer.__init__(self, addr, requestHandler)
+
+class CGIHessianRequestHandler(SimpleHessianDispatcher):
+    """Simple handler for Hessian data passed through CGI."""
+
+    def __init__(self):
+        SimpleHessianDispatcher.__init__(self)
+
+    def handle_hessian(self, request_text):
+        """Handle a single Hessian request"""
+
+        response = self._marshaled_dispatch(request_text)
+
+        print "Content-Length: %d" % len(response)
+        print
+        sys.stdout.write(response)
+
+    def handle_get(self):
+        """Handle a single HTTP GET request.
+
+        Default implementation indicates an error because
+        Hessian uses the POST method.
+        """
+
+        code = 400
+        message, explain = \
+                 BaseHTTPServer.BaseHTTPRequestHandler.responses[code]
+
+        response = BaseHTTPServer.DEFAULT_ERROR_MESSAGE % \
+            {
+             "code": code,
+             "message": message,
+             "explain": explain
+            }
+        print "Status: %d %s" % (code, message)
+        print "Content-Type: text/html"
+        print "Content-Length: %d" % len(response)
+        print
+        sys.stdout.write(response)
+
+    def handle_request(self, request_text = None):
+        """Handle a single Hessian request passed through a CGI post method.
+
+        If no Hessian data is given then it is read from stdin. The resulting
+        Hessian response is printed to stdout along with the correct HTTP
+        headers.
+        """
+
+        if request_text is None and \
+            os.environ.get("REQUEST_METHOD", None) == "GET":
+            self.handle_get()
+        else:
+            # POST data is normally available through stdin
+            if request_text is None:
+                request_text = sys.stdin.read()
+
+            self.handle_hessian(request_text)
+
+if __name__ == '__main__':
+
+    def raiseErr(code, message):
+        raise hessianlib.Fault(code, message)
+
+    def echo(param):
+        return param
+
+    server = SimpleHessianServer(("", 7000), logRequests=1)
+    server.register_function(pow)
+    server.register_function(lambda x,y: x+y, 'add')
+    server.register_function(lambda: "Hello, World", 'hello')
+    server.register_function(echo)
+    server.register_function(raiseErr)
+    server.register_introspection_functions()
+    server.serve_forever()


Property changes on: 
trunk/gnue-common/src/rpc/drivers/hessian/SimpleHessianServer.py
___________________________________________________________________
Name: svn:keywords
   + Id

Added: trunk/gnue-common/src/rpc/drivers/hessian/__init__.py
===================================================================
--- trunk/gnue-common/src/rpc/drivers/hessian/__init__.py       2006-05-18 
12:46:51 UTC (rev 8470)
+++ trunk/gnue-common/src/rpc/drivers/hessian/__init__.py       2006-05-18 
13:35:42 UTC (rev 8471)
@@ -0,0 +1,26 @@
+# GNU Enterprise Common Library - RPC library - hessian driver
+#
+# Copyright 2001-2006 Free Software Foundation
+#
+# This file is part of GNU Enterprise
+#
+# GNU Enterprise is free software; you can redistribute it
+# and/or modify it under the terms of the GNU General Public
+# License as published by the Free Software Foundation; either
+# version 2, or (at your option) any later version.
+#
+# GNU Enterprise is distributed in the hope that it will be
+# useful, but WITHOUT ANY WARRANTY; without even the implied
+# warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
+# PURPOSE. See the GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public
+# License along with program; see the file COPYING. If not,
+# write to the Free Software Foundation, Inc., 59 Temple Place
+# - Suite 330, Boston, MA 02111-1307, USA.
+#
+# $Id$
+
+"""
+GNUe RPC driver using hessian
+"""


Property changes on: trunk/gnue-common/src/rpc/drivers/hessian/__init__.py
___________________________________________________________________
Name: svn:keywords
   + Id

Added: trunk/gnue-common/src/rpc/drivers/hessian/hessianlib.py
===================================================================
--- trunk/gnue-common/src/rpc/drivers/hessian/hessianlib.py     2006-05-18 
12:46:51 UTC (rev 8470)
+++ trunk/gnue-common/src/rpc/drivers/hessian/hessianlib.py     2006-05-18 
13:35:42 UTC (rev 8471)
@@ -0,0 +1,782 @@
+# Credits: hessianlib.py was inspired by and based on
+# xmlrpclib.py created by Fredrik Lundh at www.pythonware.org
+
+import time, operator
+
+from types import *
+from struct import unpack, pack
+
+from cStringIO import StringIO
+
+# --------------------------------------------------------------------
+# Internal stuff
+try:
+    _bool_is_builtin = False.__class__.__name__ == "bool"
+except NameError:
+    _bool_is_builtin = 0
+
+__version__ = "0.2"
+
+# --------------------------------------------------------------------
+# Exceptions
+
+##
+# Base class for all kinds of client-side errors.
+
+class Error(Exception):
+    """Base class for client errors."""
+    def __str__(self):
+        return repr(self)
+
+##
+# Indicates an HTTP-level protocol error.  This is raised by the HTTP
+# transport layer, if the server returns an error code other than 200
+# (OK).
+#
+# @param url The target URL.
+# @param status Status code returned by server.
+# @param reason Reason phrase returned by server.
+# @param msg A mimetools.Message instance containing the response headers.
+
+class ProtocolError(Error):
+    """Indicates an HTTP protocol error."""
+    def __init__(self, url, status, reason, msg):
+        Error.__init__(self)
+        self.url = url
+        self.status = status
+        self.reason = reason
+        self.msg = msg
+    def __repr__(self):
+        return (
+            "<ProtocolError for %s: %s %s>" %
+            (self.url, self.status, self.reason)
+            )
+
+##
+# Indicates a broken Hessian response package.  This exception is
+# raised by the unmarshalling layer, if the Hessian response is
+# malformed.
+
+class ResponseError(Error):
+    """Indicates a broken response package."""
+    pass
+
+##
+# Indicates an Hessian fault response package.  This exception is
+# raised by the unmarshalling layer, if the Hessian response contains
+# a fault.  This exception can also used as a class,
+# to generate a fault Hessian message.
+#
+# @param code The Hessian fault code.
+# @param message The Hessian fault string.
+
+class Fault(Error):
+    """Indicates an Hessian fault package."""
+    def __init__(self, code, message, **detail):
+        Error.__init__(self)
+        self.code = code
+        self.message = message
+    def __repr__(self):
+        return (
+            "<Fault %s: %s>" %
+            (self.code, repr(self.message))
+            )
+
+# --------------------------------------------------------------------
+# Special values
+
+##
+# Wrapper for Hessian boolean values.  Use the hessianlib.True and
+# hessianlib.False constants to generate boolean Hessian values.
+#
+# @param value A boolean value.  Any true value is interpreted as True,
+#              all other values are interpreted as False.
+
+if _bool_is_builtin:
+    boolean = Boolean = bool
+    True, False = True, False
+else:
+    class Boolean:
+        """Boolean-value wrapper.
+
+        Use True or False to generate a "boolean" Hessian value.
+        """
+
+        def __init__(self, value=0):
+            self.value = operator.truth(value)
+
+        def __cmp__(self, other):
+            if isinstance(other, Boolean):
+                other = other.value
+            return cmp(self.value, other)
+
+        def __repr__(self):
+            if self.value:
+                return "<Boolean True at %x>" % id(self)
+            else:
+                return "<Boolean False at %x>" % id(self)
+
+        def __int__(self):
+            return self.value
+
+        def __nonzero__(self):
+            return self.value
+
+        def encode(self, out):
+            if self.value:
+                out.append("T")
+            else:
+                out.append("F")
+
+    True, False = Boolean(1), Boolean(0)
+
+    ##
+    # Map true or false value to Hessian boolean values.
+    #
+    # @def boolean(value)
+    # @param value A boolean value.  Any true value is mapped to True,
+    #              all other values are mapped to False.
+    # @return hessianlib.True or hessianlib.False.
+    # @see Boolean
+    # @see True
+    # @see False
+
+    def boolean(value, _truefalse=(False, True)):
+        """Convert any Python value to Hessian 'boolean'."""
+        return _truefalse[operator.truth(value)]
+
+##
+# Wrapper for Hessian DateTime values.  This converts a time value to
+# the format used by Hessian.
+# <p>
+# The value must be given as the number of seconds since epoch (UTC).
+
+class DateTime:
+    """DateTime wrapper for a datetime integer value
+    to generate Hessian value.
+    """
+
+    def __init__(self, value=0):
+        self.value = int(value)
+
+    def __cmp__(self, other):
+        if isinstance(other, DateTime):
+            other = other.value
+        return cmp(self.value, other)
+
+    def __str__(self):
+        return time.strftime("%Y-%m-%d %H:%M:%S", self.timetuple())
+
+    def __repr__(self):
+        return "<DateTime %s at %x>" % (self, id(self))
+
+    def timetuple(self):
+        return time.gmtime(self.value)
+
+    def encode(self, out):
+        out.append("d")
+        out.append(pack(">q", self.value * 1000))
+
+##
+# Wrapper for binary data.  This can be used to transport any kind
+# of binary data over Hessian.
+#
+# @param data Raw binary data.
+
+class Binary:
+    """Wrapper for binary data."""
+
+    def __init__(self, data=None):
+        self.data = data
+
+    def __cmp__(self, other):
+        if isinstance(other, Binary):
+            other = other.data
+        return cmp(self.data, other)
+
+    def __repr__(self):
+        return "<Binary at %x>" % (id(self))
+
+    def encode(self, out):
+        out.append("B")
+        out.append(pack(">H", len(self.data)))
+        out.append(self.data)
+
+WRAPPERS = (DateTime, Binary)
+if not _bool_is_builtin:
+    WRAPPERS = WRAPPERS + (Boolean,)
+
+# --------------------------------------------------------------------
+# Hessian marshalling and unmarshalling code
+
+##
+# Hessian marshaller.
+#
+# @see dumps
+
+class Marshaller:
+    """Generate an Hessian params chunk from a Python data structure.
+
+    Create a Marshaller instance for each set of parameters, and use
+    the "dumps" method to convert your data (represented as a tuple)
+    to an Hessian params chunk.  To write a fault response, pass a
+    Fault instance instead.  You may prefer to use the "dumps" module
+    function for this purpose.
+    """
+
+    def __init__(self):
+        self.stack = []
+        self.append = self.stack.append #write
+
+    dispatch = {}
+
+    def dumps(self, values):
+        if isinstance(values, Fault):
+            # fault instance
+            self.append("f")
+            self.dump("code")
+            self.dump(values.code)
+            self.dump("message")
+            self.dump(values.message)
+        else:
+            for v in values:
+                self.dump(v)
+        result = "".join(self.stack)
+        del self.append, self.stack
+        return result
+
+    def dump(self, value):
+        try:
+            func = self.dispatch[type(value)]
+            func(self, value)
+        except KeyError:
+            raise TypeError, "cannot marshal %s objects" % type(value)
+
+    def dump_nil (self, value):
+        self.append("N")
+    dispatch[NoneType] = dump_nil
+
+    if _bool_is_builtin:
+        def dump_bool(self, value):
+            self.append(value and "T" or "F")
+        dispatch[bool] = dump_bool
+
+    def dump_int(self, value):
+        self.append("I")
+        self.append(pack(">l", value))
+    dispatch[IntType] = dump_int
+
+    def dump_long(self, value):
+        self.append("L")
+        self.append(pack(">q", value))
+    dispatch[LongType] = dump_long
+
+    def dump_double(self, value):
+        self.append("D")
+        self.append(pack(">d", value))
+    dispatch[FloatType] = dump_double
+
+    def dump_string(self, value):
+        self.append("S")
+        self.append(pack(">H", len(value)))
+        self.append(value)
+    dispatch[StringType] = dump_string
+
+    def dump_unicode(self, value):
+        value = value.encode("utf-8")
+        self.append("S")
+        self.append(pack(">H", len(value)))
+        self.append(value)
+    dispatch[UnicodeType] = dump_unicode
+
+    def dump_array(self, value):
+        self.append("V")
+        for v in value:
+            self.dump(v)
+        self.append("z")
+    dispatch[TupleType] = dump_array
+    dispatch[ListType] = dump_array
+
+    def dump_struct(self, value):
+        self.append("M")
+        for k, v in value.items():
+            if type(k) not in (StringType, UnicodeType):
+                raise TypeError, "dictionary key must be string" 
+            self.dump(k)
+            self.dump(v)
+        self.append("z")
+    dispatch[DictType] = dump_struct
+
+    def dump_instance(self, value):
+        # check for special wrappers
+        if value.__class__ in WRAPPERS:
+            value.encode(self)
+        else:
+            # store instance attributes as a struct (really?)
+            self.dump_struct(value.__dict__)
+    dispatch[InstanceType] = dump_instance
+
+##
+# Hessian unmarshaller.
+#
+# @see loads
+
+class Unmarshaller:
+    """Unmarshal an Hessian response. Call close() to get the resulting
+    data structure.
+    """
+
+    def __init__(self):
+        self.stack = []
+        self.append = self.stack.append #write
+        self.typ = None
+        self.method = None
+
+    dispatch = {}
+
+    def loads(self, data):
+        data = StringIO(data)
+        typ = data.read(1)
+        if typ in ("c", "r"):
+            major = data.read(1)
+            minor = data.read(1)
+            typ = data.read(1)
+        while typ not in ("z", ""):
+            self.load(typ, data)
+            typ = data.read(1)
+        data.close()
+
+    def load(self, typ, data):
+        if typ in ("m", "f"):
+            self.__load(typ, data)
+        else:
+            self.append(self.__load(typ, data))
+
+    def __load(self, typ, data):
+        try:
+            func = self.dispatch[typ]
+            return func(self, data)
+        except KeyError:
+            raise TypeError, "cannot unmarshal %s objects" % typ
+
+    def close(self):
+        if self.typ == "f":
+            raise Fault(**self.stack[0])
+        result = tuple(self.stack)
+        del self.append, self.stack
+        return result
+
+    def getmethodname(self):
+        return self.method
+
+    dispatch = {}
+
+    def load_nil (self, data):
+        return None
+    dispatch["N"] = load_nil
+
+    def load_true(self, data):
+        return True
+    dispatch["T"] = load_true
+
+    def load_false(self, data):
+        return False
+    dispatch["F"] = load_false
+
+    def load_int(self, data):
+        return unpack(">l", data.read(4))[0]
+    dispatch["I"] = load_int
+
+    def load_long(self, data):
+        return unpack(">q", data.read(8))[0]
+    dispatch["L"] = load_long
+
+    def load_double(self, data):
+        return unpack(">d", data.read(8))[0]
+    dispatch["D"] = load_double
+
+    def load_string(self, data):
+        res = data.read(unpack(">H", data.read(2))[0]).decode("utf-8")
+        try:
+            return res.encode("ascii")
+        except UnicodeError:
+            return res
+    dispatch["S"] = load_string
+
+    def load_array(self, data):
+        res = []
+        typ = data.read(1)
+        while typ != "z":
+            res.append(self.__load(typ, data))
+            typ = data.read(1)
+        return res
+    dispatch["V"] = load_array
+
+    def load_struct(self, data):
+        res = {}
+        typ = data.read(1)
+        while typ != "z":
+            k = self.__load(typ, data)
+            v = self.__load(data.read(1), data)
+            res[k] = v
+            typ = data.read(1)
+        return res
+    dispatch["M"] = load_struct
+
+    def load_binary(self, data):
+        return Binary(data.read(unpack(">H", data.read(2))[0]))
+    dispatch["B"] = load_binary
+
+    def load_datetime(self, data):
+        return DateTime(unpack(">q", data.read(8))[0] / 1000)
+    dispatch["d"] = load_datetime
+
+    def load_fault(self, data):
+        fault = {}
+        typ = data.read(1)
+        while typ != "z":
+            k = self.__load(typ, data)
+            v = self.__load(data.read(1), data)
+            fault[k] = v
+            typ = data.read(1)
+        self.stack = [fault]
+        self.typ = "f"
+    dispatch["f"] = load_fault
+
+    def load_method(self, data):
+        res = data.read(unpack(">H", data.read(2))[0]).decode("utf-8")
+        try:
+            self.method = res.encode("ascii")
+        except UnicodeError:
+            self.method = res
+    dispatch["m"] = load_method
+
+## Multicall support
+#
+
+class _MultiCallMethod:
+    # some lesser magic to store calls made to a MultiCall object
+    # for batch execution
+    def __init__(self, call_list, name):
+        self.__call_list = call_list
+        self.__name = name
+    def __getattr__(self, name):
+        return _MultiCallMethod(self.__call_list, "%s.%s" % (self.__name, 
name))
+    def __call__(self, *args):
+        self.__call_list.append((self.__name, args))
+
+class MultiCallIterator:
+    """Iterates over the results of a multicall. Exceptions are
+    thrown in response to hessian faults."""
+
+    def __init__(self, results):
+        self.results = results
+
+    def __getitem__(self, i):
+        item = self.results[i]
+        if type(item) == type({}):
+            raise Fault(item["code"], item["message"])
+        elif type(item) == type([]):
+            return item[0]
+        else:
+            raise ValueError,\
+                  "unexpected type in multicall result"
+
+class MultiCall:
+    """server -> a object used to boxcar method calls
+
+    server should be a ServerProxy object.
+
+    Methods can be added to the MultiCall using normal
+    method call syntax e.g.:
+
+    multicall = MultiCall(server_proxy)
+    multicall.add(2,3)
+    multicall.get_address("Guido")
+
+    To execute the multicall, call the MultiCall object e.g.:
+
+    add_result, address = multicall()
+    """
+
+    def __init__(self, server):
+        self.__server = server
+        self.__call_list = []
+
+    def __repr__(self):
+        return "<MultiCall at %x>" % id(self)
+
+    __str__ = __repr__
+
+    def __getattr__(self, name):
+        return _MultiCallMethod(self.__call_list, name)
+
+    def __call__(self):
+        marshalled_list = []
+        for name, args in self.__call_list:
+            marshalled_list.append({"methodName" : name, "params" : args})
+
+        return 
MultiCallIterator(self.__server.system.multicall(marshalled_list))
+
+# --------------------------------------------------------------------
+# convenience functions
+
+##
+# Convert a Python tuple or a Fault instance to an Hessian packet.
+#
+# @def dumps(params, **options)
+# @param params A tuple or Fault instance.
+# @keyparam methodname If given, create a call request for
+#     this method name.
+# @keyparam methodresponse If given, create a reply packet.
+#     If used with a tuple, the tuple must be a singleton (that is,
+#     it must contain exactly one element).
+# @return A string containing marshalled data.
+
+def dumps(params, methodname=None, methodresponse=None):
+    """data [,options] -> marshalled data
+
+    Convert an argument tuple or a Fault instance to an Hessian
+    request (or response, if the methodresponse option is used).
+
+    In addition to the data object, the following options can be given
+    as keyword arguments:
+
+        methodname: the method name for a call packet
+
+        methodresponse: true to create a reply packet.
+        If this option is used with a tuple, the tuple must be
+        a singleton (i.e. it can contain only one element).
+
+    Unicode strings are automatically converted, where necessary.
+    """
+
+    assert isinstance(params, TupleType) or isinstance(params, Fault),\
+           "argument must be tuple or Fault instance"
+
+    if isinstance(params, Fault):
+        methodresponse = 1
+    elif methodresponse and isinstance(params, TupleType):
+        assert len(params) == 1, "response tuple must be a singleton"
+
+    m = Marshaller()
+    data = m.dumps(params)
+
+    # standard Hessian wrappings
+    if methodname:
+        if isinstance(methodname, UnicodeType):
+            methodname = methodname.encode("utf-8")
+        data = ("c\x01\x00m", pack(">H", len(methodname)), methodname, data, 
"z")
+    elif methodresponse:
+        # a method response, or a fault structure
+        data = ("r\x01\x00", data, "z")
+    else:
+        return data # return as is
+    return "".join(data)
+
+##
+# Convert an Hessian packet to a Python object.  If the Hessian packet
+# represents a fault condition, this function raises a Fault exception.
+#
+# @param data An Hessian.
+# @return A tuple containing the unpacked data, and the method name
+#     (None if not present).
+# @see Fault
+
+def loads(data):
+    """data -> unmarshalled data, method name
+
+    Convert an Hessian packet to unmarshalled data plus a method
+    name (None if not present).
+
+    If the Hessian packet represents a fault condition, this function
+    raises a Fault exception.
+    """
+    u = Unmarshaller()
+    u.loads(data)
+
+    return u.close(), u.getmethodname()
+
+
+# --------------------------------------------------------------------
+# request dispatcher
+
+class _Method:
+    # some magic to bind an Hessian method to an RPC server.
+    # supports "nested" methods (e.g. examples.getStateName)
+    def __init__(self, send, name):
+        self.__send = send
+        self.__name = name
+    def __getattr__(self, name):
+        return _Method(self.__send, "%s.%s" % (self.__name, name))
+    def __call__(self, *args):
+        return self.__send(self.__name, args)
+
+##
+# Standard transport class for Hessian over HTTP.
+# <p>
+# You can create custom transports by subclassing this method, and
+# overriding selected methods.
+
+class Transport:
+    """Handles an HTTP transaction to an Hessian server."""
+
+    # client identifier (may be overridden)
+    user_agent = "hessianlib.py/%s" % __version__
+
+    def __init__ (self):
+        self.__connection = None
+
+    ##
+    # Send a complete request, and parse the response.
+    #
+    # @param host Target host.
+    # @param handler Target PRC handler.
+    # @param request_body Hessian request body.
+    # @param verbose Debugging flag.
+    # @return Parsed response.
+
+    def request(self, host, handler, request_body, verbose=0):
+        if not self.__connection:
+            self.__connection = self.make_connection(host)
+        self.__connection.set_debuglevel (verbose)
+        self.__connection.request ("POST", handler, request_body)
+        response = self.__connection.getresponse ()
+        if response:
+            if response.status != 200:
+                raise ProtocolError (host + handler, response.status,
+                                     response.reason, response.msg)
+            u = Unmarshaller()
+            u.loads(response.read())
+            result = u.close()
+        else:
+            result = None
+        return result
+
+    ##
+    # Connect to server.
+    #
+    # @param host Target host.
+    # @return A connection handle.
+
+    def make_connection(self, host):
+        import httplib
+        return httplib.HTTPConnection(host)
+
+##
+# Standard transport class for Hessian over HTTPS.
+
+class SafeTransport(Transport):
+    """Handles an HTTPS transaction to an Hessian server."""
+
+    # FIXME: mostly untested
+
+    def make_connection(self, host):
+        import httplib
+        return httplib.HTTPSConnection(host)
+
+##
+# Standard server proxy.  This class establishes a virtual connection
+# to an Hessian server.
+# <p>
+# This class is available as ServerProxy and Server.  New code should
+# use ServerProxy, to avoid confusion.
+#
+# @def ServerProxy(uri, **options)
+# @param uri The connection point on the server.
+# @keyparam transport A transport factory, compatible with the
+#    standard transport class.
+# @keyparam verbose Use a true value to enable debugging output.
+#    (printed to standard output).
+# @see Transport
+
+class ServerProxy:
+    """uri [,options] -> a logical connection to an Hessian server
+
+    uri is the connection point on the server, given as
+    scheme://host/target.
+
+    The standard implementation always supports the "http" scheme.  If
+    SSL socket support is available (Python 2.0), it also supports
+    "https".
+
+    If the target part and the slash preceding it are both omitted,
+    "/Hessian" is assumed.
+
+    The following options can be given as keyword arguments:
+
+        transport: a transport factory
+    """
+
+    def __init__(self, uri, transport=None, verbose=0):
+        # establish a "logical" server connection
+
+        # get the url
+        import urllib
+        type, uri = urllib.splittype(uri)
+        if type not in ("http", "https"):
+            raise IOError, "unsupported Hessian protocol"
+        self.__host, self.__handler = urllib.splithost(uri)
+        if not self.__handler:
+            self.__handler = "/Hessian"
+
+        if transport is None:
+            if type == "https":
+                transport = SafeTransport()
+            else:
+                transport = Transport()
+        self.__transport = transport
+
+        self.__verbose = verbose
+
+    def __request(self, methodname, params):
+        # call a method on the remote server
+
+        request = dumps(params, methodname)
+
+        response = self.__transport.request(
+            self.__host,
+            self.__handler,
+            request,
+            verbose=self.__verbose
+            )
+
+        if len(response) == 1:
+            response = response[0]
+
+        return response
+
+    def __repr__(self):
+        return (
+            "<ServerProxy for %s%s>" %
+            (self.__host, self.__handler)
+            )
+
+    __str__ = __repr__
+
+    def __getattr__(self, name):
+        # magic method dispatcher
+        return _Method(self.__request, name)
+
+    # note: to call a remote object with an non-standard name, use
+    # result getattr(server, "strange-python-name")(args)
+
+# compatibility
+
+Server = ServerProxy
+
+# --------------------------------------------------------------------
+# test code
+
+if __name__ == "__main__":
+
+    server = ServerProxy("http://localhost:7000";, verbose=1)
+
+    try:
+        print server.hello()
+        print server.system.listMethods()
+    except Error, v:
+        print "ERROR", v
+
+    server.raiseErr(42, "don't panic")
+


Property changes on: trunk/gnue-common/src/rpc/drivers/hessian/hessianlib.py
___________________________________________________________________
Name: svn:keywords
   + Id

Added: trunk/gnue-common/src/rpc/drivers/hessian/typeconv.py
===================================================================
--- trunk/gnue-common/src/rpc/drivers/hessian/typeconv.py       2006-05-18 
12:46:51 UTC (rev 8470)
+++ trunk/gnue-common/src/rpc/drivers/hessian/typeconv.py       2006-05-18 
13:35:42 UTC (rev 8471)
@@ -0,0 +1,209 @@
+# GNU Enterprise Common Library - RPC interface - Un-/Marshalling
+#
+# Copyright 2001-2006 Free Software Foundation
+#
+# This file is part of GNU Enterprise.
+#
+# GNU Enterprise is free software; you can redistribute it
+# and/or modify it under the terms of the GNU General Public
+# License as published by the Free Software Foundation; either
+# version 2, or (at your option) any later version.
+#
+# GNU Enterprise is distributed in the hope that it will be
+# useful, but WITHOUT ANY WARRANTY; without even the implied
+# warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
+# PURPOSE. See the GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public
+# License along with program; see the file COPYING. If not,
+# write to the Free Software Foundation, Inc., 59 Temple Place
+# - Suite 330, Boston, MA 02111-1307, USA.
+#
+# $Id$
+
+import hessianlib
+import datetime
+from calendar import timegm
+
+epoch = datetime.datetime.utcfromtimestamp(0)
+
+# -----------------------------------------------------------------------------
+# Check wether a given item is an id-dictionary representing a server object
+# -----------------------------------------------------------------------------
+
+def is_rpc_object (value):
+  """
+  Check wether a given value is a structure representing a server object.
+
+  @param value: the python value to be checked
+  @returns: True for a remote server object, False otherwise.
+  """
+
+  if isinstance (value, dict):
+    k = value.keys ()
+    k.sort ()
+
+    result = (k == ['__id__', '__rpc_datatype__'])
+  else:
+    result = False
+
+  return result
+
+
+# -----------------------------------------------------------------------------
+# Convert native Python type to hessianrpc's type
+# -----------------------------------------------------------------------------
+
+def python_to_rpc (value, wrapObject, *wrapArgs):
+  """
+  Convert a value from native python type into a type acceptable to xmlrpc. 
+
+  The following type conversions are performed.
+
+  * None           -> None
+  * str            -> unicode
+  * unicode        -> unicode
+  * bool           -> hessianlib.boolean
+  * int/long/float -> int/long/float
+  * datetime.datetime/.date/.time -> hessianlib.DateTime
+
+  For lists and tuples the conversion will be applied to each element. For
+  dictionaries the conversion will be applied to the key as well as the value.
+
+  @param value: the native python value to be converted
+  @param wrapObject: if the value is not one of the base types to be converted,
+    this function will be used to wrap the value
+  """
+
+  if value is None:
+    return value
+
+  # None or String
+  if isinstance (value, str):
+    return unicode (value)
+
+  elif isinstance (value, unicode):
+    return value
+
+  # Boolean needs to be checked *before* <int>
+  elif isinstance (value, bool):
+    return hessianlib.boolean (value)
+
+  # Number
+  elif isinstance (value, (int, long, float)):
+    return value
+
+  # Date/Time
+  elif isinstance (value, datetime.datetime):
+    return hessianlib.DateTime (timegm (value.utctimetuple ()))
+
+  elif isinstance (value, datetime.date):
+    value = epoch.combine (value, epoch.time ())
+    return hessianlib.DateTime (timegm (value.utctimetuple ()))
+
+  elif isinstance (value, datetime.time):
+    value = epoch.combine (epoch.date (), value)
+    return hessianlib.DateTime (timegm (value.utctimetuple ()))
+
+  # List
+  elif isinstance (value, list):
+    return [python_to_rpc (element, wrapObject, *wrapArgs) for element in 
value]
+
+  # Tuple
+  elif isinstance (value, tuple):
+    return tuple ([python_to_rpc (element, wrapObject, *wrapArgs) \
+        for element in value])
+
+  # Dictionary
+  elif isinstance (value, dict):
+    result = {}
+
+    for (key, val) in value.items ():
+      if key is None:
+        key = ""
+
+      elif not isinstance (key, str):
+        key = python_to_rpc (key, wrapObject, *wrapArgs)
+
+      result [key] = python_to_rpc (val, wrapObject, *wrapArgs)
+
+    return result
+
+  elif wrapObject is not None:
+    return wrapObject (value, *wrapArgs)
+
+  else:
+    raise exception, repr (value)
+
+
+# -----------------------------------------------------------------------------
+# Convert xmlrpc's type to native Python type
+# -----------------------------------------------------------------------------
+
+def rpc_to_python (value, wrapObject, exception, *wrapArgs):
+  """
+  Convert a value from xmlrpc types into native python types.
+
+  @param value: xmlrpc value
+  @param wrapObject: function to be called, if L{is_rpc_object} returns True
+  @param exception: exception to be raised if no conversion is available
+
+  The following conversions are performed:
+
+  None               -> None
+  str                -> unicode or None (for empty strings)
+  unicode            -> unicode or None (for empty strings)
+  hessianlib.boolean  -> bool
+  int/long/float     -> int/long/float
+  hessianlib.DateTime -> datetime.date, time or datetime (dep. on ISO-string)
+
+  For lists and tuples the conversion will be applied to each element. For
+  dictionaries the conversion will be applied to the key as well as the value.
+  """
+
+  if value is None:
+    return None
+
+  # String (converts to None for empty strings, which will be the case for
+  # None-values used in dictionary keys)
+  elif isinstance (value, str):
+    return value and unicode (value) or None
+
+  elif isinstance (value, unicode):
+    return value and value or None
+
+  # Boolean (has to be checked before IntType)
+  elif isinstance (value, hessianlib.boolean):
+    return value and True or False
+
+  # Number
+  elif isinstance (value, (int,long,float)):
+    return value
+
+  # Date/Time/DateTime
+  elif isinstance (value, hessianlib.DateTime):
+    return datetime.datetime.utcfromtimestamp (value.value)
+
+  # List
+  elif isinstance (value, list):
+    return [rpc_to_python (element, wrapObject, exception, *wrapArgs) \
+        for element in value]
+
+  # Tuple
+  elif isinstance (value, tuple):
+    return tuple ([rpc_to_python (e, wrapObject, exception, *wrapArgs) \
+        for e in value])
+
+  # Dictionary
+  elif is_rpc_object (value):
+    return wrapObject (value, *wrapArgs)
+
+  elif isinstance (value, dict):
+    result = {}
+    for (key, val) in value.items ():
+      result [rpc_to_python (key, wrapObject, exception, *wrapArgs)] = \
+          rpc_to_python (val, wrapObject, exception, *wrapArgs)
+    return result
+
+  else:
+    raise exception, repr (value)


Property changes on: trunk/gnue-common/src/rpc/drivers/hessian/typeconv.py
___________________________________________________________________
Name: svn:keywords
   + Id





reply via email to

[Prev in Thread] Current Thread [Next in Thread]