Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

RPCInternalError: <RPCFault -32603: 'Internal error.' (None)> #47

Open
danielsamfdo opened this issue Dec 11, 2016 · 3 comments
Open

RPCInternalError: <RPCFault -32603: 'Internal error.' (None)> #47

danielsamfdo opened this issue Dec 11, 2016 · 3 comments

Comments

@danielsamfdo
Copy link

/Users/danielsampetethiyagu/github/image_caption_using_attention/coreNlpUtil.pyc in parseText(sentences)
22 def parseText(sentences):
23
---> 24 parseResult = nlp.parse(sentences)
25
26 if len(parseResult['sentences']) == 1:

/Users/danielsampetethiyagu/github/image_caption_using_attention/coreNlpUtil.pyc in parse(self, text)
16
17 def parse(self, text):
---> 18 return json.loads(self.server.parse(text))
19
20

/Users/danielsampetethiyagu/github/image_caption_using_attention/jsonrpc.py in call(self, *args, **kwargs)
932 return _method(self.__req, "%s.%s" % (self.__name, name))
933 def call(self, *args, **kwargs):
--> 934 return self.__req(self.__name, args, kwargs)
935
936 #=========================================

/Users/danielsampetethiyagu/github/image_caption_using_attention/jsonrpc.py in __req(self, methodname, args, kwargs, id)
905 except Exception,err:
906 raise RPCTransportError(err)
--> 907 resp = self.__data_serializer.loads_response( resp_str )
908 return resp[0]
909

/Users/danielsampetethiyagu/github/image_caption_using_attention/jsonrpc.py in loads_response(self, string)
624 raise RPCInvalidMethodParams(error_data)
625 elif data["error"]["code"] == INTERNAL_ERROR:
--> 626 raise RPCInternalError(error_data)
627 elif data["error"]["code"] == PROCEDURE_EXCEPTION:
628 raise RPCProcedureException(error_data)

RPCInternalError: <RPCFault -32603: 'Internal error.' (None)>

@arezki1990
Copy link

i had the same error trying to parse arabic text
File "client.py", line 16, in
result = nlp.parse(u"ﻊﻗﻮﺘﻤﻟا ﻦﻣ .ﺕﺎﺑﺎﻐﻟﺎﺑ ﻯﺫﻷا ﺕﺎﻄﻗﺎﺴﺘﻟا ﻲﻓﻭ ﺓﺭاﺮﺤﻟا ﻲﻓ ﺕاﺮﻴﻐﺘﻟا ﻖﺤﻠﺗ")
File "client.py", line 13, in parse
return json.loads(self.server.parse(text))
File "/home/arezki/stanford-corenlp-python/jsonrpc.py", line 934, in call
return self.__req(self.__name, args, kwargs)
File "/home/arezki/stanford-corenlp-python/jsonrpc.py", line 907, in __req
resp = self.__data_serializer.loads_response( resp_str )
File "/home/arezki/stanford-corenlp-python/jsonrpc.py", line 626, in loads_response
raise RPCInternalError(error_data)
jsonrpc.RPCInternalError: <RPCFault -32603: 'Internal error.' (None)>

@littlepan0413
Copy link

I had same issue, when I parse semeval 2010 , sentence is "A normal beer is 8 zlots (2 euros) and a beer with raspberry syrup is 9 zlots.".
and throw exception:

File "", line 1, in
loaddata.parse(str)

File "/home/user/workspace/python/tensorflow-rnn/loadData_semeval.py", line 65, in parse
sentence_parse = loads(self.server.parse(sentence))

File "stanfordenlp/jsonrpc.py", line 934, in call
return self.__req(self.__name, args, kwargs)

File "stanfordenlp/jsonrpc.py", line 907, in __req
resp = self.__data_serializer.loads_response( resp_str )

File "stanfordenlp/jsonrpc.py", line 626, in loads_response
raise RPCInternalError(error_data)

RPCInternalError: <RPCFault -32603: 'Internal error.' (None)>

@mohamc2022
Copy link

when I run client.py on version 3.4.1 of stanfordNLP I get the same error.
the program break in jsonrpc.py at the instruction : handeler(data). but I did not find any function called "handler()" in the program. what handler() does ? is it a python built-in function?

this is the error I got. thanks for any help.

b'{"jsonrpc": "2.0", "method": "parse", "params": ["Hello world It is so beautiful"], "id": 0}'
{"jsonrpc": "2.0", "error": {"code":-32603, "message": "Internal error."}, "id": 0}

for the code is here: look at section:
"transport-logging" in the program bellow

#!/usr/bin/env python

-- coding: ascii --

