Hi Christoph,
Christoph Haas wrote:
> The files cream_0.39.orig.tar.gz and cream_0.39-1.diff.gz are uploaded
> without any delay in between. In other cases the server does not see any
> communication for ~15 seconds.
> It appears that dput always tries to upload a file without sending the
> credentials in the HTTP "Authorization" request header even though the
> user has entered the password already. dput bumps into the 401
> (unauthorized) error time and again for every file and tries again with
> the credentials. Perhaps this bug will fade away if the "Authorization"
> header is sent automatically if the password is known already. Seems
> like dput doesn't cope well with 401 responses and times out somewhere
> in the depths of the urllib2. :)
Indeed the problem seems to be uploading the file twice which leads to disaster
with large files.
I guess fixing this means thoroughly rewriting the http.py module. OTOH, this
bug and the general problem of having reading all of the file into memory before
sending is grave enough to warrant a timely fix. I have attached a preliminary
version of an improved (by ripping out most urllib2 stuff) http.py. Does that
work better for you?
I think it should work with current dput, even though I have modified my own
copy to get progress output for http, otherwise I'll put up an experimental dput
package later this year.
Kind regards
T.
--
Thomas Viehmann, http://thomas.viehmann.net/
import os, sys, httplib, urllib2, urlparse, getpass, dputhelper
# note: requires >= python 2.4 httplib
# Custom password manager that prompts for a password using getpass() if
# required, and mangles the saved URL so that only one password is prompted for.
class PromptingPasswordMgr(urllib2.HTTPPasswordMgr):
def __init__(self, username):
urllib2.HTTPPasswordMgr.__init__(self)
self.username = username
def find_user_password(self, realm, authuri):
# Hack so that we only prompt for a password once
authuri = self.reduce_uri(authuri)[0]
authinfo = urllib2.HTTPPasswordMgr.find_user_password(self, realm, authuri)
if authinfo != (None, None):
return authinfo
password = getpass.getpass(" Password for %s:" % realm)
self.add_password(realm, authuri, self.username, password)
return (self.username, password)
class AuthHandlerHackAround:
# fake request and parent object...
def __init__(self, url, resp_headers, pwman):
# fake request header dict
self.headers = {}
# data
self.url = url
self.resp_headers = resp_headers
self.authhandlers = []
# digest untested
for authhandler_class in [urllib2.HTTPBasicAuthHandler, urllib2.HTTPDigestAuthHandler]:
ah = authhandler_class(pwman)
ah.add_parent(self)
self.authhandlers.append(ah)
# fake request methods
def add_header(self, k, v):
self.headers[k] = v
def get_full_url(self):
return self.url
# fake parent method
def open(self,*args):
pass
# and what we really want
def get_auth_headers(self):
for ah in self.authhandlers:
try:
ah.http_error_401(self, None, 401, None, self.resp_headers)
except ValueError, e:
pass
if self.headers:
return self.headers
return self.headers
# Upload the files via WebDAV
def upload(fqdn, login, incoming, files_to_upload, debug, dummy, progress=0, protocol="http"):
# EXCEPTION HANDLING!
if protocol == 'https':
connclass = httplib.HTTPSConnection
elif protocol == 'http':
connclass = httplib.HTTPConnection
else:
print >> sys.stderr, "Wrong protocol for upload http[s].py method"
sys.exit(1)
if not incoming.startswith('/'):
incoming = '/'+incoming
if not incoming.endswith('/'):
incoming += '/'
unprocessed_files_to_upload = files_to_upload[:]
auth_headers = {}
pwman = PromptingPasswordMgr(login)
while unprocessed_files_to_upload:
afile = unprocessed_files_to_upload[0]
path_to_package, package_name = os.path.split(afile)
# print without linefeed
sys.stdout.write(" %s: "% package_name)
sys.stdout.flush()
try:
size = os.stat(afile).st_size
except:
print >> sys.stderr, "Determining size of file '%s' failed"%afile
sys.exit(1)
f = open(afile,'r')
if progress:
f = dputhelper.FileWithProgress(f, ptype=progress,
progressf=sys.stderr,
size=size)
url_path = incoming+package_name
url = "%s://%s%s" % (protocol,fqdn,url_path)
if debug:
print "D: HTTP-PUT to URL: %s"%url
conn = connclass(fqdn)
conn.putrequest("PUT", url_path, skip_accept_encoding=True)
# Host: should be automatic
conn.putheader('User-Agent','dput')
for k, v in auth_headers.items():
conn.putheader(k, v)
conn.putheader('Connection','close')
conn.putheader('Content-Length',str(size))
conn.endheaders()
pos = 0
while pos < size:
s = f.read(65536) # sending in 64k steps (screws progress a bit)
conn.send(s)
pos += len(s)
s = ""
res = conn.getresponse()
if res.status >= 200 and res.status < 300:
print "done."
del unprocessed_files_to_upload[0]
elif res.status == 401 and not auth_headers:
print "need authentication."
auth_headers = AuthHandlerHackAround(url, res.msg, pwman).get_auth_headers()
else:
if res.status == 401:
print "Upload failed as unauthorized: %s"%res.msg
print " Maybe wrong username or password?"
else:
print "Upload failed: %d %d"%res.status, res.msg
sys.exit(1)
res.read() # must be done, but we're not interested