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