"""
JSON-RPC (remote procedure call).

It consists of 3 (independent) parts:
- proxy/dispatcher
- data structure / serializer
- transport

It's intended for JSON-RPC, but since the above 3 parts are independent,
it could be used for other RPCs as well.

Currently, JSON-RPC 2.0(pre) and JSON-RPC 1.0 are implemented

:Version: 2008-08-31-beta
:Status: experimental

:Example:
simple Client with JsonRPC2.0 and TCP/IP::

    >>> proxy = ServerProxy( JsonRpc20(), TransportTcpIp(addr=("127.0.0.1",31415)) )
    >>> proxy.echo( "hello world" )
    u'hello world'
    >>> proxy.echo( "bye." )
    u'bye.'

simple Server with JsonRPC2.0 and TCP/IP with logging to STDOUT::

    >>> server = Server( JsonRpc20(), TransportTcpIp(addr=("127.0.0.1",31415), logfunc=log_stdout) )
    >>> def echo( s ):
    ...   return s
    >>> server.register_function( echo )
    >>> server.serve( 2 )   # serve 2 requests          # doctest: +ELLIPSIS
    listen ('127.0.0.1', 31415)
    ('127.0.0.1', ...) connected
    ('127.0.0.1', ...) <-- {"jsonrpc": "2.0", "method": "echo", "params": ["hello world"], "id": 0}
    ('127.0.0.1', ...) --> {"jsonrpc": "2.0", "result": "hello world", "id": 0}
    ('127.0.0.1', ...) close
    ('127.0.0.1', ...) connected
    ('127.0.0.1', ...) <-- {"jsonrpc": "2.0", "method": "echo", "params": ["bye."], "id": 0}
    ('127.0.0.1', ...) --> {"jsonrpc": "2.0", "result": "bye.", "id": 0}
    ('127.0.0.1', ...) close
    close ('127.0.0.1', 31415)

Client with JsonRPC2.0 and an abstract Unix Domain Socket::

    >>> proxy = ServerProxy( JsonRpc20(), TransportUnixSocket(addr="\\x00.rpcsocket") )
    >>> proxy.hi( message="hello" )         #named parameters
    u'hi there'
    >>> proxy.test()                        #fault
    Traceback (most recent call last):
      ...
    jsonrpc.RPCMethodNotFound: <RPCFault -32601: u'Method not found.' (None)>
    >>> proxy.debug.echo( "hello world" )   #hierarchical procedures
    u'hello world'

Server with JsonRPC2.0 and abstract Unix Domain Socket with a logfile::
    
    >>> server = Server( JsonRpc20(), TransportUnixSocket(addr="\\x00.rpcsocket", logfunc=log_file("mylog.txt")) )
    >>> def echo( s ):
    ...   return s
    >>> def hi( message ):
    ...   return "hi there"
    >>> server.register_function( hi )
    >>> server.register_function( echo, name="debug.echo" )
    >>> server.serve( 3 )   # serve 3 requests

    "mylog.txt" then contains:
    listen '\\x00.rpcsocket'
    '' connected
    '' --> '{"jsonrpc": "2.0", "method": "hi", "params": {"message": "hello"}, "id": 0}'
    '' <-- '{"jsonrpc": "2.0", "result": "hi there", "id": 0}'
    '' close
    '' connected
    '' --> '{"jsonrpc": "2.0", "method": "test", "id": 0}'
    '' <-- '{"jsonrpc": "2.0", "error": {"code":-32601, "message": "Method not found."}, "id": 0}'
    '' close
    '' connected
    '' --> '{"jsonrpc": "2.0", "method": "debug.echo", "params": ["hello world"], "id": 0}'
    '' <-- '{"jsonrpc": "2.0", "result": "hello world", "id": 0}'
    '' close
    close '\\x00.rpcsocket'

:Note: all exceptions derived from RPCFault are propagated to the client.
other exceptions are logged and result in a sent-back "empty" INTERNAL_ERROR.
:Uses: simplejson, socket, sys,time,codecs
:SeeAlso: JSON-RPC 2.0 proposal, 1.0 specification
:Warning:
.. Warning::
This is experimental code!
:Bug:

:Author: Roland Koebler (rk(at)simple-is-better.org)
:Copyright: 2007-2008 by Roland Koebler (rk(at)simple-is-better.org)
:License: see license
:Changelog:
- 2008-08-31: 1st release

TODO:
- server: multithreading rpc-server
- client: multicall (send several requests)
- transport: SSL sockets, maybe HTTP, HTTPS
- types: support for date/time (ISO 8601)
- errors: maybe customizable error-codes/exceptions
- mixed 1.0/2.0 server ?
- system description etc. ?
- maybe test other json-serializers, like cjson?
"""

version = "2008-08-31-beta"
author = "Roland Koebler <rk(at)simple-is-better.org>"
license = """Copyright (c) 2007-2008 by Roland Koebler (rk(at)simple-is-better.org)

Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:

The above copyright notice and this permission notice shall be included
in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE."""

#=========================================

import sys
if sys.version_info[0] >= 3:
unicode = str

try:
import json
except ImportError:
import simplejson as json

#=========================================

errors

#----------------------

error-codes + exceptions

#JSON-RPC 2.0 error-codes
PARSE_ERROR = -32700
INVALID_REQUEST = -32600
METHOD_NOT_FOUND = -32601
INVALID_METHOD_PARAMS = -32602 #invalid number/type of parameters
INTERNAL_ERROR = -32603 #"all other errors"

#additional error-codes
PROCEDURE_EXCEPTION = -32000
AUTHENTIFICATION_ERROR = -32001
PERMISSION_DENIED = -32002
INVALID_PARAM_VALUES = -32003

#human-readable messages
ERROR_MESSAGE = {
PARSE_ERROR : "Parse error.",
INVALID_REQUEST : "Invalid Request.",
METHOD_NOT_FOUND : "Method not found.",
INVALID_METHOD_PARAMS : "Invalid parameters.",
INTERNAL_ERROR : "Internal error.",

PROCEDURE_EXCEPTION   : "Procedure exception.",
AUTHENTIFICATION_ERROR : "Authentification error.",
PERMISSION_DENIED   : "Permission denied.",
INVALID_PARAM_VALUES: "Invalid parameter values."
}

#----------------------

exceptions

class RPCError(Exception):
"""Base class for rpc-errors."""

class RPCTransportError(RPCError):
"""Transport error."""
class RPCTimeoutError(RPCTransportError):
"""Transport/reply timeout."""

