Hi,
I stumbled upon a lot of bugs and issues in the hessianlib.py, so I took the liberty of fixing them. Some changes to the interface of the writer were made as well. Use as you wish.

Yours,
Philip

#
# A Hessian client interface for Python.  The date and long types
# require Python 2.2 or later.
#
# The Hessian proxy is used as follows:
#
# proxy = Hessian("http://hessian.caucho.com/test/basic";)
#
# print proxy.hello()
#
# --------------------------------------------------------------------
#
# The Apache Software License, Version 1.1
#
# Copyright (c) 2001-2002 Caucho Technology, Inc. All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions
# are met:
#
# 1. Redistributions of source code must retain the above copyright
#    notice, this list of conditions and the following disclaimer.
#
# 2. Redistributions in binary form must reproduce the above copyright
#    notice, this list of conditions and the following disclaimer in
#    the documentation and/or other materials provided with the
#    distribution.
#
# 3. The end-user documentation included with the redistribution, if
#    any, must include the following acknowlegement:
#       "This product includes software developed by the
#        Caucho Technology (http://www.caucho.com/)."
#    Alternately, this acknowlegement may appear in the software
#    itself, if and wherever such third-party acknowlegements normally
#    appear.
#
# 4. The names "Hessian", "Resin", and "Caucho" must not be used to
#    endorse or promote products derived from this software without
#    prior written permission. For written permission, please contact
#    [EMAIL PROTECTED]
#
# 5. Products derived from this software may not be called "Resin"
#    nor may "Resin" appear in their names without prior written
#    permission of Caucho Technology.
#
# THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
# WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
# MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
# IN NO EVENT SHALL CAUCHO TECHNOLOGY OR ITS CONTRIBUTORS BE LIABLE
# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
# BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
# --------------------------------------------------------------------
#
# Credits: hessianlib.py was inspired and partially based on
# xmlrpclib.py created by Fredrik Lundh at www.pythonware.org
#

# --------------------------------------------------------------------
# Changes by Philip Bergen, Wiinz Ltd.
# 1. Date was based on seconds, where the standard requires milli-
#    seconds, our requirement is nanoseconds, so the interpretation
#    was updated to nanoseconds. This can be controlled by modifying
#    the __support_nanoseconds__ variable. If set to zero date will
#    be assumed to be in milliseconds.
# 2. The HessianWriter supported only writing of calls and all other
#    write_XXX methods would just fail. The syntax was altered to
#    serialize_call and serialize_object to reflect the difference
#    between the methods that serialize and return bytes and the
#    ones that are used to write serializations into an internal
#    array. Write methods were made private to illustrate that they
#    are not inteded for use from outside the class.
# 3. Serialization of binary and string types was improved to support
#    data larger than 64kB.
# 4. The method interface to serialize_call was changed from assuming
#    that the parameters was a vector (even with only one parameter),
#    to using *params so that it works naturally python like.
# 5. The __write_list and __write_map methods now use __write_object
#    internally so that objects and maps and lists are serialized
#    recursively (and properly).
# 6. Added support for XML data.
# 7. Removed deprecated use of raising strings.
# --------------------------------------------------------------------

import string, time
import urllib
from types import *
from struct import unpack
from struct import pack

__version__ = "0.1 wiinz"
__blobsize__ = 3
__support_nanoseconds__ = 1

# --------------------------------------------------------------------
# Exceptions

class Error:
    # base class for client errors
    pass

class ProtocolError(Error):
    # Represents an HTTP protocol error
    def __init__(self, url, code, message, headers):
        self.url = url
        self.code = code
        self.message = message
        self.headers = headers

    def __repr__(self):
        return (
            "<ProtocolError for %s: %s %s>" %
            (self.url, self.code, self.message)
            )

class Fault(Error):
    # Represents a fault from Hessian
    def __init__(self, code, message, **detail):
        self.code = code
        self.message = message

    def __repr__(self):
        return "<HessianFault %s: %s>" % (self.code, self.message)

