[Top][All Lists]
[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
[Prev in Thread] |
Current Thread |
[Next in Thread] |
- [gnue] r8471 - in trunk: gnue-appserver/src gnue-common/src/rpc/drivers gnue-common/src/rpc/drivers/hessian,
reinhard <=