class RPCFault(RPCError):
"""RPC error/fault package received.

This exception can also be used as a class, to generate a
RPC-error/fault message.

:Variables:
    - error_code:   the RPC error-code
    - error_string: description of the error
    - error_data:   optional additional information
                    (must be json-serializable)
:TODO: improve __str__
"""
def __init__(self, error_code, error_message, error_data=None):
    RPCError.__init__(self)
    self.error_code   = error_code
    self.error_message = error_message
    self.error_data   = error_data
def __str__(self):
    return repr(self)
def __repr__(self):
    return( "<RPCFault %s: %s (%s)>" % (self.error_code, repr(self.error_message), repr(self.error_data)) )

class RPCParseError(RPCFault):
"""Broken rpc-package. (PARSE_ERROR)"""
def init(self, error_data=None):
RPCFault.init(self, PARSE_ERROR, ERROR_MESSAGE[PARSE_ERROR], error_data)

class RPCInvalidRPC(RPCFault):
"""Invalid rpc-package. (INVALID_REQUEST)"""
def init(self, error_data=None):
RPCFault.init(self, INVALID_REQUEST, ERROR_MESSAGE[INVALID_REQUEST], error_data)

class RPCMethodNotFound(RPCFault):
"""Method not found. (METHOD_NOT_FOUND)"""
def init(self, error_data=None):
RPCFault.init(self, METHOD_NOT_FOUND, ERROR_MESSAGE[METHOD_NOT_FOUND], error_data)

class RPCInvalidMethodParams(RPCFault):
"""Invalid method-parameters. (INVALID_METHOD_PARAMS)"""
def init(self, error_data=None):
RPCFault.init(self, INVALID_METHOD_PARAMS, ERROR_MESSAGE[INVALID_METHOD_PARAMS], error_data)

class RPCInternalError(RPCFault):
"""Internal error. (INTERNAL_ERROR)"""
def init(self, error_data=None):
RPCFault.init(self, INTERNAL_ERROR, ERROR_MESSAGE[INTERNAL_ERROR], error_data)

class RPCProcedureException(RPCFault):
"""Procedure exception. (PROCEDURE_EXCEPTION)"""
def init(self, error_data=None):
RPCFault.init(self, PROCEDURE_EXCEPTION, ERROR_MESSAGE[PROCEDURE_EXCEPTION], error_data)
class RPCAuthentificationError(RPCFault):
"""AUTHENTIFICATION_ERROR"""
def init(self, error_data=None):
RPCFault.init(self, AUTHENTIFICATION_ERROR, ERROR_MESSAGE[AUTHENTIFICATION_ERROR], error_data)
class RPCPermissionDenied(RPCFault):
"""PERMISSION_DENIED"""
def init(self, error_data=None):
RPCFault.init(self, PERMISSION_DENIED, ERROR_MESSAGE[PERMISSION_DENIED], error_data)
class RPCInvalidParamValues(RPCFault):
"""INVALID_PARAM_VALUES"""
def init(self, error_data=None):
RPCFault.init(self, INVALID_PARAM_VALUES, ERROR_MESSAGE[INVALID_PARAM_VALUES], error_data)

#=========================================

data structure / serializer

#----------------------

def dictkeyclean(d):
"""Convert all keys of the dict 'd' to (ascii-)strings.

:Raises: UnicodeEncodeError
"""
new_d = {}
for (k, v) in d.iteritems():
    new_d[str(k)] = v
return new_d

#----------------------

JSON-RPC 1.0

class JsonRpc10:
"""JSON-RPC V1.0 data-structure / serializer

This implementation is quite liberal in what it accepts: It treats
missing "params" and "id" in Requests and missing "result"/"error" in
Responses as empty/null.

:SeeAlso:   JSON-RPC 1.0 specification
:TODO:      catch simplejson.dumps not-serializable-exceptions
"""
def __init__(self, dumps=json.dumps, loads=json.loads):
    """init: set serializer to use

    :Parameters:
        - dumps: json-encoder-function
        - loads: json-decoder-function
    :Note: The dumps_* functions of this class already directly create
           the invariant parts of the resulting json-object themselves,
           without using the given json-encoder-function.
    """
    self.dumps = dumps
    self.loads = loads

def dumps_request( self, method, params=(), id=0 ):
    """serialize JSON-RPC-Request

    :Parameters:
        - method: the method-name (str/unicode)
        - params: the parameters (list/tuple)
        - id:     if id=None, this results in a Notification
    :Returns:   | {"method": "...", "params": ..., "id": ...}
                | "method", "params" and "id" are always in this order.
    :Raises:    TypeError if method/params is of wrong type or 
                not JSON-serializable
    """
    if not isinstance(method, (str, unicode)):
        raise TypeError('"method" must be a string (or unicode string).')
    if not isinstance(params, (tuple, list)):
        raise TypeError("params must be a tuple/list.")

    return '{"method": %s, "params": %s, "id": %s}' % \
            (self.dumps(method), self.dumps(params), self.dumps(id))

def dumps_notification( self, method, params=() ):
    """serialize a JSON-RPC-Notification

    :Parameters: see dumps_request
    :Returns:   | {"method": "...", "params": ..., "id": null}
                | "method", "params" and "id" are always in this order.
    :Raises:    see dumps_request
    """
    if not isinstance(method, (str, unicode)):
        raise TypeError('"method" must be a string (or unicode string).')
    if not isinstance(params, (tuple, list)):
        raise TypeError("params must be a tuple/list.")

    return '{"method": %s, "params": %s, "id": null}' % \
            (self.dumps(method), self.dumps(params))