class Bug(Error):
    def __init__(self, message):
        self.message=message

    def __repr__(self):
        return (
            "<Bug: %s>" % (self.message)
            )

# --------------------------------------------------------------------
# Wrappers for Hessian data types non-standard in Python
#

#
# Boolean -- use the True or False constants
#
class Boolean:
    def __init__(self, value = 0):
        self.value = (value != 0)

    def _hessian_write(self, out):
        if self.value:
            out.write('T')
        else:
            out.write('F')

    def __repr__(self):
        if self.value:
            return "<True at %x>" % id(self)
        else:
            return "<False at %x>" % id(self)

    def __int__(self):
        return self.value

    def __nonzero__(self):
        return self.value

True, False = Boolean(1), Boolean(0)

#
# Date - wraps a time value in milliseconds
#
class Date:
    def __init__(self, value = 0):
        """Create a date object from the number of milliseconds since epoch.
        If __support_nanoseconds__ is non zero, the value should be in
        nanoseconds resolution."""
        self.value = value

    def __repr__(self):
        if __support_nanoseconds__ == 0:
            return ("<Date %s at %x>" %
                    (time.asctime(time.localtime(self.value/1000)), id(self)))
        else:
            return ("<Date %s at %x>" %
                    (time.asctime(time.localtime(self.value/1000000000)), id(self)))

    def _hessian_write(self, out):
        out.write("d")
        out.write(pack(">q", self.value))

#
# Binary - binary data
#
class Binary:
    def __init__(self, data=None):
        self.hess_code='B'
        self.data = data

    def _hessian_write(self, out):
        bytes=0
        total=len(self.data)
        for i in xrange(0,(total-1)/__blobsize__,1):
            out.write(self.hess_code.lower())
            out.write(pack('>H',__blobsize__))
            out.write(self.data[i*__blobsize__:(i+1)*__blobsize__])
            bytes+=__blobsize__
        out.write(self.hess_code)
        out.write(pack('>H', total-bytes))
        out.write(self.data[bytes:])

#
# XML - XML data
#
class XML(Binary):
    def __init__(self, data=None):
        self.hess_code = 'X'
        self.data = data

# --------------------------------------------------------------------
# Marshalling and unmarshalling code

