使用httplib在Python中发送HTTPConnection文件,获取进度

2 投票
1 回答
2585 浏览
提问于 2025-04-16 19:59

在一个Django应用中,我使用了一个第三方的Python 脚本,让用户可以通过httplib.HTTPConnection.send在EC2实例上上传文件到blip.tv。因为这些文件通常比较大,所以我打算使用消息队列来异步处理上传(使用RabbitMQ/Celery),并在前端给用户反馈上传进度。

httpconnection和send的操作在脚本的这一部分:

host, selector = urlparts = urlparse.urlsplit(url)[1:3]
h = httplib.HTTPConnection(host)
h.putrequest("POST", selector)
h.putheader("content-type", content_type)
h.putheader("content-length", len(data))
h.endheaders()
h.send(data)    
response = h.getresponse()
return response.status, response.reason, response.read()  

在文件传输完成后,getresponse()会返回结果,我该如何输出进度(假设用stdout.write之类的方式),以便将这个值写入缓存框架进行显示(djangosnippets 678/679)?另外,如果有更好的做法,我也很乐意听取建议!

编辑:

因为我选择了urllib2,并且参考了这个问题中的一个建议,重写了文件的read()方法来获取上传进度。此外,我还使用poster来生成multipart urlencode。以下是最新的代码:

from poster.encode import multipart_encode
from poster.streaminghttp import register_openers
def Upload(video_id, username, password, title, description, filename):

    class Progress(object):
        def __init__(self):
            self._seen = 0.0

        def update(self, total, size, name):
            self._seen += size
            pct = (self._seen / total) * 100.0
            print '%s progress: %.2f' % (name, pct)

    class file_with_callback(file):
        def __init__(self, path, mode, callback, *args):
            file.__init__(self, path, mode)
            self.seek(0, os.SEEK_END)
            self._total = self.tell()
            self.seek(0)
            self._callback = callback
            self._args = args

        def __len__(self):
            return self._total

        def read(self, size):
            data = file.read(self, size)
            self._callback(self._total, len(data), *self._args)
            return data

    progress = Progress()
    stream = file_with_callback(filename, 'rb', progress.update, filename)

    datagen, headers = multipart_encode({
                                        "post": "1",
                                        "skin": "xmlhttprequest",
                                        "userlogin": "%s" % username,
                                        "password": "%s" % password,
                                        "item_type": "file",
                                        "title": "%s" % title.encode("utf-8"),
                                        "description": "%s" % description.encode("utf-8"),                                             
                                         "file": stream
                                         })    

    opener = register_openers()

    req = urllib2.Request(UPLOAD_URL, datagen, headers)
    response = urllib2.urlopen(req)
    return response.read()

这个方法有效,但只适用于文件路径输入,而不适用于来自表单输入的InMemoryUploadedFile(request.FILES),因为它试图读取已经保存在内存中的文件,我想这就是我在这一行遇到TypeError的原因:“stream = file_with_callback(filename, 'rb', progress.update, filename)”。

coercing to Unicode: need string or buffer, InMemoryUploadedFile found

我该如何实现对用户上传文件的同样进度报告?另外,这样读取进度会消耗很多内存吗?也许对urllib2的下载进度的上传解决方案会更好,但该如何实现呢……非常欢迎任何帮助!

1 个回答

1

原来,poster这个库在multipart_encode里有一个回调钩子,可以用来获取进度(上传或下载)。这真是个好东西……

虽然我算是技术上回答了这个问题,但我相信还有其他方法可以解决这个问题。如果我找到其他方法或细节,我会再分享更多。

下面是代码:

def prog_callback(param, current, total):
    pct = 100 - ((total - current ) *100 )/ (total) 
    print "Progress: %s " % pct    


datagen, headers = multipart_encode({
                                    "post": "1",
                                    "skin": "xmlhttprequest",
                                    "userlogin": "%s" % username,
                                    "password": "%s" % password,
                                    "item_type": "file",
                                    "title": "%s" % title.encode("utf-8"),
                                    "description": "%s" % description.encode("utf-8"),                                             
                                     "file": filename
                                     }, cb=prog_callback)    

opener = register_openers()

req = urllib2.Request(UPLOAD_URL, datagen, headers)
response = urllib2.urlopen(req)
return response.read()

撰写回答