【发布时间】:2011-07-26 08:28:46
【问题描述】:
我需要为用户创建一种打开网页的方式,从表单中的复选框列表中进行选择,然后在提交表单后,将所有这些文件一起下载。
以下是客户对我施加的限制:
- 平台主要是移动设备
- 没有 zip 文件(因为我们不能假设移动设备可以处理 zip)
- 文件必须下载,而不是流式传输
因此,我在前端使用带有 jQuery 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 创建的
<a>标签和触发的click() - 使用不同的 DOCTYPE
以下是我尝试过的在台式机或移动设备上都不起作用的方法:
- 修改
location.href或window.location(显然只能做一次) - 在服务器上调用
req.sendfile()(也许我做错了?) - 返回多部分/表单数据并从服务器转储具有设定边界的二进制数据(非常混乱,也许我也做错了?)
仍然没有喜悦;我可能会错过什么?
附:请不要因为我使用隐藏的 iframe 而激怒我...
编辑:我什至可以使用可以在服务器上设置的另一个本机浏览器协议,例如 FTP。欢迎所有想法。
更新:我正在尝试启动从客户端到服务器的 FTP 连接并运行“mget”。我知道 net2ftp 可以做到这一点……现在来弄清楚 ;) 仍然有新的想法。
【问题讨论】:
-
由于您实际上不需要在
<iframes>中填充任何内容,您是否尝试过使用<script>标签而不是<iframe>标签?您可能还想在 URL 的末尾添加一个 nonce 参数以防止缓存问题。 -
这不起作用...它正确加载了
<script>标签,但没有下载文件。我怀疑它对待<script>标记很像典型的 JS 文件:非持久性。 -
嗯。哦,好吧,很抱歉浪费你的时间(但如果是我,我也会尝试的 :-)
-
@Pointy:别担心,不要浪费时间!我花了大约 5 秒钟的时间来尝试;)
标签: jquery ajax apache jquery-mobile mod-python