#
# HessianWriter - writes Hessian data from Python objects
#
class HessianWriter:
    """An object for serializing objects and calls in Hessian encoding.
    Use the two methods serialize_call and serialize_object only."""

    def serialize_call(self, method, *params):
        """Serializes a call for submitting in hessian encoding.
        @param method String method name (limited to 64k)
        @param params Objects to serialize as parameters to the method
        @return A byte sequence"""
        self.refs = {}
        self.ref = 0
        self.__out = []
        self.write = self.__out.append

        self.__write_call(method, params)

        result = string.join(self.__out, '')
        del self.__out, self.write, self.refs
        return result

    def serialize_object(self, value):
        """Serializes an object in hessian encoding.
        @param value Any object that can be serialized in Hessian encoding.
        @return A byte sequence"""
        self.refs = {}
        self.ref = 0
        self.__out = []
        self.write = self.__out.append

        self.__write_object(value)

        result = string.join(self.__out, '')
        del self.__out, self.write, self.refs
        return result

    __dispatch = {}

    def __write_object(self, value):
        try:
            f = self.__dispatch[type(value)]
        except KeyError:
            raise TypeError, "cannot write %s objects" % type(value)
        else:
            f(self, value)

    def __write_call(self, method, params):
        """Writes a call to method 'method' using parameter 'params'.
        @param method Any method name (string shorter than 64kb).
        @param params A list or vector of parameters."""
        self.write("c\x01\x00m");
        self.write(pack(">H", len(method)));
        self.write(method);
        for param in params:
            self.__write_object(param)
        self.write("z");

    def __write_int(self, value):
        self.write('I')
        self.write(pack(">l", value))
    __dispatch[IntType] = __write_int

    def __write_long(self, value):
        self.write('L')
        self.write(pack(">q", value))
    __dispatch[LongType] = __write_long

    def __write_double(self, value):
        self.write('D')
        self.write(pack(">d", value))
    __dispatch[FloatType] = __write_double

    #
    # String - String data. Only used internally, normal python strings
    # should be used in programs.
    #
    class __String(Binary):
        def __init__(self, data=None):
            self.hess_code = 'S'
            self.data = data

    def __write_string(self, value):
        self.__String(value)._hessian_write(self)
    __dispatch[StringType] = __write_string

    def __write_reference(self, value):
        # check for and write circular references
        # returns 1 if the object should be written, i.e. not a reference
        i = id(value)
        if self.refs.has_key(i):
            self.write('R')
            self.write(pack('>L', self.refs[i]))
            return 0
        else:
            self.refs[i] = self.ref
            self.ref = self.ref + 1
            return 1

    def __write_list(self, value):
        if self.__write_reference(value):
            self.write("Vt\x00\x00l");
            self.write(pack('>l', len(value)))
            for v in value:
                self.__write_object(v)
            self.write('z')
    __dispatch[TupleType] = __write_list
    __dispatch[ListType] = __write_list

    def __write_map(self, value):
        if self.__write_reference(value):
            self.write("Mt\x00\x00")
            for k, v in value.items():
                self.__write_object(k)
                self.__write_object(v)
            self.write('z')
    __dispatch[DictType] = __write_map

    def __write_instance(self, value):
        """Writes an object using _hessian_write method. If that is not
        implemented, the object is treated like a map and each
        property on it serialized using its name and value as key and
        value."""
        # check for special wrappers
        if hasattr(value, "_hessian_write"):
            value._hessian_write(self)
        else:
            fields = value.__dict__
            if self.__write_reference(fields):
                self.write("Mt\x00\x00")
                for k, v in fields.items():
                    self.__write_object(k)
                    self.__write_object(v)
                out.write("z")
    __dispatch[InstanceType] = __write_instance

class StringReader:
    """Simple class for making strings behave as input file."""
    def __init__(self, value):
        self.value=value
        self.pos=0

    def read(self,n=None):
        if n is None:
            n=len(self.value)-self.pos
        self.pos+=n
        return self.value[self.pos-n:self.pos]

#
# Parses the results from the server
#
class HessianParser:
    def __init__(self, f):
        self._f = f
        self._peek = -1
        self._refs = []
        self.tmp=[]

    def read(self, len):
        if self._peek >= 0:
          value = self._peek # Will be buggy if the subsequent call
          self._peek = -1    # is not of the same len as the peek'd
          return value
        else:
          return self._f.read(len)

    def parse_reply(self):
        # parse header 'r' x01 x00 'v' ... 'z'
        read = self.read
        if read(1) != 'r':
            self.error()
        major = read(1)
        minor = read(1)

        value = self.parse_object()

        if read(1) == 'z':
            return value
        self.error() # actually a fault

    def parse_object(self):
        # parse an arbitrary object based on the type in the data
        return self.parse_object_code(self.read(1))

    def parse_object_code(self, code):
        # parse an object when the code is known
        read = self.read

        if code == 'N':
            return None

        elif code == 'T':
            return True

        elif code == 'F':
            return False

        elif code == 'I':
            return unpack('>l', read(4))[0]

        elif code == 'l':
            return unpack('>l', read(4))[0]

        elif code == 'L':
            return unpack('>q', read(8))[0]

        elif code == 'D':
            return unpack('>d', read(8))[0]

        elif code == 'd':
            ms = unpack('>q', read(8))[0]

            return Date(int(ms / 1000.0))

        elif code == 's' or code == 'x' or code == 'b':
            self.tmp.append(self.parse_string())
            return self.parse_object_code(read(1))

        elif code == 'S' or code == 'X':
            res=string.join(self.tmp,'')+self.parse_string()
            self.tmp=[]
            return res

        elif code == 'B':
            res=Binary(string.join(self.tmp,'')+self.parse_string())
            self.tmp=[]
            return res

        elif code == 'V':
            self.parse_type() # skip type
            self.parse_length() # skip length
            list = []
            self._refs.append(list)
            ch = read(1)
            while ch != 'z':
                list.append(self.parse_object_code(ch))
                ch = read(1)
            return list

        elif code == 'M':
            self.parse_type() # skip type
            map = {}
            self._refs.append(map)
            ch = read(1)
            while ch != 'z':
                key = self.parse_object_code(ch)
                value = self.parse_object()
                map[key] = value
                ch = read(1)
            return map

        elif code == 'R':
            return self._refs[unpack('>l', read(4))[0]]

        elif code == 'r':
            self.parse_type()       # skip type
            url = self.parse_type() # reads the url
            return Hessian(url)

        else:
            raise Bug, "UnknownObjectCode %s" % code

    def parse_string(self):
        f = self._f
        len = unpack('>H', f.read(2))[0]
        return f.read(len)

    def parse_type(self):
        f = self._f
        code = self.read(1)
        if code != 't':
          self._peek = code
          return ''
        len = unpack('>H', f.read(2))[0]
        return f.read(len)

    def parse_length(self):
        f = self._f
        code = self.read(1);
        if code != 'l':
          self._peek = code
          return -1;
        len = unpack('>l', f.read(4))
        return len

    def error(self):
        raise Bug, "FOO"

