在单个请求中下载多个文件 (jQuery/Python)

Posted

技术标签:

【中文标题】在单个请求中下载多个文件 (jQuery/Python)【英文标题】:Download Multiple Files in a Single Request (jQuery/Python) 【发布时间】:2011-07-26 08:28:46 【问题描述】:

我需要为用户创建一种打开网页的方式,从表单中的复选框列表中进行选择,然后在提交表单后,将所有这些文件一起下载。

以下是客户对我施加的限制:

平台主要是移动设备 没有 zip 文件(因为我们不能假设移动设备可以处理 zip) 文件必须下载,而不是流式传输

因此,我在前端使用带有 jQ​​uery Mobile 1.0a3 的 Xhtml/CSS 和在后端使用带有 Python 2.6 的 Apache 创建了一个 Web 应用程序。将下载的目标文件是 .mp3 文件。

我已经成功地使用从服务器传递并由 jQuery 通过 AJAX 加载的隐藏 iframe 在桌面上执行了所需的效果......但它在默认的 android 浏览器或 Dolphin 浏览器上不起作用。

我确保我的 Apache 配置会强制执行下载行为:

<Files *.mp3>
    ForceType application/octet-stream
    Header set Content-Disposition attachment
</Files>

此外,Apache 的“headers”模块已启用(需要“Header set”配置参数),所以这不是问题。

我使用所有选定的项目作为参数对服务器进行 AJAX 调用,当服务器读取项目数组时,它将查询数据库以获取有关每个项目的信息(例如每个 mp3 文件的 URL) .然后在后端为每个 mp3 文件创建 iframe 代码,然后发送回 jQuery 的$.load() 函数以加载新的 iframe(下载 mp3)。

不用粘贴太多代码,这是我正在做的一个非常简短的测试用例:

服务器端

def download(req):
    resultDiv = """<div id="downloads">"""
    queryIds = []
    for element in req.form:
        # "element" contains the id number that matches database record
        trackId = re.match('^track(\d+)', element).group(1)
        queryIds.append(trackId)

    conn = mysqldb.connect(host='localhost', user='fake', passwd='fake', db='fake')
    cursor = conn.cursor()
    
    buildQuery = """\
        SELECT filePath FROM tracks
        WHERE trackNum in ("""

    buildQuery += ','.join(queryIds)
    buildQuery += ')'

    cursor.execute(buildQuery)
    downloadRows = cursor.fetchall()

    for track in downloadRows:
        resultDiv += """
            <iframe src="%s"></iframe>
        """ % track[0]

    return resultDiv

客户端

<!DOCTYPE html>
<html>
    <head>
        <!-- INCLUDES FOR JQUERY MOBILE AND JQUERY -->
        <style type="text/css">
            .invisible 
                display: none;
            
        </style>
        <script type="text/javascript">
            $(document).ready(function() 
                $('#albumForm').submit(function(e) 
                    e.preventDefault();

                    // this will hold the selected items on the form
                    selTracks = ;

                    $('#trackList').find(':checked').each(function() 
                        selTracks[this.id] = 'on';
                    );

                    // load the iframes into a 'div' set aside for that purpose
                    $('#results').load('control.py/download #tracks', selTracks);
                );
            );
        </script>
    </head>
    <body>
        <div data-role='page' id='page'>
            <div data-role='header' id='header'>
            </div>
            <div data-role='content' id='content'>
                <div id='container'>
                    <form id='albumForm'>
                        <div data-role='controlgroup' data-role='fieldcontain'>
                            <input type='checkbox' name='track1' id='track1' />
                            <label for='track1' id='track1label'>Track 1</label>
                            <input type='checkbox' name='track2' id='track2' />
                            <label for='track2' id='track2label'>Track 2</label>
                            <input type='checkbox' name='track3' id='track3' />
                            <label for='track3' id='track3label'>Track 3</label>
                            
                            <input type='submit' id='downloadButton' value='Download' />
                        </div>
                    </form>
                </div>
                <div id='results' class='invisible'>
                </div>
            </div>
            <div data-role='footer' id='footer'>
            </div>
        </div>
    </body>
