Chunked glib-integrated HTTP server and url downloader classes

This commit is contained in:
Dan Williams 2007-05-02 22:23:01 -04:00
parent 587bc3be19
commit 3f480c1495

View File

@ -18,14 +18,18 @@
# pylint: disable-msg = W0221
import socket
import os
import threading
import traceback
import xmlrpclib
import sys
import httplib
import urllib
import fcntl
import gobject
import SimpleXMLRPCServer
import SimpleHTTPServer
import SocketServer
@ -68,6 +72,174 @@ class GlibTCPServer(SocketServer.TCPServer):
self.handle_request()
return True
class ChunkedGlibHTTPRequestHandler(SimpleHTTPServer.SimpleHTTPRequestHandler):
"""RequestHandler class that integrates with Glib mainloop. It writes
the specified file to the client in chunks, returning control to the
mainloop between chunks.
"""
CHUNK_SIZE = 4096
def __init__(self, request, client_address, server):
self._file = None
self._srcid = 0
SimpleHTTPServer.SimpleHTTPRequestHandler.__init__(self, request, client_address, server)
def log_request(self, code='-', size='-'):
pass
def do_GET(self):
"""Serve a GET request."""
self._file = self.send_head()
if self._file:
self._srcid = gobject.io_add_watch(self.wfile, gobject.IO_OUT | gobject.IO_ERR, self._send_next_chunk)
else:
f.close()
self._cleanup()
def _send_next_chunk(self, source, condition):
if condition & gobject.IO_ERR:
self._cleanup()
return False
if not (condition & gobject.IO_OUT):
self._cleanup()
return False
data = self._file.read(self.CHUNK_SIZE)
count = os.write(self.wfile.fileno(), data)
if count != len(data) or len(data) != self.CHUNK_SIZE:
self._cleanup()
return False
return True
def _cleanup(self):
if self._file:
self._file.close()
if self._srcid > 0:
gobject.source_remove(self._srcid)
self._srcid = 0
if not self.wfile.closed:
self.wfile.flush()
self.wfile.close()
self.rfile.close()
def finish(self):
"""Close the sockets when we're done, not before"""
pass
def send_head(self):
"""Common code for GET and HEAD commands.
This sends the response code and MIME headers.
Return value is either a file object (which has to be copied
to the outputfile by the caller unless the command was HEAD,
and must be closed by the caller under all circumstances), or
None, in which case the caller has nothing further to do.
** [dcbw] modified to send Content-disposition filename too
"""
path = self.translate_path(self.path)
f = None
if os.path.isdir(path):
for index in "index.html", "index.htm":
index = os.path.join(path, index)
if os.path.exists(index):
path = index
break
else:
return self.list_directory(path)
ctype = self.guess_type(path)
try:
# Always read in binary mode. Opening files in text mode may cause
# newline translations, making the actual size of the content
# transmitted *less* than the content-length!
f = open(path, 'rb')
except IOError:
self.send_error(404, "File not found")
return None
self.send_response(200)
self.send_header("Content-type", ctype)
self.send_header("Content-Length", str(os.fstat(f.fileno())[6]))
self.send_header("Content-Disposition", 'attachment; filename="%s"' % os.path.basename(path))
self.end_headers()
return f
class GlibURLDownloader(gobject.GObject):
"""Grabs a URL in chunks, returning to the mainloop after each chunk"""
__gsignals__ = {
'finished': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE,
([gobject.TYPE_PYOBJECT]))
}
CHUNK_SIZE = 4096
def __init__(self, url, destdir=None):
self._url = url
if not destdir:
destdir = "/tmp"
self._destdir = destdir
self._srcid = 0
self._fname = None
self._outf = None
gobject.GObject.__init__(self)
def start(self):
self._info = urllib.urlopen(self._url)
self._fname = _get_filename_from_headers(info.headers)
if not self._fname:
import tempfile
garbage, path = urllib.splittype(url)
garbage, path = urllib.splithost(path or "")
path, garbage = urllib.splitquery(path or "")
path, garbage = urllib.splitattr(path or "")
suffix = os.path.splitext(path)[1]
(outf, self._fname) = tempfile.mkstemp(suffix=suffix, dir=self._destdir)
else:
outf = open(os.path.join(self._destdir, self._fname), "w")
fcntl.fcntl(self._info.fp.fileno(), fcntl.F_SETFD, os.O_NDELAY)
self._srcid = gobject.io_add_watch(self._info.fp.fileno(), gobject.IO_IN, self._read_next_chunk)
def _get_filename_from_headers(self, headers):
if not headers.has_key("Content-Disposition"):
return None
ftag = "filename="
data = headers["Content-Disposition"]
fidx = data.find(ftag)
if fidx < 0:
return None
fname = data[fidx+len(ftag):]
if fname[0] == '"' or fname[0] == "'":
fname = fname[1:]
if fname[len(fname)-1] == '"' or fname[len(fname)-1] == "'":
fname = fname[:len(fname)-1]
return fname
def _read_next_chunk(self, source, condition):
if not (condition & gobject.IO_IN):
self.cleanup()
self.emit("finished", None)
return False
data = info.fp.read(self.CHUNK_SIZE)
count = outf.write(data)
if len(data) < self.CHUNK_SIZE:
self.cleanup()
self.emit("finished", self._fname)
return False
return True
def cleanup(self):
if self._srcid > 0:
gobject.source_remove(self._srcid)
self._srcid = 0
del self._info
self._outf.close()
class GlibXMLRPCRequestHandler(SimpleXMLRPCServer.SimpleXMLRPCRequestHandler):
""" GlibXMLRPCRequestHandler