def dumps_response( self, result, id=None ):
    """serialize a JSON-RPC-Response (without error)

    :Returns:   | {"result": ..., "error": null, "id": ...}
                | "result", "error" and "id" are always in this order.
    :Raises:    TypeError if not JSON-serializable
    """
    return '{"result": %s, "error": null, "id": %s}' % \
            (self.dumps(result), self.dumps(id))

def dumps_error( self, error, id=None ):
    """serialize a JSON-RPC-Response-error

    Since JSON-RPC 1.0 does not define an error-object, this uses the
    JSON-RPC 2.0 error-object.
  
    :Parameters:
        - error: a RPCFault instance
    :Returns:   | {"result": null, "error": {"code": error_code, "message": error_message, "data": error_data}, "id": ...}
                | "result", "error" and "id" are always in this order, data is omitted if None.
    :Raises:    ValueError if error is not a RPCFault instance,
                TypeError if not JSON-serializable
    """
    if not isinstance(error, RPCFault):
        raise ValueError("""error must be a RPCFault-instance.""")
    if error.error_data is None:
        return '{"result": null, "error": {"code":%s, "message": %s}, "id": %s}' % \
                (self.dumps(error.error_code), self.dumps(error.error_message), self.dumps(id))
    else:
        return '{"result": null, "error": {"code":%s, "message": %s, "data": %s}, "id": %s}' % \
                (self.dumps(error.error_code), self.dumps(error.error_message), self.dumps(error.error_data), self.dumps(id))

def loads_request( self, string ):
    """de-serialize a JSON-RPC Request/Notification

    :Returns:   | [method_name, params, id] or [method_name, params]
                | params is a tuple/list
                | if id is missing, this is a Notification
    :Raises:    RPCParseError, RPCInvalidRPC, RPCInvalidMethodParams
    """
    try:
        data = self.loads(string)
    except ValueError as err:
        raise RPCParseError("No valid JSON. (%s)" % str(err))
    if not isinstance(data, dict):  raise RPCInvalidRPC("No valid RPC-package.")
    if "method" not in data:        raise RPCInvalidRPC("""Invalid Request, "method" is missing.""")
    if not isinstance(data["method"], (str, unicode)):
        raise RPCInvalidRPC("""Invalid Request, "method" must be a string.""")
    if "id"     not in data:        data["id"]     = None   #be liberal
    if "params" not in data:        data["params"] = ()     #be liberal
    if not isinstance(data["params"], (list, tuple)):
        raise RPCInvalidRPC("""Invalid Request, "params" must be an array.""")
    if len(data) != 3:          raise RPCInvalidRPC("""Invalid Request, additional fields found.""")
    # notification / request
    if data["id"] is None:
        return data["method"], data["params"]               #notification
    else:
        return data["method"], data["params"], data["id"]   #request

def loads_response( self, string ):
    """de-serialize a JSON-RPC Response/error

    :Returns: | [result, id] for Responses
    :Raises:  | RPCFault+derivates for error-packages/faults, RPCParseError, RPCInvalidRPC
              | Note that for error-packages which do not match the
                V2.0-definition, RPCFault(-1, "Error", RECEIVED_ERROR_OBJ)
                is raised.
    """
    try:
        data = self.loads(string)
    except ValueError as err:
        raise RPCParseError("No valid JSON. (%s)" % str(err))
    if not isinstance(data, dict):  raise RPCInvalidRPC("No valid RPC-package.")
    if "id" not in data:            raise RPCInvalidRPC("""Invalid Response, "id" missing.""")
    if "result" not in data:        data["result"] = None    #be liberal
    if "error"  not in data:        data["error"]  = None    #be liberal
    if len(data) != 3:              raise RPCInvalidRPC("""Invalid Response, additional or missing fields.""")

    #error
    if data["error"] is not None:
        if data["result"] is not None:
            raise RPCInvalidRPC("""Invalid Response, one of "result" or "error" must be null.""")
        #v2.0 error-format
        if( isinstance(data["error"], dict)  and  "code" in data["error"]  and  "message" in data["error"]  and
            (len(data["error"])==2 or ("data" in data["error"] and len(data["error"])==3)) ):
            if "data" not in data["error"]:
                error_data = None
            else:
                error_data = data["error"]["data"]

            if   data["error"]["code"] == PARSE_ERROR:
                raise RPCParseError(error_data)
            elif data["error"]["code"] == INVALID_REQUEST:
                raise RPCInvalidRPC(error_data)
            elif data["error"]["code"] == METHOD_NOT_FOUND:
                raise RPCMethodNotFound(error_data)
            elif data["error"]["code"] == INVALID_METHOD_PARAMS:
                raise RPCInvalidMethodParams(error_data)
            elif data["error"]["code"] == INTERNAL_ERROR:
                raise RPCInternalError(error_data)
            elif data["error"]["code"] == PROCEDURE_EXCEPTION:
                raise RPCProcedureException(error_data)
            elif data["error"]["code"] == AUTHENTIFICATION_ERROR:
                raise RPCAuthentificationError(error_data)
            elif data["error"]["code"] == PERMISSION_DENIED:
                raise RPCPermissionDenied(error_data)
            elif data["error"]["code"] == INVALID_PARAM_VALUES:
                raise RPCInvalidParamValues(error_data)
            else:
                raise RPCFault(data["error"]["code"], data["error"]["message"], error_data)
        #other error-format
        else:
            raise RPCFault(-1, "Error", data["error"])
    #result
    else:
        return data["result"], data["id"]

#----------------------

JSON-RPC 2.0

