1
# Copyright (C) 2006-2007 Red Hat, Inc.
3
# This library is free software; you can redistribute it and/or
4
# modify it under the terms of the GNU Lesser General Public
5
# License as published by the Free Software Foundation; either
6
# version 2 of the License, or (at your option) any later version.
8
# This library is distributed in the hope that it will be useful,
9
# but WITHOUT ANY WARRANTY; without even the implied warranty of
10
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
11
# Lesser General Public License for more details.
13
# You should have received a copy of the GNU Lesser General Public
14
# License along with this library; if not, write to the
15
# Free Software Foundation, Inc., 59 Temple Place - Suite 330,
16
# Boston, MA 02111-1307, USA.
30
import SimpleXMLRPCServer
31
import SimpleHTTPServer
37
def _add_authinfo(authinfo):
38
__authinfos[threading.currentThread()] = authinfo
41
return __authinfos.get(threading.currentThread())
44
del __authinfos[threading.currentThread()]
47
class GlibTCPServer(SocketServer.TCPServer):
50
Integrate socket accept into glib mainloop.
53
allow_reuse_address = True
54
request_queue_size = 20
56
def __init__(self, server_address, RequestHandlerClass):
57
SocketServer.TCPServer.__init__(self, server_address,
59
self.socket.setblocking(0) # Set nonblocking
61
# Watch the listener socket for data
62
gobject.io_add_watch(self.socket, gobject.IO_IN, self._handle_accept)
64
def _handle_accept(self, source, condition):
65
"""Process incoming data on the server's socket by doing an accept()
66
via handle_request()."""
67
if not (condition & gobject.IO_IN):
72
def close_request(self, request):
73
"""Called to clean up an individual request."""
74
# let the request be closed by the request handler when its done
78
class ChunkedGlibHTTPRequestHandler(SimpleHTTPServer.SimpleHTTPRequestHandler):
79
"""RequestHandler class that integrates with Glib mainloop. It writes
80
the specified file to the client in chunks, returning control to the
81
mainloop between chunks.
86
def __init__(self, request, client_address, server):
89
SimpleHTTPServer.SimpleHTTPRequestHandler.__init__(
90
self, request, client_address, server)
92
def log_request(self, code='-', size='-'):
96
"""Serve a GET request."""
97
self._file = self.send_head()
99
self._srcid = gobject.io_add_watch(self.wfile, gobject.IO_OUT |
101
self._send_next_chunk)
106
def _send_next_chunk(self, source, condition):
107
if condition & gobject.IO_ERR:
110
if not (condition & gobject.IO_OUT):
113
data = self._file.read(self.CHUNK_SIZE)
114
count = os.write(self.wfile.fileno(), data)
115
if count != len(data) or len(data) != self.CHUNK_SIZE:
125
gobject.source_remove(self._srcid)
127
if not self.wfile.closed:
133
"""Close the sockets when we're done, not before"""
137
"""Common code for GET and HEAD commands.
139
This sends the response code and MIME headers.
141
Return value is either a file object (which has to be copied
142
to the outputfile by the caller unless the command was HEAD,
143
and must be closed by the caller under all circumstances), or
144
None, in which case the caller has nothing further to do.
146
** [dcbw] modified to send Content-disposition filename too
148
path = self.translate_path(self.path)
149
if not path or not os.path.exists(path):
150
self.send_error(404, "File not found")
154
if os.path.isdir(path):
155
for index in "index.html", "index.htm":
156
index = os.path.join(path, index)
157
if os.path.exists(index):
161
return self.list_directory(path)
162
ctype = self.guess_type(path)
164
# Always read in binary mode. Opening files in text mode may cause
165
# newline translations, making the actual size of the content
166
# transmitted *less* than the content-length!
169
self.send_error(404, "File not found")
171
self.send_response(200)
172
self.send_header("Content-type", ctype)
173
self.send_header("Content-Length", str(os.fstat(f.fileno())[6]))
174
self.send_header("Content-Disposition", 'attachment; filename="%s"' %
175
os.path.basename(path))
179
class GlibURLDownloader(gobject.GObject):
180
"""Grabs a URL in chunks, returning to the mainloop after each chunk"""
183
'finished': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE,
184
([gobject.TYPE_PYOBJECT, gobject.TYPE_PYOBJECT])),
185
'error': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE,
186
([gobject.TYPE_PYOBJECT])),
187
'progress': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE,
188
([gobject.TYPE_PYOBJECT]))
193
def __init__(self, url, destdir=None):
196
destdir = tempfile.gettempdir()
197
self._destdir = destdir
201
self._suggested_fname = None
204
gobject.GObject.__init__(self)
206
def start(self, destfile=None, destfd=None):
207
self._info = urllib.urlopen(self._url)
210
if destfd and not destfile:
211
raise ValueError("Must provide destination file too when" \
212
"specifying file descriptor")
214
self._suggested_fname = os.path.basename(destfile)
215
self._fname = os.path.abspath(os.path.expanduser(destfile))
217
# Use the user-supplied destination file descriptor
220
self._outf = os.open(self._fname, os.O_RDWR |
221
os.O_TRUNC | os.O_CREAT, 0644)
223
fname = self._get_filename_from_headers(self._info.headers)
224
self._suggested_fname = fname
225
garbage_, path = urllib.splittype(self._url)
226
garbage_, path = urllib.splithost(path or "")
227
path, garbage_ = urllib.splitquery(path or "")
228
path, garbage_ = urllib.splitattr(path or "")
229
suffix = os.path.splitext(path)[1]
230
(self._outf, self._fname) = tempfile.mkstemp(suffix=suffix,
233
fcntl.fcntl(self._info.fp.fileno(), fcntl.F_SETFD, os.O_NDELAY)
234
self._srcid = gobject.io_add_watch(self._info.fp.fileno(),
235
gobject.IO_IN | gobject.IO_ERR,
236
self._read_next_chunk)
240
raise RuntimeError("Download already canceled or stopped")
241
self.cleanup(remove=True)
243
def _get_filename_from_headers(self, headers):
244
if not headers.has_key("Content-Disposition"):
248
data = headers["Content-Disposition"]
249
fidx = data.find(ftag)
252
fname = data[fidx+len(ftag):]
253
if fname[0] == '"' or fname[0] == "'":
255
if fname[len(fname)-1] == '"' or fname[len(fname)-1] == "'":
256
fname = fname[:len(fname)-1]
259
def _read_next_chunk(self, source, condition):
260
if condition & gobject.IO_ERR:
261
self.cleanup(remove=True)
262
self.emit("error", "Error downloading file.")
264
elif not (condition & gobject.IO_IN):
265
# shouldn't get here, but...
269
data = self._info.fp.read(self.CHUNK_SIZE)
270
count = os.write(self._outf, data)
271
self._written += len(data)
273
# error writing data to file?
274
if count < len(data):
275
self.cleanup(remove=True)
276
self.emit("error", "Error writing to download file.")
279
self.emit("progress", self._written)
282
if len(data) < self.CHUNK_SIZE:
284
self.emit("finished", self._fname, self._suggested_fname)
286
except Exception, err:
287
self.cleanup(remove=True)
288
self.emit("error", "Error downloading file: %s" % err)
292
def cleanup(self, remove=False):
294
gobject.source_remove(self._srcid)
300
os.remove(self._fname)
304
class GlibXMLRPCRequestHandler(SimpleXMLRPCServer.SimpleXMLRPCRequestHandler):
305
""" GlibXMLRPCRequestHandler
307
The stock SimpleXMLRPCRequestHandler and server don't allow any way to pass
308
the client's address and/or SSL certificate into the function that actually
309
_processes_ the request. So we have to store it in a thread-indexed dict.
313
_add_authinfo(self.client_address)
315
SimpleXMLRPCServer.SimpleXMLRPCRequestHandler.do_POST(self)
316
except socket.timeout:
318
except socket.error, e:
319
print "Error (%s): socket error - '%s'" % (self.client_address, e)
321
print "Error while processing POST:"
322
traceback.print_exc()
325
class GlibXMLRPCServer(GlibTCPServer,
326
SimpleXMLRPCServer.SimpleXMLRPCDispatcher):
329
Use nonblocking sockets and handle the accept via glib rather than
330
blocking on accept().
333
def __init__(self, addr, requestHandler=GlibXMLRPCRequestHandler,
334
logRequests=0, allow_none=False):
335
self.logRequests = logRequests
336
if sys.version_info[:3] >= (2, 5, 0):
337
SimpleXMLRPCServer.SimpleXMLRPCDispatcher.__init__(
338
self, allow_none, encoding="utf-8")
340
SimpleXMLRPCServer.SimpleXMLRPCDispatcher.__init__(self)
341
GlibTCPServer.__init__(self, addr, requestHandler)
343
def _marshaled_dispatch(self, data, dispatch_method = None):
344
"""Dispatches an XML-RPC method from marshalled (XML) data.
346
XML-RPC methods are dispatched from the marshalled (XML) data
347
using the _dispatch method and the result is returned as
348
marshalled data. For backwards compatibility, a dispatch
349
function can be provided as an argument (see comment in
350
SimpleXMLRPCRequestHandler.do_POST) but overriding the
351
existing method through subclassing is the prefered means
352
of changing method dispatch behavior.
355
params, method = xmlrpclib.loads(data)
359
if dispatch_method is not None:
360
response = dispatch_method(method, params)
362
response = self._dispatch(method, params)
363
# wrap response in a singleton tuple
364
response = (response,)
365
response = xmlrpclib.dumps(response, methodresponse=1)
366
except xmlrpclib.Fault, fault:
367
response = xmlrpclib.dumps(fault)
369
print "Exception while processing request:"
370
traceback.print_exc()
372
# report exception back to server
373
response = xmlrpclib.dumps(
374
xmlrpclib.Fault(1, "%s:%s" % (sys.exc_type, sys.exc_value))
380
class GlibHTTP(httplib.HTTP):
381
"""Subclass HTTP so we can return it's connection class' socket."""
382
def connect(self, host=None, port=None):
383
httplib.HTTP.connect(self, host, port)
384
self._conn.sock.setblocking(0)
386
class GlibXMLRPCTransport(xmlrpclib.Transport):
387
"""Integrate the request with the glib mainloop rather than blocking."""
391
# @param host Target host.
392
# @return A connection handle.
394
def __init__(self, use_datetime=0):
396
if sys.version_info[:3] >= (2, 5, 0):
397
xmlrpclib.Transport.__init__(self, use_datetime)
399
def make_connection(self, host):
400
"""Use our own connection object so we can get its socket."""
401
# create a HTTP connection object from a host descriptor
402
host, extra_headers_, x509_ = self.get_host_info(host)
403
return GlibHTTP(host)
406
# Send a complete request, and parse the response.
408
# @param host Target host.
409
# @param handler Target PRC handler.
410
# @param request_body XML-RPC request body.
411
# @param verbose Debugging flag.
412
# @return Parsed response.
414
def start_request(self, host, handler, request_body, verbose=0,
415
reply_handler=None, error_handler=None, user_data=None):
416
"""Do the first half of the request by sending data to the remote
417
server. The bottom half bits get run when the remote server's response
418
actually comes back."""
419
# issue XML-RPC request
421
h = self.make_connection(host)
425
self.send_request(h, handler, request_body)
426
self.send_host(h, host)
427
self.send_user_agent(h)
428
self.send_content(h, request_body)
430
# Schedule a GIOWatch so we don't block waiting for the response
431
gobject.io_add_watch(h._conn.sock, gobject.IO_IN, self._finish_request,
432
h, host, handler, verbose, reply_handler, error_handler, user_data)
434
def _finish_request(self, source, condition, h, host, handler, verbose,
435
reply_handler=None, error_handler=None, user_data=None):
436
"""Parse and return response when the
437
remote server actually returns it."""
438
if not (condition & gobject.IO_IN):
442
errcode, errmsg, headers = h.getreply()
443
except socket.error, err:
445
raise socket.error(err)
448
gobject.idle_add(error_handler, err, user_data)
452
raise xmlrpclib.ProtocolError(host + handler, errcode,
454
self.verbose = verbose
455
response = self._parse_response(h.getfile(), h._conn.sock)
457
# Coerce to a list so we can append user data
458
response = response[0]
459
if not isinstance(response, list):
460
response = [response]
461
response.append(user_data)
462
gobject.idle_add(reply_handler, *response)
466
"""Right, so python people thought it would be funny to make this
467
class private to xmlrpclib.py..."""
468
# some magic to bind an XML-RPC method to an RPC server.
469
# supports "nested" methods (e.g. examples.getStateName)
470
def __init__(self, send, name):
473
def __getattr__(self, name):
474
return _Method(self.__send, "%s.%s" % (self.__name, name))
475
def __call__(self, *args, **kwargs):
476
return self.__send(self.__name, *args, **kwargs)
479
class GlibServerProxy(xmlrpclib.ServerProxy):
480
"""Subclass xmlrpclib.ServerProxy so we can run the XML-RPC request
481
in two parts, integrated with the glib mainloop, such that we don't
484
Using this object is somewhat special; it requires more arguments to each
485
XML-RPC request call than the normal xmlrpclib.ServerProxy object:
487
client = GlibServerProxy("http://127.0.0.1:8888")
491
client.test(xmlrpc_test_cb, user_data, xmlrpc_arg1, xmlrpc_arg2)
493
Here, 'xmlrpc_test_cb' is the callback function, which has the following
496
def xmlrpc_test_cb(result_status, response, user_data=None):
499
def __init__(self, uri, encoding=None, verbose=0, allow_none=0):
500
self._transport = GlibXMLRPCTransport()
501
self._encoding = encoding
502
self._verbose = verbose
503
self._allow_none = allow_none
504
xmlrpclib.ServerProxy.__init__(self, uri, self._transport,
505
encoding, verbose, allow_none)
508
urltype, uri = urllib.splittype(uri)
509
if urltype not in ("http", "https"):
510
raise IOError, "unsupported XML-RPC protocol"
511
self._host, self._handler = urllib.splithost(uri)
512
if not self._handler:
513
self._handler = "/RPC2"
515
def __request(self, methodname, *args, **kwargs):
516
"""Call the method on the remote server. We just start the request here
517
and the transport itself takes care of scheduling the response callback
518
when the remote server returns the response.
519
We don't want to block anywhere."""
521
request = xmlrpclib.dumps(args, methodname, encoding=self._encoding,
522
allow_none=self._allow_none)
524
reply_hdl = kwargs.get("reply_handler")
525
err_hdl = kwargs.get("error_handler")
526
udata = kwargs.get("user_data")
528
response = self._transport.start_request(
532
verbose=self._verbose,
533
reply_handler=reply_hdl,
534
error_handler=err_hdl,
537
except socket.error, exc:
539
gobject.idle_add(err_hdl, exc, udata)
541
def __getattr__(self, name):
542
# magic method dispatcher
543
return _Method(self.__request, name)
547
def test(self, arg1, arg2):
548
print "Request got %s, %s" % (arg1, arg2)
549
return "success", "bork"
551
def xmlrpc_success_cb(response, resp2, loop):
552
print "Response was %s %s" % (response, resp2)
555
def xmlrpc_error_cb(err, loop):
556
print "Error: %s" % err
559
def xmlrpc_test(loop):
560
client = GlibServerProxy("http://127.0.0.1:8888")
561
client.test("bar", "baz",
562
reply_handler=xmlrpc_success_cb,
563
error_handler=xmlrpc_error_cb,
567
server = GlibXMLRPCServer(("", 8888))
569
server.register_instance(inst)
570
gobject.idle_add(xmlrpc_test, loop)
572
class TestReqHandler(ChunkedGlibHTTPRequestHandler):
573
def translate_path(self, path):
577
server = GlibTCPServer(("", 8890), TestReqHandler)
580
loop = gobject.MainLoop()
585
except KeyboardInterrupt:
586
print 'Ctrl+C pressed, exiting...'
589
if __name__ == "__main__":