#
# Encapsulates the method to be called
#
class _Method:
    def __init__(self, invoker, method):
        self._invoker = invoker
        self._method = method

    def __call__(self, *args):
        return self._invoker(self._method, args)

# --------------------------------------------------------------------
# Hessian is the main class.  A Hessian proxy is created with the URL
# and then called just as for a local method
#
# proxy = Hessian("http://www.caucho.com/hessian/test/basic";)
# print proxy.hello()
#
class Hessian:
    """Represents a remote object reachable by Hessian"""

    def __init__(self, url):
        # Creates a Hessian proxy object

        self._url = url

        # get the uri
        type, uri = urllib.splittype(url)
        if type != "http":
            raise IOError, "unsupported Hessian protocol"

        self._host, self._uri = urllib.splithost(uri)

    def __invoke(self, method, params):
        # call a method on the remote server

        request = HessianWriter().serialize_call(method, params)

        import httplib

        h = httplib.HTTP(self._host)
        h.putrequest("POST", self._uri)

        # required by HTTP/1.1
        h.putheader("Host", self._host)

        h.putheader("User-Agent", "hessianlib.py/%s" % __version__)
        h.putheader("Content-Length", str(len(request)))

        h.endheaders()

        h.send(request)

        errcode, errmsg, headers = h.getreply()

        if errcode != 200:
            raise ProtocolError(self._url, errcode, errmsg, headers)

        return self.parse_response(h.getfile())

    def parse_response(self, f):
        # read response from input file, and parse it

        parser = HessianParser(f)
        value = parser.parse_reply()
        f.close()

        return value

    def _hessian_write(self, out):
        # marshals the proxy itself
        out.write("rt\x00\x00S")
        out.write(pack(">H", len(self._url)))
        out.write(self._url)

    def __repr__(self):
        return "<HessianProxy %s>" % self._url

    __str__ = __repr__

    def __getattr__(self, name):
        # encapsulate the method call
        return _Method(self.__invoke, name)

#
# Testing code.
#
if __name__ == "__main__":

    proxy = Hessian("http://hessian.caucho.com/test/test";)

    try:
        print proxy.hello()
    except Error, v:
        print "ERROR", v



--
"Do first things first and second things not at all" -- Peter Drucker


Attachment: smime.p7s
Description: S/MIME cryptographic signature

_______________________________________________
hessian-interest mailing list
[email protected]
http://maillist.caucho.com/mailman/listinfo/hessian-interest

Reply via email to