</html>

抱歉,代码太通用了(而且大大缩短了),但我无权发布实际代码(你知道它是怎么回事)。但这基本上是它的要点;我相信问题出在移动浏览器的解释中,或者可能在某处的 HTTP 标头中?这在桌面上的 Chrome 和 Firefox 中工作,它实际上在 Fennec for Android 中完全按照预期工作(它下载所有文件而无需进一步交互,只是在通知栏中显示它们)。我只是不能假设每个人都在使用 Fennec(他们不是,哈哈)。

除了上述之外,我还尝试了以下方法(都可以在台式机上运行,​​但不能在移动设备上运行):

从服务器返回的 JSON,以及 jQuery 在客户端创建的 iframe 从服务器返回的 JSON,以及为每个 URL 调用 window.open() 的 for 循环 从服务器返回的 JSON,以及由 jQuery 创建的 &lt;a&gt; 标签和触发的 click() 使用不同的 DOCTYPE

以下是我尝试过的在台式机或移动设备上都不起作用的方法:

修改location.hrefwindow.location(显然只能做一次) 在服务器上调用req.sendfile()(也许我做错了?) 返回多部分/表单数据并从服务器转储具有设定边界的二进制数据(非常混乱,也许我也做错了?)

仍然没有喜悦;我可能会错过什么?

附:请不要因为我使用隐藏的 iframe 而激怒我...

编辑:我什至可以使用可以在服务器上设置的另一个本机浏览器协议,例如 FTP。欢迎所有想法。

更新:我正在尝试启动从客户端到服务器的 FTP 连接并运行“mget”。我知道 net2ftp 可以做到这一点……现在来弄清楚 ;) 仍然有新的想法。

【问题讨论】:

由于您实际上不需要在&lt;iframes&gt; 中填充任何内容,您是否尝试过使用&lt;script&gt; 标签而不是&lt;iframe&gt; 标签?您可能还想在 URL 的末尾添加一个 nonce 参数以防止缓存问题。 这不起作用...它正确加载了&lt;script&gt;标签,但没有下载文件。我怀疑它对待 &lt;script&gt; 标记很像典型的 JS 文件:非持久性。 嗯。哦,好吧,很抱歉浪费你的时间(但如果是我,我也会尝试的 :-) @Pointy:别担心,不要浪费时间!我花了大约 5 秒钟的时间来尝试;) 【参考方案1】:

这是一个客户端限制。每个域有一个并行下载限制。尝试提供来自不同域的文件,例如 d1.example.com 和 d2.example.com。它们都可以由同一个虚拟主机提供服务:

<VirtualHost *:80>
    ServerName example.com
    ServerAlias d1.example.com d2.example.com
    ...
</VirtualHost>

【讨论】:

这是专门针对 Android 浏览器的客户端限制吗?因为它可以在桌面上正常运行,也可以在 Android (Fennec) 上运行 Firefox for Mobile。它只是无法在默认的 Android 浏览器或 Dolphin 浏览器上运行。 @JoeLinux 每个浏览器都有自己的限制。 好的,我添加了从不同但顺序的子域下载每个曲目的代码......仍然无法在移动浏览器上运行。它继续在桌面上工作。

以上是关于在单个请求中下载多个文件 (jQuery/Python)的主要内容,如果未能解决你的问题,请参考以下文章

我们可以在 Android Kotlin 的单个协程中下载多个文件吗

分享一个FileUtil工具类,基本满足web开发中的文件上传,单个文件下载,多个文件下载的需求

在 Swift 上发出多个网络请求

Apache驼峰单个文件的多个动态路由

在 .net Core 中使用 HttpClient 下载分块编码文件

iOS,Swift:串行下载多个文件并将所有文件的单个进度条显示为一个进度