class JsonRpc20:
"""JSON-RPC V2.0 data-structure / serializer

:SeeAlso:   JSON-RPC 2.0 specification
:TODO:      catch simplejson.dumps not-serializable-exceptions
"""
def __init__(self, dumps=json.dumps, loads=json.loads):
    """init: set serializer to use

    :Parameters:
        - dumps: json-encoder-function
        - loads: json-decoder-function
    :Note: The dumps_* functions of this class already directly create
           the invariant parts of the resulting json-object themselves,
           without using the given json-encoder-function.
    """
    self.dumps = dumps
    self.loads = loads

def dumps_request( self, method, params=(), id=0 ):
    """serialize JSON-RPC-Request

    :Parameters:
        - method: the method-name (str/unicode)
        - params: the parameters (list/tuple/dict)
        - id:     the id (should not be None)
    :Returns:   | {"jsonrpc": "2.0", "method": "...", "params": ..., "id": ...}
                | "jsonrpc", "method", "params" and "id" are always in this order.
                | "params" is omitted if empty
    :Raises:    TypeError if method/params is of wrong type or 
                not JSON-serializable
    """
    if not isinstance(method, (str, unicode)):
        raise TypeError('"method" must be a string (or unicode string).')
    if not isinstance(params, (tuple, list, dict)):
        raise TypeError("params must be a tuple/list/dict or None.")

    if params:
        return '{"jsonrpc": "2.0", "method": %s, "params": %s, "id": %s}' % \
                (self.dumps(method), self.dumps(params), self.dumps(id))
    else:
        return '{"jsonrpc": "2.0", "method": %s, "id": %s}' % \
                (self.dumps(method), self.dumps(id))

def dumps_notification( self, method, params=() ):
    """serialize a JSON-RPC-Notification

    :Parameters: see dumps_request
    :Returns:   | {"jsonrpc": "2.0", "method": "...", "params": ...}
                | "jsonrpc", "method" and "params" are always in this order.
    :Raises:    see dumps_request
    """
    if not isinstance(method, (str, unicode)):
        raise TypeError('"method" must be a string (or unicode string).')
    if not isinstance(params, (tuple, list, dict)):
        raise TypeError("params must be a tuple/list/dict or None.")

    if params:
        return '{"jsonrpc": "2.0", "method": %s, "params": %s}' % \
                (self.dumps(method), self.dumps(params))
    else:
        return '{"jsonrpc": "2.0", "method": %s}' % \
                (self.dumps(method))

def dumps_response( self, result, id=None ):
    """serialize a JSON-RPC-Response (without error)

    :Returns:   | {"jsonrpc": "2.0", "result": ..., "id": ...}
                | "jsonrpc", "result", and "id" are always in this order.
    :Raises:    TypeError if not JSON-serializable
    """
    return '{"jsonrpc": "2.0", "result": %s, "id": %s}' % \
            (self.dumps(result), self.dumps(id))

def dumps_error( self, error, id=None ):
    """serialize a JSON-RPC-Response-error
  
    :Parameters:
        - error: a RPCFault instance
    :Returns:   | {"jsonrpc": "2.0", "error": {"code": error_code, "message": error_message, "data": error_data}, "id": ...}
                | "jsonrpc", "result", "error" and "id" are always in this order, data is omitted if None.
    :Raises:    ValueError if error is not a RPCFault instance,
                TypeError if not JSON-serializable
    """
    if not isinstance(error, RPCFault):
        raise ValueError("""error must be a RPCFault-instance.""")
    if error.error_data is None:
        return '{"jsonrpc": "2.0", "error": {"code":%s, "message": %s}, "id": %s}' % \
                (self.dumps(error.error_code), self.dumps(error.error_message), self.dumps(id))
    else:
        return '{"jsonrpc": "2.0", "error": {"code":%s, "message": %s, "data": %s}, "id": %s}' % \
                (self.dumps(error.error_code), self.dumps(error.error_message), self.dumps(error.error_data), self.dumps(id))

def loads_request( self, string ):
    """de-serialize a JSON-RPC Request/Notification

    :Returns:   | [method_name, params, id] or [method_name, params]
                | params is a tuple/list or dict (with only str-keys)
                | if id is missing, this is a Notification
    :Raises:    RPCParseError, RPCInvalidRPC, RPCInvalidMethodParams
    """
    try:
        data = self.loads(string)
    except ValueError as err:
        raise RPCParseError("No valid JSON. (%s)" % str(err))
    if not isinstance(data, dict):  raise RPCInvalidRPC("No valid RPC-package.")
    if "jsonrpc" not in data:       raise RPCInvalidRPC("""Invalid Response, "jsonrpc" missing.""")
    if not isinstance(data["jsonrpc"], (str, unicode)):
        raise RPCInvalidRPC("""Invalid Response, "jsonrpc" must be a string.""")
    if data["jsonrpc"] != "2.0":    raise RPCInvalidRPC("""Invalid jsonrpc version.""")
    if "method" not in data:        raise RPCInvalidRPC("""Invalid Request, "method" is missing.""")
    if not isinstance(data["method"], (str, unicode)):
        raise RPCInvalidRPC("""Invalid Request, "method" must be a string.""")
    if "params" not in data:        data["params"] = ()
    #convert params-keys from unicode to str
    elif isinstance(data["params"], dict):
        try:
            data["params"] = dictkeyclean(data["params"])
        except UnicodeEncodeError:
            raise RPCInvalidMethodParams("Parameter-names must be in ascii.")
    elif not isinstance(data["params"], (list, tuple)):
        raise RPCInvalidRPC("""Invalid Request, "params" must be an array or object.""")
    if not( len(data)==3 or ("id" in data and len(data)==4) ):
        raise RPCInvalidRPC("""Invalid Request, additional fields found.""")

    # notification / request
    if "id" not in data:
        return data["method"], data["params"]               #notification
    else:
        return data["method"], data["params"], data["id"]   #request

