Created
February 22, 2012 20:03
-
-
Save mamcx/1886919 to your computer and use it in GitHub Desktop.
jsonrpclib for dataabstract?
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# -*- coding: utf-8 -*- | |
# a port of xmlrpclib to json.... | |
# | |
# | |
# The JSON-RPC client interface is based on the XML-RPC client | |
# | |
# Copyright (c) 1999-2002 by Secret Labs AB | |
# Copyright (c) 1999-2002 by Fredrik Lundh | |
# Copyright (c) 2006 by Matt Harrison | |
# Copyright (c) 2008 by Mario Montoya (elmalabarista.com) | |
# | |
# By obtaining, using, and/or copying this software and/or its | |
# associated documentation, you agree that you have read, understood, | |
# and will comply with the following terms and conditions: | |
# | |
# Permission to use, copy, modify, and distribute this software and | |
# its associated documentation for any purpose and without fee is | |
# hereby granted, provided that the above copyright notice appears in | |
# all copies, and that both that copyright notice and this permission | |
# notice appear in supporting documentation, and that the name of | |
# Secret Labs AB or the author not be used in advertising or publicity | |
# pertaining to distribution of the software without specific, written | |
# prior permission. | |
# | |
# SECRET LABS AB AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD | |
# TO THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANT- | |
# ABILITY AND FITNESS. IN NO EVENT SHALL SECRET LABS AB OR THE AUTHOR | |
# BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY | |
# DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, | |
# WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS | |
# ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE | |
# OF THIS SOFTWARE. | |
# -------------------------------------------------------------------- | |
""" | |
jsonrpclib | |
---------- | |
The JSON-RPC client interface is based on the XML-RPC client | |
""" | |
import urllib | |
import httplib | |
import base64 | |
import json as simplejson | |
import types | |
from xmlrpclib import ProtocolError | |
__version__ = "0.0.1" | |
ID = 1 | |
def _gen_id(): | |
global ID | |
ID = ID + 1 | |
return ID | |
def getparser(): | |
un = Unmarshaller() | |
par = Parser(un) | |
return par,un | |
def dumps(params, methodname=None, methodresponse=None, encoding=None, | |
allow_none=0,Id=None): | |
if methodname: | |
request = {} | |
request["method"] = methodname | |
request["params"] = params | |
if Id: | |
request["id"] = Id | |
else: | |
request["_id"] = _gen_id() | |
return simplejson.dumps(request) | |
class Unmarshaller(object): | |
def __init__(self): | |
self.data = None | |
def feed(self, data): | |
if self.data is None: | |
self.data = data | |
else: | |
self.data = self.data + data | |
def close(self): | |
#try to convert string to json | |
return simplejson.loads(self.data) | |
class Parser(object): | |
def __init__(self, unmarshaller): | |
self._target = unmarshaller | |
self.data = None | |
def feed(self, data): | |
if self.data is None: | |
self.data = data | |
else: | |
self.data = self.data + data | |
def close(self): | |
self._target.feed(self.data) | |
class _Method(object): | |
# some magic to bind an JSON-RPC method to an RPC server. | |
# supports "nested" methods (e.g. examples.getStateName) | |
def __init__(self, send, name): | |
self.__send = send | |
self.__name = name | |
def __getattr__(self, name): | |
return _Method(self.__send, "%s.%s" % (self.__name, name)) | |
def __call__(self, *args): | |
return self.__send(self.__name, args) | |
## | |
# Standard transport class for JSON-RPC over HTTP. | |
# <p> | |
# You can create custom transports by subclassing this method, and | |
# overriding selected methods. | |
class Transport: | |
"""Handles an HTTP transaction to an JSON-RPC server.""" | |
# client identifier (may be overridden) | |
user_agent = "jsonlib.py/%s (by matt harrison)" % __version__ | |
## | |
# Send a complete request, and parse the response. | |
# | |
# @param host Target host. | |
# @param handler Target PRC handler. | |
# @param request_body JSON-RPC request body. | |
# @param verbose Debugging flag. | |
# @return Parsed response. | |
def request(self, host, handler, request_body, verbose=0): | |
# issue JSON-RPC request | |
try: | |
h = self.make_connection(host) | |
h.set_debuglevel(2) | |
if verbose: | |
h.set_debuglevel(1) | |
self.send_request(h, handler, request_body) | |
self.send_host(h, host) | |
self.send_user_agent(h) | |
self.send_content(h, request_body) | |
except IOError, e: | |
if (e.errno == 10061): #Clarify the meaning of this error. | |
raise IOError("Can't connect to BestSeller Server on %s.\n%s" % (host, str(e))) | |
errcode, errmsg, headers = h.getreply() | |
if errcode not in (200,500): | |
raise ProtocolError( | |
host + handler, | |
errcode, errmsg, | |
headers | |
) | |
expected_payload_length = 1024 | |
if headers.has_key('content-length'): | |
expected_payload_length = int(headers['content-length']) | |
self.verbose = verbose | |
try: | |
sock = h._conn.sock | |
except AttributeError: | |
sock = None | |
return self._parse_response(h.getfile(), sock, expected_payload_length) | |
## | |
# Create parser. | |
# | |
# @return A 2-tuple containing a parser and a unmarshaller. | |
def getparser(self): | |
# get parser and unmarshaller | |
return getparser() | |
## | |
# Get authorization info from host parameter | |
# Host may be a string, or a (host, x509-dict) tuple; if a string, | |
# it is checked for a "user:pw@host" format, and a "Basic | |
# Authentication" header is added if appropriate. | |
# | |
# @param host Host descriptor (URL or (URL, x509 info) tuple). | |
# @return A 3-tuple containing (actual host, extra headers, | |
# x509 info). The header and x509 fields may be None. | |
def get_host_info(self, host): | |
x509 = {} | |
if isinstance(host, types.TupleType): | |
host, x509 = host | |
auth, host = urllib.splituser(host) | |
if auth: | |
auth = base64.encodestring(urllib.unquote(auth)) | |
auth = string.join(string.split(auth), "") # get rid of whitespace | |
extra_headers = [ | |
("Authorization", "Basic " + auth) | |
] | |
else: | |
extra_headers = None | |
return host, extra_headers, x509 | |
## | |
# Connect to server. | |
# | |
# @param host Target host. | |
# @return A connection handle. | |
def make_connection(self, host): | |
# create a HTTP connection object from a host descriptor | |
host, extra_headers, x509 = self.get_host_info(host) | |
return httplib.HTTP(host) | |
## | |
# Send request header. | |
# | |
# @param connection Connection handle. | |
# @param handler Target RPC handler. | |
# @param request_body JSON-RPC body. | |
def send_request(self, connection, handler, request_body): | |
connection.putrequest("POST", handler) | |
## | |
# Send host name. | |
# | |
# @param connection Connection handle. | |
# @param host Host name. | |
def send_host(self, connection, host): | |
host, extra_headers, x509 = self.get_host_info(host) | |
connection.putheader("Host", host) | |
if extra_headers: | |
if isinstance(extra_headers, DictType): | |
extra_headers = extra_headers.items() | |
for key, value in extra_headers: | |
connection.putheader(key, value) | |
## | |
# Send user-agent identifier. | |
# | |
# @param connection Connection handle. | |
def send_user_agent(self, connection): | |
connection.putheader("User-Agent", self.user_agent) | |
## | |
# Send request body. | |
# | |
# @param connection Connection handle. | |
# @param request_body JSON-RPC request body. | |
def send_content(self, connection, request_body): | |
connection.putheader("Content-Type", "application/json") | |
connection.putheader("Content-Length", str(len(request_body))) | |
connection.endheaders() | |
if request_body: | |
connection.send(request_body) | |
## | |
# Parse response. | |
# | |
# @param file Stream. | |
# @return Response tuple and target method. | |
def parse_response(self, file, size): | |
# compatibility interface | |
return self._parse_response(file, None, size) | |
## | |
# Parse response (alternate interface). This is similar to the | |
# parse_response method, but also provides direct access to the | |
# underlying socket object (where available). | |
# | |
# @param file Stream. | |
# @param sock Socket handle (or None, if the socket object | |
# could not be accessed). | |
# @return Response tuple and target method. | |
def _parse_response(self, file, sock, size): | |
# read response from input file/socket, and parse it | |
p, u = self.getparser() | |
while 1: | |
if sock: | |
response = sock.recv(size) | |
else: | |
response = file.read(size) | |
if not response: | |
break | |
if self.verbose: | |
print "body:", repr(response) | |
p.feed(response) | |
if len(response) == size: | |
break | |
file.close() | |
p.close() | |
return u.close() | |
## | |
# Standard transport class for JSON-RPC over HTTPS. | |
class SafeTransport(Transport): | |
"""Handles an HTTPS transaction to an JSON-RPC server.""" | |
# FIXME: mostly untested | |
def make_connection(self, host): | |
# create a HTTPS connection object from a host descriptor | |
# host may be a string, or a (host, x509-dict) tuple | |
host, extra_headers, x509 = self.get_host_info(host) | |
try: | |
HTTPS = httplib.HTTPS | |
except AttributeError: | |
raise NotImplementedError( | |
"your version of httplib doesn't support HTTPS" | |
) | |
else: | |
return HTTPS(host, None, **(x509 or {})) | |
class ServerProxy(object): | |
def __init__(self, uri, transport=None, encoding=None, | |
verbose=None, allow_none=0, serviceName=""): | |
utype, uri = urllib.splittype(uri) | |
if utype not in ("http", "https"): | |
raise IOError, "Unsupported JSONRPC protocol" | |
self.__host, self.__handler = urllib.splithost(uri) | |
if not self.__handler: | |
self.__handler = "/RPC2" | |
if transport is None: | |
if utype == "https": | |
transport = SafeTransport() | |
else: | |
transport = Transport() | |
self.__transport = transport | |
self.__encoding = encoding | |
self.__verbose = verbose | |
self.__allow_none = allow_none | |
self.Id = None | |
self.serviceName = serviceName | |
def __request(self, methodname, params): | |
"""call a method on the remote server | |
""" | |
request = dumps(params, self.serviceName + "." + methodname, encoding=self.__encoding, | |
allow_none=self.__allow_none,Id=self.Id) | |
#print request | |
try: | |
response = self.__transport.request( | |
self.__host, | |
self.__handler, | |
request, | |
verbose=self.__verbose | |
) | |
except AttributeError,e: | |
raise Exception("Fail to connect to server. %s" % e) | |
if len(response) == 1: | |
response = response[0] | |
#print response | |
if response.has_key('id'): | |
self.Id = response['id'] | |
else: | |
self.Id = None | |
return response | |
def __repr__(self): | |
return ("<JSONProxy for %s%s>" % | |
(self.__host, self.__handler) | |
) | |
__str__ = __repr__ | |
def __getattr__(self, name): | |
#dispatch | |
return _Method(self.__request, name) | |
# note: to call a remote object with an non-standard name, use | |
# result getattr(server, "strange-python-name")(args) | |
if __name__ == "__main__": | |
s = ServerProxy("http://localhost:8080/foo/", verbose = 1) | |
c = s.echo("foo bar") | |
print c | |
d = s.bad("other") | |
print d | |
e = s.echo("foo bar", "baz") | |
print e | |
f = s.echo(5) | |
print f |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment