1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213 | # Heavily based on the XML-RPC implementation in python.
# Based on the json-rpc specs: http://json-rpc.org/wiki/specification
# The main deviation is on the error treatment. The official spec
# would set the 'error' attribute to a string. This implementation
# sets it to a dictionary with keys: message/traceback/type
import cjson
import SocketServer
import sys
import traceback
try:
import fcntl
except ImportError:
fcntl = None
###
### Server code
###
import SimpleXMLRPCServer
class SimpleJSONRPCDispatcher(SimpleXMLRPCServer.SimpleXMLRPCDispatcher):
def _marshaled_dispatch(self, data, dispatch_method = None):
id = None
try:
req = cjson.decode(data)
method = req['method']
params = req['params']
id = req['id']
if dispatch_method is not None:
result = dispatch_method(method, params)
else:
result = self._dispatch(method, params)
response = dict(id=id, result=result, error=None)
except:
extpe, exv, extrc = sys.exc_info()
err = dict(type=str(extpe),
message=str(exv),
traceback=''.join(traceback.format_tb(extrc)))
response = dict(id=id, result=None, error=err)
try:
return cjson.encode(response)
except:
extpe, exv, extrc = sys.exc_info()
err = dict(type=str(extpe),
message=str(exv),
traceback=''.join(traceback.format_tb(extrc)))
response = dict(id=id, result=None, error=err)
return cjson.encode(response)
class SimpleJSONRPCRequestHandler(SimpleXMLRPCServer.SimpleXMLRPCRequestHandler):
# Class attribute listing the accessible path components;
# paths not on this list will result in a 404 error.
rpc_paths = ('/', '/JSON')
class SimpleJSONRPCServer(SocketServer.TCPServer,
SimpleJSONRPCDispatcher):
"""Simple JSON-RPC server.
Simple JSON-RPC server that allows functions and a single instance
to be installed to handle requests. The default implementation
attempts to dispatch JSON-RPC calls to the functions or instance
installed in the server. Override the _dispatch method inhereted
from SimpleJSONRPCDispatcher to change this behavior.
"""
allow_reuse_address = True
def __init__(self, addr, requestHandler=SimpleJSONRPCRequestHandler,
logRequests=True):
self.logRequests = logRequests
SimpleJSONRPCDispatcher.__init__(self, allow_none=True, encoding=None)
SocketServer.TCPServer.__init__(self, addr, requestHandler)
# [Bug #1222790] If possible, set close-on-exec flag; if a
# method spawns a subprocess, the subprocess shouldn't have
# the listening socket open.
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)
###
### Client code
###
import xmlrpclib
class ResponseError(xmlrpclib.ResponseError):
pass
class Fault(xmlrpclib.ResponseError):
pass
def _get_response(file, sock):
data = ""
while 1:
if sock:
response = sock.recv(1024)
else:
response = file.read(1024)
if not response:
break
data += response
file.close()
return data
class Transport(xmlrpclib.Transport):
def _parse_response(self, file, sock):
return _get_response(file, sock)
class SafeTransport(xmlrpclib.SafeTransport):
def _parse_response(self, file, sock):
return _get_response(file, sock)
class ServerProxy:
def __init__(self, uri, id=None, transport=None, use_datetime=0):
# establish a "logical" server connection
# get the url
import urllib
type, uri = urllib.splittype(uri)
if type not in ("http", "https"):
raise IOError, "unsupported JSON-RPC protocol"
self.__host, self.__handler = urllib.splithost(uri)
if not self.__handler:
self.__handler = "/JSON"
if transport is None:
if type == "https":
transport = SafeTransport(use_datetime=use_datetime)
else:
transport = Transport(use_datetime=use_datetime)
self.__transport = transport
self.__id = id
def __request(self, methodname, params):
# call a method on the remote server
request = cjson.encode(dict(id=self.__id, method=methodname,
params=params))
data = self.__transport.request(
self.__host,
self.__handler,
request,
verbose=False
)
response = cjson.decode(data)
if response["id"] != self.__id:
raise ResponseError("Invalid request id (is: %s, expected: %s)" \
% (response["id"], self.__id))
if response["error"] is not None:
raise Fault("JSON Error", response["error"])
return response["result"]
def __repr__(self):
return (
"<ServerProxy for %s%s>" %
(self.__host, self.__handler)
)
__str__ = __repr__
def __getattr__(self, name):
# magic method dispatcher
return xmlrpclib._Method(self.__request, name)
if __name__ == '__main__':
if not len(sys.argv) > 1:
import socket
print 'Running JSON-RPC server on port 8000'
server = SimpleJSONRPCServer(("localhost", 8000))
server.register_function(pow)
server.register_function(lambda x,y: x+y, 'add')
server.serve_forever()
else:
remote = ServerProxy(sys.argv[1])
print 'Using connection', remote
print repr(remote.add(1, 2))
aaa = remote.add
print repr(remote.pow(2, 4))
print aaa(5, 6)
try:
# Invalid parameters
aaa(5, "toto")
print "Successful execution of invalid code"
except Fault:
pass
try:
# Invalid parameters
aaa(5, 6, 7)
print "Successful execution of invalid code"
except Fault:
pass
try:
# Invalid method name
print repr(remote.powx(2, 4))
print "Successful execution of invalid code"
except Fault:
pass
|
cjson is buggy, use jsonlib or demjson instead. The linked code uses cjson for JSON serialization/deserialization. cjson is very buggy - please use jsonlib[1] or demjson[2] (in strict mode) instead.
[1] http://pypi.python.org/pypi/jsonlib/
[2] http://pypi.python.org/pypi/demjson
WSGI. This is a lot easier to do with WSGI. I wrote up a tutorial linked from here: http://blog.ianbicking.org/2008/04/02/json-rpc-webob-example/