mirror of
https://github.com/NaN-tic/trytond-monitoring_network.git
synced 2023-12-14 03:42:58 +01:00
Add jsonrpclib in the module as it's not been updated for a while.
This commit is contained in:
parent
751b672e51
commit
35f8bd1be6
229
jsonrpclib/SimpleJSONRPCServer.py
Normal file
229
jsonrpclib/SimpleJSONRPCServer.py
Normal file
|
@ -0,0 +1,229 @@
|
|||
import jsonrpclib
|
||||
from jsonrpclib import Fault
|
||||
from jsonrpclib.jsonrpc import USE_UNIX_SOCKETS
|
||||
import SimpleXMLRPCServer
|
||||
import SocketServer
|
||||
import socket
|
||||
import logging
|
||||
import os
|
||||
import types
|
||||
import traceback
|
||||
import sys
|
||||
try:
|
||||
import fcntl
|
||||
except ImportError:
|
||||
# For Windows
|
||||
fcntl = None
|
||||
|
||||
def get_version(request):
|
||||
# must be a dict
|
||||
if 'jsonrpc' in request.keys():
|
||||
return 2.0
|
||||
if 'id' in request.keys():
|
||||
return 1.0
|
||||
return None
|
||||
|
||||
def validate_request(request):
|
||||
if type(request) is not types.DictType:
|
||||
fault = Fault(
|
||||
-32600, 'Request must be {}, not %s.' % type(request)
|
||||
)
|
||||
return fault
|
||||
rpcid = request.get('id', None)
|
||||
version = get_version(request)
|
||||
if not version:
|
||||
fault = Fault(-32600, 'Request %s invalid.' % request, rpcid=rpcid)
|
||||
return fault
|
||||
request.setdefault('params', [])
|
||||
method = request.get('method', None)
|
||||
params = request.get('params')
|
||||
param_types = (types.ListType, types.DictType, types.TupleType)
|
||||
if not method or type(method) not in types.StringTypes or \
|
||||
type(params) not in param_types:
|
||||
fault = Fault(
|
||||
-32600, 'Invalid request parameters or method.', rpcid=rpcid
|
||||
)
|
||||
return fault
|
||||
return True
|
||||
|
||||
class SimpleJSONRPCDispatcher(SimpleXMLRPCServer.SimpleXMLRPCDispatcher):
|
||||
|
||||
def __init__(self, encoding=None):
|
||||
SimpleXMLRPCServer.SimpleXMLRPCDispatcher.__init__(self,
|
||||
allow_none=True,
|
||||
encoding=encoding)
|
||||
|
||||
def _marshaled_dispatch(self, data, dispatch_method = None):
|
||||
response = None
|
||||
try:
|
||||
request = jsonrpclib.loads(data)
|
||||
except Exception, e:
|
||||
fault = Fault(-32700, 'Request %s invalid. (%s)' % (data, e))
|
||||
response = fault.response()
|
||||
return response
|
||||
if not request:
|
||||
fault = Fault(-32600, 'Request invalid -- no request data.')
|
||||
return fault.response()
|
||||
if type(request) is types.ListType:
|
||||
# This SHOULD be a batch, by spec
|
||||
responses = []
|
||||
for req_entry in request:
|
||||
result = validate_request(req_entry)
|
||||
if type(result) is Fault:
|
||||
responses.append(result.response())
|
||||
continue
|
||||
resp_entry = self._marshaled_single_dispatch(req_entry)
|
||||
if resp_entry is not None:
|
||||
responses.append(resp_entry)
|
||||
if len(responses) > 0:
|
||||
response = '[%s]' % ','.join(responses)
|
||||
else:
|
||||
response = ''
|
||||
else:
|
||||
result = validate_request(request)
|
||||
if type(result) is Fault:
|
||||
return result.response()
|
||||
response = self._marshaled_single_dispatch(request)
|
||||
return response
|
||||
|
||||
def _marshaled_single_dispatch(self, request):
|
||||
# TODO - Use the multiprocessing and skip the response if
|
||||
# it is a notification
|
||||
# Put in support for custom dispatcher here
|
||||
# (See SimpleXMLRPCServer._marshaled_dispatch)
|
||||
method = request.get('method')
|
||||
params = request.get('params')
|
||||
try:
|
||||
response = self._dispatch(method, params)
|
||||
except:
|
||||
exc_type, exc_value, exc_tb = sys.exc_info()
|
||||
fault = Fault(-32603, '%s:%s' % (exc_type, exc_value))
|
||||
return fault.response()
|
||||
if 'id' not in request.keys() or request['id'] == None:
|
||||
# It's a notification
|
||||
return None
|
||||
try:
|
||||
response = jsonrpclib.dumps(response,
|
||||
methodresponse=True,
|
||||
rpcid=request['id']
|
||||
)
|
||||
return response
|
||||
except:
|
||||
exc_type, exc_value, exc_tb = sys.exc_info()
|
||||
fault = Fault(-32603, '%s:%s' % (exc_type, exc_value))
|
||||
return fault.response()
|
||||
|
||||
def _dispatch(self, method, params):
|
||||
func = None
|
||||
try:
|
||||
func = self.funcs[method]
|
||||
except KeyError:
|
||||
if self.instance is not None:
|
||||
if hasattr(self.instance, '_dispatch'):
|
||||
return self.instance._dispatch(method, params)
|
||||
else:
|
||||
try:
|
||||
func = SimpleXMLRPCServer.resolve_dotted_attribute(
|
||||
self.instance,
|
||||
method,
|
||||
True
|
||||
)
|
||||
except AttributeError:
|
||||
pass
|
||||
if func is not None:
|
||||
try:
|
||||
if type(params) is types.ListType:
|
||||
response = func(*params)
|
||||
else:
|
||||
response = func(**params)
|
||||
return response
|
||||
except TypeError:
|
||||
return Fault(-32602, 'Invalid parameters.')
|
||||
except:
|
||||
err_lines = traceback.format_exc().splitlines()
|
||||
trace_string = '%s | %s' % (err_lines[-3], err_lines[-1])
|
||||
fault = jsonrpclib.Fault(-32603, 'Server error: %s' %
|
||||
trace_string)
|
||||
return fault
|
||||
else:
|
||||
return Fault(-32601, 'Method %s not supported.' % method)
|
||||
|
||||
class SimpleJSONRPCRequestHandler(
|
||||
SimpleXMLRPCServer.SimpleXMLRPCRequestHandler):
|
||||
|
||||
def do_POST(self):
|
||||
if not self.is_rpc_path_valid():
|
||||
self.report_404()
|
||||
return
|
||||
try:
|
||||
max_chunk_size = 10*1024*1024
|
||||
size_remaining = int(self.headers["content-length"])
|
||||
L = []
|
||||
while size_remaining:
|
||||
chunk_size = min(size_remaining, max_chunk_size)
|
||||
L.append(self.rfile.read(chunk_size))
|
||||
size_remaining -= len(L[-1])
|
||||
data = ''.join(L)
|
||||
response = self.server._marshaled_dispatch(data)
|
||||
self.send_response(200)
|
||||
except Exception, e:
|
||||
self.send_response(500)
|
||||
err_lines = traceback.format_exc().splitlines()
|
||||
trace_string = '%s | %s' % (err_lines[-3], err_lines[-1])
|
||||
fault = jsonrpclib.Fault(-32603, 'Server error: %s' % trace_string)
|
||||
response = fault.response()
|
||||
if response == None:
|
||||
response = ''
|
||||
self.send_header("Content-type", "application/json-rpc")
|
||||
self.send_header("Content-length", str(len(response)))
|
||||
self.end_headers()
|
||||
self.wfile.write(response)
|
||||
self.wfile.flush()
|
||||
self.connection.shutdown(1)
|
||||
|
||||
class SimpleJSONRPCServer(SocketServer.TCPServer, SimpleJSONRPCDispatcher):
|
||||
|
||||
allow_reuse_address = True
|
||||
|
||||
def __init__(self, addr, requestHandler=SimpleJSONRPCRequestHandler,
|
||||
logRequests=True, encoding=None, bind_and_activate=True,
|
||||
address_family=socket.AF_INET):
|
||||
self.logRequests = logRequests
|
||||
SimpleJSONRPCDispatcher.__init__(self, encoding)
|
||||
# TCPServer.__init__ has an extra parameter on 2.6+, so
|
||||
# check Python version and decide on how to call it
|
||||
vi = sys.version_info
|
||||
self.address_family = address_family
|
||||
if USE_UNIX_SOCKETS and address_family == socket.AF_UNIX:
|
||||
# Unix sockets can't be bound if they already exist in the
|
||||
# filesystem. The convention of e.g. X11 is to unlink
|
||||
# before binding again.
|
||||
if os.path.exists(addr):
|
||||
try:
|
||||
os.unlink(addr)
|
||||
except OSError:
|
||||
logging.warning("Could not unlink socket %s", addr)
|
||||
# if python 2.5 and lower
|
||||
if vi[0] < 3 and vi[1] < 6:
|
||||
SocketServer.TCPServer.__init__(self, addr, requestHandler)
|
||||
else:
|
||||
SocketServer.TCPServer.__init__(self, addr, requestHandler,
|
||||
bind_and_activate)
|
||||
if fcntl is not None and hasattr(fcntl, 'FD_CLOEXEC'):
|
||||
flags = fcntl.fcntl(self.fileno(), fcntl.F_GETFD)
|
||||
flags |= fcntl.FD_CLOEXEC
|
||||
fcntl.fcntl(self.fileno(), fcntl.F_SETFD, flags)
|
||||
|
||||
class CGIJSONRPCRequestHandler(SimpleJSONRPCDispatcher):
|
||||
|
||||
def __init__(self, encoding=None):
|
||||
SimpleJSONRPCDispatcher.__init__(self, encoding)
|
||||
|
||||
def handle_jsonrpc(self, request_text):
|
||||
response = self._marshaled_dispatch(request_text)
|
||||
print 'Content-Type: application/json-rpc'
|
||||
print 'Content-Length: %d' % len(response)
|
||||
print
|
||||
sys.stdout.write(response)
|
||||
|
||||
handle_xmlrpc = handle_jsonrpc
|
6
jsonrpclib/__init__.py
Normal file
6
jsonrpclib/__init__.py
Normal file
|
@ -0,0 +1,6 @@
|
|||
from jsonrpclib.config import Config
|
||||
config = Config.instance()
|
||||
from jsonrpclib.history import History
|
||||
history = History.instance()
|
||||
from jsonrpclib.jsonrpc import Server, MultiCall, Fault
|
||||
from jsonrpclib.jsonrpc import ProtocolError, loads, dumps
|
38
jsonrpclib/config.py
Normal file
38
jsonrpclib/config.py
Normal file
|
@ -0,0 +1,38 @@
|
|||
import sys
|
||||
|
||||
class LocalClasses(dict):
|
||||
def add(self, cls):
|
||||
self[cls.__name__] = cls
|
||||
|
||||
class Config(object):
|
||||
"""
|
||||
This is pretty much used exclusively for the 'jsonclass'
|
||||
functionality... set use_jsonclass to False to turn it off.
|
||||
You can change serialize_method and ignore_attribute, or use
|
||||
the local_classes.add(class) to include "local" classes.
|
||||
"""
|
||||
use_jsonclass = True
|
||||
# Change to False to keep __jsonclass__ entries raw.
|
||||
serialize_method = '_serialize'
|
||||
# The serialize_method should be a string that references the
|
||||
# method on a custom class object which is responsible for
|
||||
# returning a tuple of the constructor arguments and a dict of
|
||||
# attributes.
|
||||
ignore_attribute = '_ignore'
|
||||
# The ignore attribute should be a string that references the
|
||||
# attribute on a custom class object which holds strings and / or
|
||||
# references of the attributes the class translator should ignore.
|
||||
classes = LocalClasses()
|
||||
# The list of classes to use for jsonclass translation.
|
||||
version = 2.0
|
||||
# Version of the JSON-RPC spec to support
|
||||
user_agent = 'jsonrpclib/0.1 (Python %s)' % \
|
||||
'.'.join([str(ver) for ver in sys.version_info[0:3]])
|
||||
# User agent to use for calls.
|
||||
_instance = None
|
||||
|
||||
@classmethod
|
||||
def instance(cls):
|
||||
if not cls._instance:
|
||||
cls._instance = cls()
|
||||
return cls._instance
|
40
jsonrpclib/history.py
Normal file
40
jsonrpclib/history.py
Normal file
|
@ -0,0 +1,40 @@
|
|||
class History(object):
|
||||
"""
|
||||
This holds all the response and request objects for a
|
||||
session. A server using this should call "clear" after
|
||||
each request cycle in order to keep it from clogging
|
||||
memory.
|
||||
"""
|
||||
requests = []
|
||||
responses = []
|
||||
_instance = None
|
||||
|
||||
@classmethod
|
||||
def instance(cls):
|
||||
if not cls._instance:
|
||||
cls._instance = cls()
|
||||
return cls._instance
|
||||
|
||||
def add_response(self, response_obj):
|
||||
self.responses.append(response_obj)
|
||||
|
||||
def add_request(self, request_obj):
|
||||
self.requests.append(request_obj)
|
||||
|
||||
@property
|
||||
def request(self):
|
||||
if len(self.requests) == 0:
|
||||
return None
|
||||
else:
|
||||
return self.requests[-1]
|
||||
|
||||
@property
|
||||
def response(self):
|
||||
if len(self.responses) == 0:
|
||||
return None
|
||||
else:
|
||||
return self.responses[-1]
|
||||
|
||||
def clear(self):
|
||||
del self.requests[:]
|
||||
del self.responses[:]
|
152
jsonrpclib/jsonclass.py
Normal file
152
jsonrpclib/jsonclass.py
Normal file
|
@ -0,0 +1,152 @@
|
|||
import types
|
||||
import inspect
|
||||
import re
|
||||
import traceback
|
||||
|
||||
from jsonrpclib import config
|
||||
|
||||
iter_types = [
|
||||
types.DictType,
|
||||
types.ListType,
|
||||
types.TupleType
|
||||
]
|
||||
|
||||
string_types = [
|
||||
types.StringType,
|
||||
types.UnicodeType
|
||||
]
|
||||
|
||||
numeric_types = [
|
||||
types.IntType,
|
||||
types.LongType,
|
||||
types.FloatType
|
||||
]
|
||||
|
||||
value_types = [
|
||||
types.BooleanType,
|
||||
types.NoneType
|
||||
]
|
||||
|
||||
supported_types = iter_types+string_types+numeric_types+value_types
|
||||
invalid_module_chars = r'[^a-zA-Z0-9\_\.]'
|
||||
|
||||
class TranslationError(Exception):
|
||||
pass
|
||||
|
||||
def dump(obj, serialize_method=None, ignore_attribute=None, ignore=[]):
|
||||
if not serialize_method:
|
||||
serialize_method = config.serialize_method
|
||||
if not ignore_attribute:
|
||||
ignore_attribute = config.ignore_attribute
|
||||
obj_type = type(obj)
|
||||
# Parse / return default "types"...
|
||||
if obj_type in numeric_types+string_types+value_types:
|
||||
return obj
|
||||
if obj_type in iter_types:
|
||||
if obj_type in (types.ListType, types.TupleType):
|
||||
new_obj = []
|
||||
for item in obj:
|
||||
new_obj.append(dump(item, serialize_method,
|
||||
ignore_attribute, ignore))
|
||||
if obj_type is types.TupleType:
|
||||
new_obj = tuple(new_obj)
|
||||
return new_obj
|
||||
# It's a dict...
|
||||
else:
|
||||
new_obj = {}
|
||||
for key, value in obj.iteritems():
|
||||
new_obj[key] = dump(value, serialize_method,
|
||||
ignore_attribute, ignore)
|
||||
return new_obj
|
||||
# It's not a standard type, so it needs __jsonclass__
|
||||
module_name = inspect.getmodule(obj).__name__
|
||||
class_name = obj.__class__.__name__
|
||||
json_class = class_name
|
||||
if module_name not in ['', '__main__']:
|
||||
json_class = '%s.%s' % (module_name, json_class)
|
||||
return_obj = {"__jsonclass__":[json_class,]}
|
||||
# If a serialization method is defined..
|
||||
if serialize_method in dir(obj):
|
||||
# Params can be a dict (keyword) or list (positional)
|
||||
# Attrs MUST be a dict.
|
||||
serialize = getattr(obj, serialize_method)
|
||||
params, attrs = serialize()
|
||||
return_obj['__jsonclass__'].append(params)
|
||||
return_obj.update(attrs)
|
||||
return return_obj
|
||||
# Otherwise, try to figure it out
|
||||
# Obviously, we can't assume to know anything about the
|
||||
# parameters passed to __init__
|
||||
return_obj['__jsonclass__'].append([])
|
||||
attrs = {}
|
||||
ignore_list = getattr(obj, ignore_attribute, [])+ignore
|
||||
for attr_name, attr_value in obj.__dict__.iteritems():
|
||||
if type(attr_value) in supported_types and \
|
||||
attr_name not in ignore_list and \
|
||||
attr_value not in ignore_list:
|
||||
attrs[attr_name] = dump(attr_value, serialize_method,
|
||||
ignore_attribute, ignore)
|
||||
return_obj.update(attrs)
|
||||
return return_obj
|
||||
|
||||
def load(obj):
|
||||
if type(obj) in string_types+numeric_types+value_types:
|
||||
return obj
|
||||
if type(obj) is types.ListType:
|
||||
return_list = []
|
||||
for entry in obj:
|
||||
return_list.append(load(entry))
|
||||
return return_list
|
||||
# Othewise, it's a dict type
|
||||
if '__jsonclass__' not in obj.keys():
|
||||
return_dict = {}
|
||||
for key, value in obj.iteritems():
|
||||
new_value = load(value)
|
||||
return_dict[key] = new_value
|
||||
return return_dict
|
||||
# It's a dict, and it's a __jsonclass__
|
||||
orig_module_name = obj['__jsonclass__'][0]
|
||||
params = obj['__jsonclass__'][1]
|
||||
if orig_module_name == '':
|
||||
raise TranslationError('Module name empty.')
|
||||
json_module_clean = re.sub(invalid_module_chars, '', orig_module_name)
|
||||
if json_module_clean != orig_module_name:
|
||||
raise TranslationError('Module name %s has invalid characters.' %
|
||||
orig_module_name)
|
||||
json_module_parts = json_module_clean.split('.')
|
||||
json_class = None
|
||||
if len(json_module_parts) == 1:
|
||||
# Local class name -- probably means it won't work
|
||||
if json_module_parts[0] not in config.classes.keys():
|
||||
raise TranslationError('Unknown class or module %s.' %
|
||||
json_module_parts[0])
|
||||
json_class = config.classes[json_module_parts[0]]
|
||||
else:
|
||||
json_class_name = json_module_parts.pop()
|
||||
json_module_tree = '.'.join(json_module_parts)
|
||||
try:
|
||||
temp_module = __import__(json_module_tree)
|
||||
except ImportError:
|
||||
raise TranslationError('Could not import %s from module %s.' %
|
||||
(json_class_name, json_module_tree))
|
||||
|
||||
# The returned class is the top-level module, not the one we really
|
||||
# want. (E.g., if we import a.b.c, we now have a.) Walk through other
|
||||
# path components to get to b and c.
|
||||
for i in json_module_parts[1:]:
|
||||
temp_module = getattr(temp_module, i)
|
||||
|
||||
json_class = getattr(temp_module, json_class_name)
|
||||
# Creating the object...
|
||||
new_obj = None
|
||||
if type(params) is types.ListType:
|
||||
new_obj = json_class(*params)
|
||||
elif type(params) is types.DictType:
|
||||
new_obj = json_class(**params)
|
||||
else:
|
||||
raise TranslationError('Constructor args must be a dict or list.')
|
||||
for key, value in obj.iteritems():
|
||||
if key == '__jsonclass__':
|
||||
continue
|
||||
setattr(new_obj, key, value)
|
||||
return new_obj
|
561
jsonrpclib/jsonrpc.py
Normal file
561
jsonrpclib/jsonrpc.py
Normal file
|
@ -0,0 +1,561 @@
|
|||
"""
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
|
||||
============================
|
||||
JSONRPC Library (jsonrpclib)
|
||||
============================
|
||||
|
||||
This library is a JSON-RPC v.2 (proposed) implementation which
|
||||
follows the xmlrpclib API for portability between clients. It
|
||||
uses the same Server / ServerProxy, loads, dumps, etc. syntax,
|
||||
while providing features not present in XML-RPC like:
|
||||
|
||||
* Keyword arguments
|
||||
* Notifications
|
||||
* Versioning
|
||||
* Batches and batch notifications
|
||||
|
||||
Eventually, I'll add a SimpleXMLRPCServer compatible library,
|
||||
and other things to tie the thing off nicely. :)
|
||||
|
||||
For a quick-start, just open a console and type the following,
|
||||
replacing the server address, method, and parameters
|
||||
appropriately.
|
||||
>>> import jsonrpclib
|
||||
>>> server = jsonrpclib.Server('http://localhost:8181')
|
||||
>>> server.add(5, 6)
|
||||
11
|
||||
>>> server._notify.add(5, 6)
|
||||
>>> batch = jsonrpclib.MultiCall(server)
|
||||
>>> batch.add(3, 50)
|
||||
>>> batch.add(2, 3)
|
||||
>>> batch._notify.add(3, 5)
|
||||
>>> batch()
|
||||
[53, 5]
|
||||
|
||||
See http://code.google.com/p/jsonrpclib/ for more info.
|
||||
"""
|
||||
|
||||
import types
|
||||
import sys
|
||||
from xmlrpclib import Transport as XMLTransport
|
||||
from xmlrpclib import SafeTransport as XMLSafeTransport
|
||||
from xmlrpclib import ServerProxy as XMLServerProxy
|
||||
from xmlrpclib import _Method as XML_Method
|
||||
import time
|
||||
import string
|
||||
import random
|
||||
|
||||
# Library includes
|
||||
import jsonrpclib
|
||||
from jsonrpclib import config
|
||||
from jsonrpclib import history
|
||||
|
||||
# JSON library importing
|
||||
cjson = None
|
||||
json = None
|
||||
try:
|
||||
import cjson
|
||||
except ImportError:
|
||||
try:
|
||||
import json
|
||||
except ImportError:
|
||||
try:
|
||||
import simplejson as json
|
||||
except ImportError:
|
||||
raise ImportError(
|
||||
'You must have the cjson, json, or simplejson ' +
|
||||
'module(s) available.'
|
||||
)
|
||||
|
||||
IDCHARS = string.ascii_lowercase+string.digits
|
||||
|
||||
class UnixSocketMissing(Exception):
|
||||
"""
|
||||
Just a properly named Exception if Unix Sockets usage is
|
||||
attempted on a platform that doesn't support them (Windows)
|
||||
"""
|
||||
pass
|
||||
|
||||
#JSON Abstractions
|
||||
|
||||
def jdumps(obj, encoding='utf-8'):
|
||||
# Do 'serialize' test at some point for other classes
|
||||
global cjson
|
||||
if cjson:
|
||||
return cjson.encode(obj)
|
||||
else:
|
||||
return json.dumps(obj, encoding=encoding)
|
||||
|
||||
def jloads(json_string):
|
||||
global cjson
|
||||
if cjson:
|
||||
return cjson.decode(json_string)
|
||||
else:
|
||||
return json.loads(json_string)
|
||||
|
||||
|
||||
# XMLRPClib re-implementations
|
||||
|
||||
class ProtocolError(Exception):
|
||||
pass
|
||||
|
||||
class TransportMixIn(object):
|
||||
""" Just extends the XMLRPC transport where necessary. """
|
||||
user_agent = config.user_agent
|
||||
# for Python 2.7 support
|
||||
_connection = None
|
||||
|
||||
def send_content(self, connection, request_body):
|
||||
connection.putheader("Content-Type", "application/json-rpc")
|
||||
connection.putheader("Content-Length", str(len(request_body)))
|
||||
connection.endheaders()
|
||||
if request_body:
|
||||
connection.send(request_body)
|
||||
|
||||
def getparser(self):
|
||||
target = JSONTarget()
|
||||
return JSONParser(target), target
|
||||
|
||||
class JSONParser(object):
|
||||
def __init__(self, target):
|
||||
self.target = target
|
||||
|
||||
def feed(self, data):
|
||||
self.target.feed(data)
|
||||
|
||||
def close(self):
|
||||
pass
|
||||
|
||||
class JSONTarget(object):
|
||||
def __init__(self):
|
||||
self.data = []
|
||||
|
||||
def feed(self, data):
|
||||
self.data.append(data)
|
||||
|
||||
def close(self):
|
||||
return ''.join(self.data)
|
||||
|
||||
class Transport(TransportMixIn, XMLTransport):
|
||||
def __init__(self):
|
||||
TransportMixIn.__init__(self)
|
||||
XMLTransport.__init__(self)
|
||||
|
||||
class SafeTransport(TransportMixIn, XMLSafeTransport):
|
||||
def __init__(self):
|
||||
TransportMixIn.__init__(self)
|
||||
XMLSafeTransport.__init__(self)
|
||||
|
||||
from httplib import HTTP, HTTPConnection
|
||||
from socket import socket
|
||||
|
||||
USE_UNIX_SOCKETS = False
|
||||
|
||||
try:
|
||||
from socket import AF_UNIX, SOCK_STREAM
|
||||
USE_UNIX_SOCKETS = True
|
||||
except ImportError:
|
||||
pass
|
||||
|
||||
if (USE_UNIX_SOCKETS):
|
||||
|
||||
class UnixHTTPConnection(HTTPConnection):
|
||||
def connect(self):
|
||||
self.sock = socket(AF_UNIX, SOCK_STREAM)
|
||||
self.sock.connect(self.host)
|
||||
|
||||
class UnixHTTP(HTTP):
|
||||
_connection_class = UnixHTTPConnection
|
||||
|
||||
class UnixTransport(TransportMixIn, XMLTransport):
|
||||
def make_connection(self, host):
|
||||
import httplib
|
||||
host, extra_headers, x509 = self.get_host_info(host)
|
||||
return UnixHTTP(host)
|
||||
|
||||
|
||||
class ServerProxy(XMLServerProxy):
|
||||
"""
|
||||
Unfortunately, much more of this class has to be copied since
|
||||
so much of it does the serialization.
|
||||
"""
|
||||
|
||||
def __init__(self, uri, transport=None, encoding=None,
|
||||
verbose=0, version=None):
|
||||
import urllib
|
||||
if not version:
|
||||
version = config.version
|
||||
self.__version = version
|
||||
schema, uri = urllib.splittype(uri)
|
||||
if schema not in ('http', 'https', 'unix'):
|
||||
raise IOError('Unsupported JSON-RPC protocol.')
|
||||
if schema == 'unix':
|
||||
if not USE_UNIX_SOCKETS:
|
||||
# Don't like the "generic" Exception...
|
||||
raise UnixSocketMissing("Unix sockets not available.")
|
||||
self.__host = uri
|
||||
self.__handler = '/'
|
||||
else:
|
||||
self.__host, self.__handler = urllib.splithost(uri)
|
||||
if not self.__handler:
|
||||
# Not sure if this is in the JSON spec?
|
||||
#self.__handler = '/'
|
||||
self.__handler == '/'
|
||||
if transport is None:
|
||||
if schema == 'unix':
|
||||
transport = UnixTransport()
|
||||
elif schema == 'https':
|
||||
transport = SafeTransport()
|
||||
else:
|
||||
transport = Transport()
|
||||
self.__transport = transport
|
||||
self.__encoding = encoding
|
||||
self.__verbose = verbose
|
||||
|
||||
def _request(self, methodname, params, rpcid=None):
|
||||
request = dumps(params, methodname, encoding=self.__encoding,
|
||||
rpcid=rpcid, version=self.__version)
|
||||
response = self._run_request(request)
|
||||
check_for_errors(response)
|
||||
return response['result']
|
||||
|
||||
def _request_notify(self, methodname, params, rpcid=None):
|
||||
request = dumps(params, methodname, encoding=self.__encoding,
|
||||
rpcid=rpcid, version=self.__version, notify=True)
|
||||
response = self._run_request(request, notify=True)
|
||||
check_for_errors(response)
|
||||
return
|
||||
|
||||
def _run_request(self, request, notify=None):
|
||||
history.add_request(request)
|
||||
|
||||
response = self.__transport.request(
|
||||
self.__host,
|
||||
self.__handler,
|
||||
request,
|
||||
verbose=self.__verbose
|
||||
)
|
||||
|
||||
# Here, the XMLRPC library translates a single list
|
||||
# response to the single value -- should we do the
|
||||
# same, and require a tuple / list to be passed to
|
||||
# the response object, or expect the Server to be
|
||||
# outputting the response appropriately?
|
||||
|
||||
history.add_response(response)
|
||||
if not response:
|
||||
return None
|
||||
return_obj = loads(response)
|
||||
return return_obj
|
||||
|
||||
def __getattr__(self, name):
|
||||
# Same as original, just with new _Method reference
|
||||
return _Method(self._request, name)
|
||||
|
||||
@property
|
||||
def _notify(self):
|
||||
# Just like __getattr__, but with notify namespace.
|
||||
return _Notify(self._request_notify)
|
||||
|
||||
|
||||
class _Method(XML_Method):
|
||||
|
||||
def __call__(self, *args, **kwargs):
|
||||
if len(args) > 0 and len(kwargs) > 0:
|
||||
raise ProtocolError('Cannot use both positional ' +
|
||||
'and keyword arguments (according to JSON-RPC spec.)')
|
||||
if len(args) > 0:
|
||||
return self.__send(self.__name, args)
|
||||
else:
|
||||
return self.__send(self.__name, kwargs)
|
||||
|
||||
def __getattr__(self, name):
|
||||
self.__name = '%s.%s' % (self.__name, name)
|
||||
return self
|
||||
# The old method returned a new instance, but this seemed wasteful.
|
||||
# The only thing that changes is the name.
|
||||
#return _Method(self.__send, "%s.%s" % (self.__name, name))
|
||||
|
||||
class _Notify(object):
|
||||
def __init__(self, request):
|
||||
self._request = request
|
||||
|
||||
def __getattr__(self, name):
|
||||
return _Method(self._request, name)
|
||||
|
||||
# Batch implementation
|
||||
|
||||
class MultiCallMethod(object):
|
||||
|
||||
def __init__(self, method, notify=False):
|
||||
self.method = method
|
||||
self.params = []
|
||||
self.notify = notify
|
||||
|
||||
def __call__(self, *args, **kwargs):
|
||||
if len(kwargs) > 0 and len(args) > 0:
|
||||
raise ProtocolError('JSON-RPC does not support both ' +
|
||||
'positional and keyword arguments.')
|
||||
if len(kwargs) > 0:
|
||||
self.params = kwargs
|
||||
else:
|
||||
self.params = args
|
||||
|
||||
def request(self, encoding=None, rpcid=None):
|
||||
return dumps(self.params, self.method, version=2.0,
|
||||
encoding=encoding, rpcid=rpcid, notify=self.notify)
|
||||
|
||||
def __repr__(self):
|
||||
return '%s' % self.request()
|
||||
|
||||
def __getattr__(self, method):
|
||||
new_method = '%s.%s' % (self.method, method)
|
||||
self.method = new_method
|
||||
return self
|
||||
|
||||
class MultiCallNotify(object):
|
||||
|
||||
def __init__(self, multicall):
|
||||
self.multicall = multicall
|
||||
|
||||
def __getattr__(self, name):
|
||||
new_job = MultiCallMethod(name, notify=True)
|
||||
self.multicall._job_list.append(new_job)
|
||||
return new_job
|
||||
|
||||
class MultiCallIterator(object):
|
||||
|
||||
def __init__(self, results):
|
||||
self.results = results
|
||||
|
||||
def __iter__(self):
|
||||
for i in range(0, len(self.results)):
|
||||
yield self[i]
|
||||
raise StopIteration
|
||||
|
||||
def __getitem__(self, i):
|
||||
item = self.results[i]
|
||||
check_for_errors(item)
|
||||
return item['result']
|
||||
|
||||
def __len__(self):
|
||||
return len(self.results)
|
||||
|
||||
class MultiCall(object):
|
||||
|
||||
def __init__(self, server):
|
||||
self._server = server
|
||||
self._job_list = []
|
||||
|
||||
def _request(self):
|
||||
if len(self._job_list) < 1:
|
||||
# Should we alert? This /is/ pretty obvious.
|
||||
return
|
||||
request_body = '[ %s ]' % ','.join([job.request() for
|
||||
job in self._job_list])
|
||||
responses = self._server._run_request(request_body)
|
||||
del self._job_list[:]
|
||||
if not responses:
|
||||
responses = []
|
||||
return MultiCallIterator(responses)
|
||||
|
||||
@property
|
||||
def _notify(self):
|
||||
return MultiCallNotify(self)
|
||||
|
||||
def __getattr__(self, name):
|
||||
new_job = MultiCallMethod(name)
|
||||
self._job_list.append(new_job)
|
||||
return new_job
|
||||
|
||||
__call__ = _request
|
||||
|
||||
# These lines conform to xmlrpclib's "compatibility" line.
|
||||
# Not really sure if we should include these, but oh well.
|
||||
Server = ServerProxy
|
||||
|
||||
class Fault(object):
|
||||
# JSON-RPC error class
|
||||
def __init__(self, code=-32000, message='Server error', rpcid=None):
|
||||
self.faultCode = code
|
||||
self.faultString = message
|
||||
self.rpcid = rpcid
|
||||
|
||||
def error(self):
|
||||
return {'code':self.faultCode, 'message':self.faultString}
|
||||
|
||||
def response(self, rpcid=None, version=None):
|
||||
if not version:
|
||||
version = config.version
|
||||
if rpcid:
|
||||
self.rpcid = rpcid
|
||||
return dumps(
|
||||
self, methodresponse=True, rpcid=self.rpcid, version=version
|
||||
)
|
||||
|
||||
def __repr__(self):
|
||||
return '<Fault %s: %s>' % (self.faultCode, self.faultString)
|
||||
|
||||
def random_id(length=8):
|
||||
return_id = ''
|
||||
for i in range(length):
|
||||
return_id += random.choice(IDCHARS)
|
||||
return return_id
|
||||
|
||||
class Payload(dict):
|
||||
def __init__(self, rpcid=None, version=None):
|
||||
if not version:
|
||||
version = config.version
|
||||
self.id = rpcid
|
||||
self.version = float(version)
|
||||
|
||||
def request(self, method, params=[]):
|
||||
if type(method) not in types.StringTypes:
|
||||
raise ValueError('Method name must be a string.')
|
||||
if not self.id:
|
||||
self.id = random_id()
|
||||
request = { 'id':self.id, 'method':method }
|
||||
if params:
|
||||
request['params'] = params
|
||||
if self.version >= 2:
|
||||
request['jsonrpc'] = str(self.version)
|
||||
return request
|
||||
|
||||
def notify(self, method, params=[]):
|
||||
request = self.request(method, params)
|
||||
if self.version >= 2:
|
||||
del request['id']
|
||||
else:
|
||||
request['id'] = None
|
||||
return request
|
||||
|
||||
def response(self, result=None):
|
||||
response = {'result':result, 'id':self.id}
|
||||
if self.version >= 2:
|
||||
response['jsonrpc'] = str(self.version)
|
||||
else:
|
||||
response['error'] = None
|
||||
return response
|
||||
|
||||
def error(self, code=-32000, message='Server error.'):
|
||||
error = self.response()
|
||||
if self.version >= 2:
|
||||
del error['result']
|
||||
else:
|
||||
error['result'] = None
|
||||
error['error'] = {'code':code, 'message':message}
|
||||
return error
|
||||
|
||||
def dumps(params=[], methodname=None, methodresponse=None,
|
||||
encoding=None, rpcid=None, version=None, notify=None):
|
||||
"""
|
||||
This differs from the Python implementation in that it implements
|
||||
the rpcid argument since the 2.0 spec requires it for responses.
|
||||
"""
|
||||
if not version:
|
||||
version = config.version
|
||||
valid_params = (types.TupleType, types.ListType, types.DictType)
|
||||
if methodname in types.StringTypes and \
|
||||
type(params) not in valid_params and \
|
||||
not isinstance(params, Fault):
|
||||
"""
|
||||
If a method, and params are not in a listish or a Fault,
|
||||
error out.
|
||||
"""
|
||||
raise TypeError('Params must be a dict, list, tuple or Fault ' +
|
||||
'instance.')
|
||||
# Begin parsing object
|
||||
payload = Payload(rpcid=rpcid, version=version)
|
||||
if not encoding:
|
||||
encoding = 'utf-8'
|
||||
if type(params) is Fault:
|
||||
response = payload.error(params.faultCode, params.faultString)
|
||||
return jdumps(response, encoding=encoding)
|
||||
if type(methodname) not in types.StringTypes and methodresponse != True:
|
||||
raise ValueError('Method name must be a string, or methodresponse '+
|
||||
'must be set to True.')
|
||||
if config.use_jsonclass == True:
|
||||
from jsonrpclib import jsonclass
|
||||
params = jsonclass.dump(params)
|
||||
if methodresponse is True:
|
||||
if rpcid is None:
|
||||
raise ValueError('A method response must have an rpcid.')
|
||||
response = payload.response(params)
|
||||
return jdumps(response, encoding=encoding)
|
||||
request = None
|
||||
if notify == True:
|
||||
request = payload.notify(methodname, params)
|
||||
else:
|
||||
request = payload.request(methodname, params)
|
||||
return jdumps(request, encoding=encoding)
|
||||
|
||||
def loads(data):
|
||||
"""
|
||||
This differs from the Python implementation, in that it returns
|
||||
the request structure in Dict format instead of the method, params.
|
||||
It will return a list in the case of a batch request / response.
|
||||
"""
|
||||
if data == '':
|
||||
# notification
|
||||
return None
|
||||
result = jloads(data)
|
||||
# if the above raises an error, the implementing server code
|
||||
# should return something like the following:
|
||||
# { 'jsonrpc':'2.0', 'error': fault.error(), id: None }
|
||||
if config.use_jsonclass == True:
|
||||
from jsonrpclib import jsonclass
|
||||
result = jsonclass.load(result)
|
||||
return result
|
||||
|
||||
def check_for_errors(result):
|
||||
if not result:
|
||||
# Notification
|
||||
return result
|
||||
if type(result) is not types.DictType:
|
||||
raise TypeError('Response is not a dict.')
|
||||
if 'jsonrpc' in result.keys() and float(result['jsonrpc']) > 2.0:
|
||||
raise NotImplementedError('JSON-RPC version not yet supported.')
|
||||
if 'result' not in result.keys() and 'error' not in result.keys():
|
||||
raise ValueError('Response does not have a result or error key.')
|
||||
if 'error' in result.keys() and result['error'] != None:
|
||||
code = result['error']['code']
|
||||
message = result['error']['message']
|
||||
raise ProtocolError((code, message))
|
||||
return result
|
||||
|
||||
def isbatch(result):
|
||||
if type(result) not in (types.ListType, types.TupleType):
|
||||
return False
|
||||
if len(result) < 1:
|
||||
return False
|
||||
if type(result[0]) is not types.DictType:
|
||||
return False
|
||||
if 'jsonrpc' not in result[0].keys():
|
||||
return False
|
||||
try:
|
||||
version = float(result[0]['jsonrpc'])
|
||||
except ValueError:
|
||||
raise ProtocolError('"jsonrpc" key must be a float(able) value.')
|
||||
if version < 2:
|
||||
return False
|
||||
return True
|
||||
|
||||
def isnotification(request):
|
||||
if 'id' not in request.keys():
|
||||
# 2.0 notification
|
||||
return True
|
||||
if request['id'] == None:
|
||||
# 1.0 notification
|
||||
return True
|
||||
return False
|
Loading…
Reference in a new issue