【问题标题】:How to forward HTTP range requests using Python and Flask?如何使用 Python 和 Flask 转发 HTTP 范围请求?
【发布时间】:2020-10-14 13:42:54
【问题描述】:

我有一个 Flask 应用程序,它应该提供一个端点来下载一个大文件。但是,必须先通过 HTTP 从另一台服务器下载此文件,而不是从文件系统提供或即时生成文件。

当然,我可以先向外部服务器执行 GET 请求,完全下载文件并将其存储在文件系统或内存中,然后作为第二步提供原始请求的结果。例如,这看起来像这样(还包括基本身份验证,以表明为什么较低层上的简单代理是不够的):

#!flask/bin/python
from flask import Flask, jsonify
import os
import requests
from requests.auth import HTTPBasicAuth

app = Flask(__name__)

@app.route('/download')
def download():
    auth = HTTPBasicAuth("some_user", "some_password")
    session = requests.Session()
    session.auth = auth
    response = session.get("http://example.com")
    return response.content

if __name__ == '__main__':
    app.run(host='0.0.0.0', port=1234, debug=True)

但是,这会增加应用程序的延迟和存储需求。而且,即使接收方只需要执行文件的部分下载(即执行 HTTP 范围请求),也必须首先从外部服务器完全获取。

有没有更优雅的选择来解决这个问题,即支持直接转发到外部服务器的 HTTP 范围请求?

【问题讨论】:

  • 你看过StringIO吗?
  • @koalo PATCH 可用于恢复带有内容范围标头的上传。
  • @IMCoins 是的,我知道 StringIO,但我承认我不知道如何将这些部分组合在一起。我可以在 Flask 中只返回一个 StringIO 对象,然后它会自动支持部分下载吗?
  • @ShakaFlex PATCH 不是用于部分修改(客户端到服务器)而不是部分下载(服务器到客户端)吗?
  • @koalo 另一种可能更容易实现的方式是将中间服务器转换为 HTTP 代理。让客户端浏览器和端服务器完成剩下的工作。中介的唯一资源成本主要是网络。

标签: python http flask


【解决方案1】:

根据Proxying to another web service with FlaskDownload large file in python with requestsFlask large file download,我设法在流模式下制作了一个Flask HTTP代理。

from flask import Flask, request, Response
import requests

PROXY_URL = 'http://ipv4.download.thinkbroadband.com/'

def download_file(streamable):
    with streamable as stream:
        stream.raise_for_status()
        for chunk in stream.iter_content(chunk_size=8192):
            yield chunk


def _proxy(*args, **kwargs):
    resp = requests.request(
        method=request.method,
        url=request.url.replace(request.host_url, PROXY_URL),
        headers={key: value for (key, value) in request.headers if key != 'Host'},
        data=request.get_data(),
        cookies=request.cookies,
        allow_redirects=False,
        stream=True)

    excluded_headers = ['content-encoding', 'content-length', 'transfer-encoding', 'connection']
    headers = [(name, value) for (name, value) in resp.raw.headers.items()
               if name.lower() not in excluded_headers]

    return Response(download_file(resp), resp.status_code, headers)


app = Flask(__name__)

@app.route('/', defaults={'path': ''})
@app.route('/<path:path>')
def download(path):
    return _proxy()

if __name__ == '__main__':
    app.run(host='0.0.0.0', port=1234, debug=True)

download_file() 将以流模式打开请求,并在流式传输后立即生成每个块。

_proxy() 创建请求,然后使用迭代器 download_file() 作为内容创建并返回 Flask Response

我使用https://www.thinkbroadband.com/download 对其进行了测试,其中几个存档文件可以免费下载用于测试目的。 (请注意,存档已损坏,因此您最好使用校验和来确保获得预期的文件)。

一些例子:

curl 'http://0.0.0.0:1234/100MB.zip' --output /tmp/100MB.zip
curl 'http://0.0.0.0:1234/20MB.zip' --output /tmp/20MB.zip

我还在随机网站上进行了一些其他测试以获取大图像。到目前为止,我没有遇到任何问题。

【讨论】:

  • 这已经解决了范围请求的问题,但是仍然需要执行整个请求才能返回响应。
  • @koalo 抱歉,我已经修复了我的答案,现在应该会更好
猜你喜欢
  • 2014-11-21
  • 1970-01-01
  • 1970-01-01
  • 2015-02-02
  • 2016-08-03
  • 1970-01-01
  • 2015-02-04
  • 1970-01-01
  • 2015-03-13
相关资源
最近更新 更多