def loads_response( self, string ):
    """de-serialize a JSON-RPC Response/error

    :Returns: | [result, id] for Responses
    :Raises:  | RPCFault+derivates for error-packages/faults, RPCParseError, RPCInvalidRPC
    """
    try:
        data = self.loads(string)
    except ValueError as err:
        raise RPCParseError("No valid JSON. (%s)" % str(err))
    if not isinstance(data, dict):  raise RPCInvalidRPC("No valid RPC-package.")
    if "jsonrpc" not in data:       raise RPCInvalidRPC("""Invalid Response, "jsonrpc" missing.""")
    if not isinstance(data["jsonrpc"], (str, unicode)):
        raise RPCInvalidRPC("""Invalid Response, "jsonrpc" must be a string.""")
    if data["jsonrpc"] != "2.0":    raise RPCInvalidRPC("""Invalid jsonrpc version.""")
    if "id" not in data:            raise RPCInvalidRPC("""Invalid Response, "id" missing.""")
    if "result" not in data:        data["result"] = None
    if "error"  not in data:        data["error"]  = None
    if len(data) != 4:              raise RPCInvalidRPC("""Invalid Response, additional or missing fields.""")

    #error
    if data["error"] is not None:
        if data["result"] is not None:
            raise RPCInvalidRPC("""Invalid Response, only "result" OR "error" allowed.""")
        if not isinstance(data["error"], dict): raise RPCInvalidRPC("Invalid Response, invalid error-object.")
        if "code" not in data["error"]  or  "message" not in data["error"]:
            raise RPCInvalidRPC("Invalid Response, invalid error-object.")
        if "data" not in data["error"]:  data["error"]["data"] = None
        if len(data["error"]) != 3:
            raise RPCInvalidRPC("Invalid Response, invalid error-object.")

        error_data = data["error"]["data"]
        if   data["error"]["code"] == PARSE_ERROR:
            raise RPCParseError(error_data)
        elif data["error"]["code"] == INVALID_REQUEST:
            raise RPCInvalidRPC(error_data)
        elif data["error"]["code"] == METHOD_NOT_FOUND:
            raise RPCMethodNotFound(error_data)
        elif data["error"]["code"] == INVALID_METHOD_PARAMS:
            raise RPCInvalidMethodParams(error_data)
        elif data["error"]["code"] == INTERNAL_ERROR:
            raise RPCInternalError(error_data)
        elif data["error"]["code"] == PROCEDURE_EXCEPTION:
            raise RPCProcedureException(error_data)
        elif data["error"]["code"] == AUTHENTIFICATION_ERROR:
            raise RPCAuthentificationError(error_data)
        elif data["error"]["code"] == PERMISSION_DENIED:
            raise RPCPermissionDenied(error_data)
        elif data["error"]["code"] == INVALID_PARAM_VALUES:
            raise RPCInvalidParamValues(error_data)
        else:
            raise RPCFault(data["error"]["code"], data["error"]["message"], error_data)
    #result
    else:
        return data["result"], data["id"]

#=========================================

transports

#----------------------

transport-logging

import codecs
import time

def log_dummy( message ):
"""dummy-logger: do nothing"""
pass
def log_stdout( message ):
"""print message to STDOUT"""
print (message)

def log_file( filename ):
"""return a logfunc which logs to a file (in utf-8)"""
def logfile( message ):
f = codecs.open( filename, 'a', encoding='utf-8' )
f.write( message+"\n" )
f.close()
return logfile

def log_filedate( filename ):
"""return a logfunc which logs date+message to a file (in utf-8)"""
def logfile( message ):
f = codecs.open( filename, 'a', encoding='utf-8' )
f.write( time.strftime("%Y-%m-%d %H:%M:%S ")+message+"\n" )
f.close()
return logfile

#----------------------

class Transport:
"""generic Transport-interface.

This class, and especially its methods and docstrings,
define the Transport-Interface.
"""
def __init__(self):
    pass

def send( self, data ):
    """send all data. must be implemented by derived classes."""
    raise NotImplementedError
def recv( self ):
    """receive data. must be implemented by derived classes."""
    raise NotImplementedError

def sendrecv( self, string ):
    """send + receive data"""
    self.send( string )
    return self.recv()
def serve( self, handler, n=None ):
    """serve (forever or for n communicaions).
    
    - receive data
    - call result = handler(data)
    - send back result if not None

    The serving can be stopped by SIGINT.

    :TODO:
        - how to stop?
          maybe use a .run-file, and stop server if file removed?
        - maybe make n_current accessible? (e.g. for logging)
    """
    n_current = 0
    while 1:
        if n is not None  and  n_current >= n:
            break
        data = self.recv()
        result = handler(data)
        if result is not None:
            self.send( result )
        n_current += 1

class TransportSTDINOUT(Transport):
"""receive from STDIN, send to STDOUT.

Useful e.g. for debugging.
"""
def send(self, string):
    """write data to STDOUT with '***SEND:' prefix """
    print ("***SEND:")
    print (string)
def recv(self):
    """read data from STDIN"""
    print ("***RECV (please enter, ^D ends.):")
    return sys.stdin.read()

