1
2
3
4
5
6
7
8
9
10
11
12 import cjson
13 import SocketServer
14 import SimpleHTTPServer
15 import BaseHTTPServer
16 import sys
17 import traceback
18 import socket
19 import os
20 import cgi
21 import urllib
22 try:
23 import fcntl
24 except ImportError:
25 fcntl = None
26 try:
27 from cStringIO import StringIO
28 except ImportError:
29 from StringIO import StringIO
30
31
32
33
34
35 import SimpleXMLRPCServer
36
38 return html.replace("&", "&").replace("<", "<").replace(">", ">")
39
42 id = None
43 try:
44 print "about to decode. pid:", os.getpid(), repr(data)
45 req = cjson.decode(data)
46 method = req['method']
47 params = req['params'] or ()
48 id = req['id']
49 print "decoded and about to call. pid:", os.getpid(), method, params
50
51 if dispatch_method is not None:
52 result = dispatch_method(method, params)
53 else:
54 result = self._dispatch(method, params)
55 response = dict(id=id, result=result, error=None)
56 except:
57 extpe, exv, extrc = sys.exc_info()
58 err = dict(type=str(extpe),
59 message=str(exv),
60 traceback=''.join(traceback.format_tb(extrc)))
61 response = dict(id=id, result=None, error=err)
62 try:
63 return cjson.encode(response)
64 except:
65 extpe, exv, extrc = sys.exc_info()
66 err = dict(type=str(extpe),
67 message=str(exv),
68 traceback=''.join(traceback.format_tb(extrc)))
69 response = dict(id=id, result=None, error=err)
70 return cjson.encode(response)
71
72
74 """Simple JSONRPC request handler class and HTTP GET Server
75
76 Handles all HTTP POST requests and attempts to decode them as
77 JSONRPC requests.
78
79 Handles all HTTP GET requests and serves the content from the
80 current directory.
81
82 """
83
84
85
86 rpc_paths = ('/', '/JSON')
87
94
96 """Common code for GET and HEAD commands.
97
98 This sends the response code and MIME headers.
99
100 Return value is either a file object (which has to be copied
101 to the outputfile by the caller unless the command was HEAD,
102 and must be closed by the caller under all circumstances), or
103 None, in which case the caller has nothing further to do.
104
105 """
106 print "send_head. pid:", os.getpid()
107 path = self.translate_path(self.path)
108 f = None
109 if os.path.isdir(path):
110 if not self.path.endswith('/'):
111
112 self.send_response(301)
113 self.send_header("Location", self.path + "/")
114 self.end_headers()
115 return None
116 for index in "index.html", "index.htm":
117 index = os.path.join(path, index)
118 if os.path.exists(index):
119 path = index
120 break
121 else:
122 return self.list_directory(path)
123 ctype = self.guess_type(path)
124 if ctype.startswith('text/'):
125 mode = 'r'
126 else:
127 mode = 'rb'
128 try:
129 f = open(path, mode)
130 except IOError:
131 self.send_error(404, "File not found")
132 return None
133 self.send_response(200)
134 self.send_header("Content-type", ctype)
135 fs = os.fstat(f.fileno())
136 self.send_header("Content-Length", str(fs[6]))
137 self.send_header("Last-Modified", self.date_time_string(fs.st_mtime))
138 if self.request_version == "HTTP/1.1":
139 self.send_header("Connection", "keep-alive")
140
141 self.end_headers()
142 return f
143
145 """Helper to produce a directory listing (absent index.html).
146
147 Return value is either a file object, or None (indicating an
148 error). In either case, the headers are sent, making the
149 interface the same as for send_head().
150
151 """
152 try:
153 list = os.listdir(path)
154 except os.error:
155 self.send_error(404, "No permission to list directory")
156 return None
157 list.sort(key=lambda a: a.lower())
158 f = StringIO()
159 displaypath = cgi.escape(urllib.unquote(self.path))
160 f.write("<title>Directory listing for %s</title>\n" % displaypath)
161 f.write("<h2>Directory listing for %s</h2>\n" % displaypath)
162 f.write("<hr>\n<ul>\n")
163 for name in list:
164 fullname = os.path.join(path, name)
165 displayname = linkname = name
166
167 if os.path.isdir(fullname):
168 displayname = name + "/"
169 linkname = name + "/"
170 if os.path.islink(fullname):
171 displayname = name + "@"
172
173 f.write('<li><a href="%s">%s</a>\n'
174 % (urllib.quote(linkname), cgi.escape(displayname)))
175 f.write("</ul>\n<hr>\n")
176 f.write("\n<hr>\nCookies: %s\n<hr>\n" % str(self.headers.get("Cookie", None)))
177 length = f.tell()
178 f.seek(0)
179 self.send_response(200)
180 self.send_header("Content-type", "text/html")
181 if self.request_version == "HTTP/1.1":
182 self.send_header("Connection", "keep-alive")
183 self.send_header("Content-Length", str(length))
184 self.end_headers()
185 return f
186
188 """Send and log an error reply.
189
190 Arguments are the error code, and a detailed message.
191 The detailed message defaults to the short entry matching the
192 response code.
193
194 This sends an error response (so it must be called before any
195 output has been generated), logs the error, and finally sends
196 a piece of HTML explaining the error to the user.
197
198 """
199
200 try:
201 short, long = self.responses[code]
202 except KeyError:
203 short, long = '???', '???'
204 if message is None:
205 message = short
206 explain = long
207 self.log_error("code %d, message %s", code, message)
208
209 content = (self.error_message_format %
210 {'code': code, 'message': _quote_html(message), 'explain': explain})
211 self.send_response(code, message)
212 self.send_header("Content-Type", "text/html")
213 if self.command != 'HEAD' and code >= 200 and code not in (204, 304):
214 self.send_header("Content-Length", str(len(content)))
215 if self.request_version == "HTTP/1.1":
216 self.send_header("Connection", "keep-alive")
217 else:
218 self.send_header('Connection', 'close')
219 self.end_headers()
220 if self.command != 'HEAD' and code >= 200 and code not in (204, 304):
221 self.wfile.write(content)
222
224 """Handles the HTTP POST request.
225
226 Attempts to interpret all HTTP POST requests as XML-RPC calls,
227 which are forwarded to the server's _dispatch method for handling.
228 """
229
230 self.connection.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1)
231
232
233 if not self.is_rpc_path_valid():
234 self.report_404()
235 return
236
237 try:
238
239
240
241
242 max_chunk_size = 10*1024*1024
243 size_remaining = int(self.headers["content-length"])
244 L = []
245 while size_remaining:
246 chunk_size = min(size_remaining, max_chunk_size)
247 L.append(self.rfile.read(chunk_size))
248 size_remaining -= len(L[-1])
249 data = ''.join(L)
250
251
252
253
254
255
256 response = self.server._marshaled_dispatch(
257 data, getattr(self, '_dispatch', None)
258 )
259 except:
260
261 self.send_response(500)
262 self.end_headers()
263 else:
264
265 self.send_response(200)
266 self.send_header("Content-type", "text/x-json")
267 self.send_header("Connection", "keep-alive")
268 self.send_header("Content-length", str(len(response)))
269 self.end_headers()
270 print "response", repr(response)
271 self.wfile.write(response)
272
273 self.wfile.flush()
274 print "flushed"
275
277
278 self.send_response(404)
279 response = 'No such page'
280 self.send_header("Content-type", "text/plain")
281 self.send_header("Content-length", str(len(response)))
282 self.end_headers()
283 self.wfile.write(response)
284
285 self.wfile.flush()
286 self.connection.shutdown(1)
287
289 """Selectively log an accepted request."""
290
291 if self.server.logRequests:
292 BaseHTTPServer.BaseHTTPRequestHandler.log_request(self, code, size)
293
294
297 """Simple JSON-RPC server.
298
299 Simple JSON-RPC server that allows functions and a single instance
300 to be installed to handle requests. The default implementation
301 attempts to dispatch JSON-RPC calls to the functions or instance
302 installed in the server. Override the _dispatch method inhereted
303 from SimpleJSONRPCDispatcher to change this behavior.
304 """
305
306 allow_reuse_address = True
307
322
325 """Simple JSON-RPC server.
326
327 Simple JSON-RPC server that allows functions and a single instance
328 to be installed to handle requests. The default implementation
329 attempts to dispatch JSON-RPC calls to the functions or instance
330 installed in the server. Override the _dispatch method inhereted
331 from SimpleJSONRPCDispatcher to change this behavior.
332 """
333
334 allow_reuse_address = True
335
350
351
352
353
354
355 import xmlrpclib
356
359 -class Fault(xmlrpclib.ResponseError):
361
363 data = ""
364 while 1:
365 if sock:
366 response = sock.recv(1024)
367 else:
368 response = file.read(1024)
369 if not response:
370 break
371 data += response
372
373 file.close()
374
375 return data
376
379 return _get_response(file, sock)
380
383 return _get_response(file, sock)
384
386 - def __init__(self, uri, id=None, transport=None, use_datetime=0):
387
388
389
390 import urllib
391 type, uri = urllib.splittype(uri)
392 if type not in ("http", "https"):
393 raise IOError, "unsupported JSON-RPC protocol"
394 self.__host, self.__handler = urllib.splithost(uri)
395 if not self.__handler:
396 self.__handler = "/JSON"
397
398 if transport is None:
399 if type == "https":
400 transport = SafeTransport(use_datetime=use_datetime)
401 else:
402 transport = Transport(use_datetime=use_datetime)
403
404 self.__transport = transport
405 self.__id = id
406
408
409
410 request = cjson.encode(dict(id=self.__id, method=methodname,
411 params=params))
412
413 data = self.__transport.request(
414 self.__host,
415 self.__handler,
416 request,
417 verbose=False
418 )
419
420 response = cjson.decode(data)
421
422 if response["id"] != self.__id:
423 raise ResponseError("Invalid request id (is: %s, expected: %s)" \
424 % (response["id"], self.__id))
425 if response["error"] is not None:
426 raise Fault("JSON Error", response["error"])
427 return response["result"]
428
430 return (
431 "<ServerProxy for %s%s>" %
432 (self.__host, self.__handler)
433 )
434
435 __str__ = __repr__
436
438
439 return xmlrpclib._Method(self.__request, name)
440
441
442 if __name__ == '__main__':
443 if not len(sys.argv) > 1:
444 import socket
445 print 'Running JSON-RPC server on port 8000'
446 server = SimpleJSONRPCServer(("localhost", 8000))
447 server.register_function(pow)
448 server.register_function(lambda x,y: x+y, 'add')
449 server.register_function(lambda x: x, 'echo')
450 server.serve_forever()
451 else:
452 remote = ServerProxy(sys.argv[1])
453 print 'Using connection', remote
454
455 print repr(remote.add(1, 2))
456 aaa = remote.add
457 print repr(remote.pow(2, 4))
458 print aaa(5, 6)
459
460 try:
461
462 aaa(5, "toto")
463 print "Successful execution of invalid code"
464 except Fault:
465 pass
466
467 try:
468
469 aaa(5, 6, 7)
470 print "Successful execution of invalid code"
471 except Fault:
472 pass
473
474 try:
475
476 print repr(remote.powx(2, 4))
477 print "Successful execution of invalid code"
478 except Fault:
479 pass
480