【发布时间】:2018-05-10 08:53:54
【问题描述】:
我正在尝试从一个扭曲的应用程序中从 Internet 下载文件。我想使用请求来执行此操作,因为它直接提供或具有维护良好的库来提供(重试、代理、缓存控制等)。我对没有这些功能的唯一扭曲解决方案持开放态度,但无论如何我似乎都找不到。
文件应该相当大,并且会在慢速连接时下载。因此,我使用请求的stream=True 接口和响应的 iter_content。本问题末尾列出了或多或少完整的代码片段。其入口点是http_download 函数,调用时使用url、dst 将文件写入,callback 和可选errback 用于处理失败的下载。我已经删除了一些与准备目的地有关的代码(创建文件夹等)和在反应器退出期间关闭会话的代码,但我认为它仍然应该按原样工作。
此代码有效。文件下载完毕,双绞反应器继续运行。但是,我似乎对这段代码有疑问:
def _stream_download(r, f):
for chunk in r.iter_content(chunk_size=128):
f.write(chunk)
yield None
cooperative_dl = cooperate(_stream_download(response, filehandle))
因为iter_content 仅在它有一个块要返回时才返回,所以反应器处理一个块,运行其他代码,然后返回等待下一个块,而不是让自己忙于更新 GUI 上的旋转等待动画(代码实际上并未在此处发布)。
问题来了——
- 有没有一种方法可以扭曲这个生成器,以便在生成器本身不准备让出某些东西时让出控制权?我遇到了一些
twisted.flow的文档,这似乎很合适,但这似乎并没有使它变得扭曲或今天不再存在。这个问题可以独立于细节来阅读,即,关于任何任意阻塞生成器,或者可以在问题的直接上下文中阅读。 - 有没有办法扭曲以使用请求等功能齐全的东西异步下载文件?是否有一个现有的扭曲模块可以做到这一点,我可以使用吗?
- 解决这种扭曲问题的基本方法是什么,与我想从请求中使用的 http 功能无关。假设我准备放弃它们或以其他方式实施它们。如何通过 HTTP 异步下载文件。
import os
import re
from functools import partial
from six.moves.urllib.parse import urlparse
from requests import HTTPError
from twisted.internet.task import cooperate
from txrequests import Session
class HttpClientMixin(object):
def __init__(self, *args, **kwargs):
self._http_session = None
def http_download(self, url, dst, callback, errback=None, **kwargs):
dst = os.path.abspath(dst)
# Log request
deferred_response = self.http_session.get(url, stream=True, **kwargs)
deferred_response.addCallback(self._http_check_response)
deferred_response.addCallbacks(
partial(self._http_download, destination=dst, callback=callback),
partial(self._http_error_handler, url=url, errback=errback)
)
def _http_download(self, response, destination=None, callback=None):
def _stream_download(r, f):
for chunk in r.iter_content(chunk_size=128):
f.write(chunk)
yield None
def _rollback(r, f, d):
if r:
r.close()
if f:
f.close()
if os.path.exists(d):
os.remove(d)
filehandle = open(destination, 'wb')
cooperative_dl = cooperate(_stream_download(response, filehandle))
cooperative_dl.whenDone().addCallback(lambda _: response.close)
cooperative_dl.whenDone().addCallback(lambda _: filehandle.close)
cooperative_dl.whenDone().addCallback(
partial(callback, url=response.url, destination=destination)
)
cooperative_dl.whenDone().addErrback(
partial(_rollback, r=response, f=filehandle, d=destination)
)
def _http_error_handler(self, failure, url=None, errback=None):
failure.trap(HTTPError)
# Log error message
if errback:
errback(failure)
@staticmethod
def _http_check_response(response):
response.raise_for_status()
return response
@property
def http_session(self):
if not self._http_session:
# Log session start
self._http_session = Session()
return self._http_session
【问题讨论】:
标签: python asynchronous python-requests twisted