import socket, select
class TransportSocket(Transport):
"""Transport via socket.

:SeeAlso:   python-module socket
:TODO:
    - documentation
    - improve this (e.g. make sure that connections are closed, socket-files are deleted etc.)
    - exception-handling? (socket.error)
"""
def __init__( self, addr, limit=4096, sock_type=socket.AF_INET, sock_prot=socket.SOCK_STREAM, timeout=5.0, logfunc=log_dummy ):
    """
    :Parameters:
        - addr: socket-address
        - timeout: timeout in seconds
        - logfunc: function for logging, logfunc(message)
    :Raises: socket.timeout after timeout
    """
    self.limit  = limit
    self.addr   = addr
    self.s_type = sock_type
    self.s_prot = sock_prot
    self.s      = None
    self.timeout = timeout
    self.log    = logfunc
def connect( self ):
    self.close()
    self.log( "connect to %s" % repr(self.addr) )
    self.s = socket.socket( self.s_type, self.s_prot )
    self.s.settimeout( self.timeout )
    self.s.connect( self.addr )
def close( self ):
    if self.s is not None:
        self.log( "close %s" % repr(self.addr) )
        self.s.close()
        self.s = None
def __repr__(self):
    return "<TransportSocket, %s>" % repr(self.addr)

def send( self, string ):
    if self.s is None:
        self.connect()
    self.log( "--> "+repr(string) )
    self.s.sendall( string.encode() )
def recv( self ):
    if self.s is None:
        self.connect()
    data = self.s.recv( self.limit )
    while( select.select((self.s,), (), (), 0.1)[0] ):  #TODO: this select is probably not necessary, because server closes this socket
        d = self.s.recv( self.limit )
        if len(d) == 0:
            break
        data += d
    self.log( "<-- "+repr(data) )
    return data

def sendrecv( self, string ):
    """send data + receive data + close"""
    try:
        self.send( string )
        return self.recv()
    finally:
        self.close()
def serve(self, handler, n=None):
    """open socket, wait for incoming connections and handle them.
    
    :Parameters:
        - n: serve n requests, None=forever
    """
    self.close()
    self.s = socket.socket( self.s_type, self.s_prot )
    try:
        self.log( "listen %s" % repr(self.addr) )
        self.s.bind( self.addr )
        self.s.listen(1)
        n_current = 0
        while 1:
            if n is not None  and  n_current >= n:
                break
            conn, addr = self.s.accept()
            self.log( "%s connected" % repr(addr) )
            data = conn.recv(self.limit)
            self.log( "%s --> %s" % (repr(addr), repr(data)) )
            print("821 data before handler:", data)
            result = handler(data )
            print("823 result after handler:", result)
            if data is not None:
                self.log( "%s <-- %s" % (repr(addr), repr(result)) )
                conn.send( result )
            self.log( "%s close" % repr(addr) )
            conn.close()
            n_current += 1
    finally:
        self.close()

if hasattr(socket, 'AF_UNIX'):

class TransportUnixSocket(TransportSocket):
    """Transport via Unix Domain Socket.
    """
    def __init__(self, addr=None, limit=4096, timeout=5.0, logfunc=log_dummy):
        """
        :Parameters:
            - addr: "socket_file"
        :Note: | The socket-file is not deleted.
               | If the socket-file begins with \x00, abstract sockets are used,
                 and no socket-file is created.
        :SeeAlso:   TransportSocket
        """
        TransportSocket.__init__( self, addr, limit, socket.AF_UNIX, socket.SOCK_STREAM, timeout, logfunc )

class TransportTcpIp(TransportSocket):
"""Transport via TCP/IP.
"""
def init(self, addr=None, limit=4096, timeout=5.0, logfunc=log_dummy):
"""
:Parameters:
- addr: ("host",port)
:SeeAlso: TransportSocket
"""
TransportSocket.init( self, addr, limit, socket.AF_INET, socket.SOCK_STREAM, timeout, logfunc )

#=========================================

client side: server proxy

class ServerProxy:
"""RPC-client: server proxy

A logical connection to a RPC server.

It works with different data/serializers and different transports.

Notifications and id-handling/multicall are not yet implemented.

:Example:
    see module-docstring

:TODO: verbose/logging?
"""
def __init__( self, data_serializer, transport ):
    """
    :Parameters:
        - data_serializer: a data_structure+serializer-instance
        - transport: a Transport instance
    """
    #TODO: check parameters
    self.__data_serializer = data_serializer
    if not isinstance(transport, Transport):
        raise ValueError('invalid "transport" (must be a Transport-instance)"')
    self.__transport = transport

def __str__(self):
    return repr(self)
def __repr__(self):
    return "<ServerProxy for %s, with serializer %s>" % (self.__transport, self.__data_serializer)

def __req( self, methodname, args=None, kwargs=None, id=0 ):
    # JSON-RPC 1.0: only positional parameters
    if len(kwargs) > 0 and isinstance(self.data_serializer, JsonRpc10):
        raise ValueError("Only positional parameters allowed in JSON-RPC 1.0")
    # JSON-RPC 2.0: only args OR kwargs allowed!
    if len(args) > 0 and len(kwargs) > 0:
        raise ValueError("Only positional or named parameters are allowed!")
    if len(kwargs) == 0:
        req_str  = self.__data_serializer.dumps_request( methodname, args, id )
    else:
        req_str  = self.__data_serializer.dumps_request( methodname, kwargs, id )
    try:
        resp_str = self.__transport.sendrecv( req_str )
    except Exception as err:
        raise RPCTransportError(err)
    resp = self.__data_serializer.loads_response( resp_str )
    return resp[0]

def __getattr__(self, name):
    # magic method dispatcher
    #  note: to call a remote object with an non-standard name, use
    #  result getattr(my_server_proxy, "strange-python-name")(args)
    return _method(self.__req, name)

request dispatcher

class _method:
"""some "magic" to bind an RPC method to an RPC server.

Supports "nested" methods (e.g. examples.getStateName).

:Raises: AttributeError for method-names/attributes beginning with '_'.
"""
def __init__(self, req, name):
    if name[0] == "_":  #prevent rpc-calls for proxy._*-functions
        raise AttributeError("invalid attribute '%s'" % name)
    self.__req  = req
    self.__name = name
def __getattr__(self, name):
    if name[0] == "_":  #prevent rpc-calls for proxy._*-functions
        raise AttributeError("invalid attribute '%s'" % name)
    return _method(self.__req, "%s.%s" % (self.__name, name))
def __call__(self, *args, **kwargs):
    return self.__req(self.__name, args, kwargs)

#=========================================

server side: Server

class Server:
"""RPC-server.

It works with different data/serializers and 
with different transports.

:Example:
    see module-docstring

:TODO:
    - mixed JSON-RPC 1.0/2.0 server?
    - logging/loglevels?
"""
def __init__( self, data_serializer, transport, logfile=None ):
    """
    :Parameters:
        - data_serializer: a data_structure+serializer-instance
        - transport: a Transport instance
        - logfile: file to log ("unexpected") errors to
    """
    #TODO: check parameters
    self.__data_serializer = data_serializer
    if not isinstance(transport, Transport):
        raise ValueError('invalid "transport" (must be a Transport-instance)"')
    self.__transport = transport
    self.logfile = logfile
    if self.logfile is not None:    #create logfile (or raise exception)
        f = codecs.open( self.logfile, 'a', encoding='utf-8' )
        f.close()

    self.funcs = {}

def __repr__(self):
    return "<Server for %s, with serializer %s>" % (self.__transport, self.__data_serializer)

def log(self, message):
    """write a message to the logfile (in utf-8)"""
    if self.logfile is not None:
        f = codecs.open( self.logfile, 'a', encoding='utf-8' )
        f.write( time.strftime("%Y-%m-%d %H:%M:%S ")+message+"\n" )
        f.close()

def register_instance(self, myinst, name=None):
    """Add all functions of a class-instance to the RPC-services.
    
    All entries of the instance which do not begin with '_' are added.

    :Parameters:
        - myinst: class-instance containing the functions
        - name:   | hierarchical prefix.
                  | If omitted, the functions are added directly.
                  | If given, the functions are added as "name.function".
    :TODO:
        - only add functions and omit attributes?
        - improve hierarchy?
    """
    for e in dir(myinst):
        if e[0][0] != "_":
            if name is None:
                self.register_function( getattr(myinst, e) )
            else:
                self.register_function( getattr(myinst, e), name="%s.%s" % (name, e) )
def register_function(self, function, name=None):
    """Add a function to the RPC-services.
    
    :Parameters:
        - function: function to add
        - name:     RPC-name for the function. If omitted/None, the original
                    name of the function is used.
    """
    if name is None:
        self.funcs[function.__name__] = function
    else:
        self.funcs[name] = function

def handle(self, rpcstr):
    """Handle a RPC-Request.

    :Parameters:
        - rpcstr: the received rpc-string
    :Returns: the data to send back or None if nothing should be sent back
    :Raises:  RPCFault (and maybe others)
    """
    #TODO: id
    notification = False
    try:
        req = self.__data_serializer.loads_request( rpcstr )
        if len(req) == 2:       #notification
            method, params = req
            notification = True
        else:                   #request
            method, params, id = req
    except RPCFault as err:
        return self.__data_serializer.dumps_error( err, id=None )
    except Exception as err:
        self.log( "%d (%s): %s" % (INTERNAL_ERROR, ERROR_MESSAGE[INTERNAL_ERROR], str(err)) )
        return self.__data_serializer.dumps_error( RPCFault(INTERNAL_ERROR, ERROR_MESSAGE[INTERNAL_ERROR]), id=None )

    if method not in self.funcs:
        if notification:
            return None
        return self.__data_serializer.dumps_error( RPCFault(METHOD_NOT_FOUND, ERROR_MESSAGE[METHOD_NOT_FOUND]), id )

    try:
        if isinstance(params, dict):
            result = self.funcs[method]( **params )
        else:
            result = self.funcs[method]( *params )
    except RPCFault as err:
        if notification:
            return None
        return self.__data_serializer.dumps_error( err, id=None )
    except Exception as err:
        if notification:
            return None
        self.log( "%d (%s): %s" % (INTERNAL_ERROR, ERROR_MESSAGE[INTERNAL_ERROR], str(err)) )
        return self.__data_serializer.dumps_error( RPCFault(INTERNAL_ERROR, ERROR_MESSAGE[INTERNAL_ERROR]), id )

    if notification:
        return None
    try:
        return self.__data_serializer.dumps_response( result, id )
    except Exception as err:
        self.log( "%d (%s): %s" % (INTERNAL_ERROR, ERROR_MESSAGE[INTERNAL_ERROR], str(err)) )
        return self.__data_serializer.dumps_error( RPCFault(INTERNAL_ERROR, ERROR_MESSAGE[INTERNAL_ERROR]), id )

def serve(self, n=None):
    """serve (forever or for n communicaions).
    
    :See: Transport
    """
    self.__transport.serve( self.handle, n )

#=========================================

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

4 participants