一般注意事项
首先,我不得不说这是一个堆积如山的配置,CherryPy -> uWSGI -> Nginx,对于这样一个约束环境。 According to the author,在没有特殊要求的情况下,单独使用 CherryPy 用于小型应用程序是安全的。在前面添加 Nginx 会增加很多灵活性,所以通常是有好处的,但只要 CherryPy 的默认部署是标准 HTTP,我强烈建议保留这两者(完全忘记 WSGI)。
其次,考虑到您尝试过的解决方法,您可能已经知道您的问题可能与会话有关。这是来自文档的the quote,关于流响应正文是哪个文件下载。
一般来说,不流式输出更安全、更容易。因此,流输出是
默认关闭。流式输出和使用会话需要很好的理解
how session locks work.
它建议手动会话锁定管理。了解您的应用程序的工作原理应该会引导您进行适当的锁设计。
第三个。通常有一种方法可以将处理文件下载的职责转移到 Web 服务器,基本上是通过从代理应用程序发送带有文件名的适当标头。如果在 nginx 上,它被称为X-accel。这样您就可以避免锁管理的麻烦,仍然有会话限制下载。
实验
我制作了一个简单的 CherrPy 应用程序,它有两个下载选项,并将其放在 Nginx 后面。我在 Firefox 和 Chromium 的本地 Linux 机器上播放了 1.3GiB 的视频文件。有三种方式:
- 从 CherryPy (
http://127.0.0.1:8080/native/video.mp4) 未经代理下载,
- 通过 Nginx (
http://test/native/video.mp4) 从 CherryPy 代理下载,
- 通过 Nginx (
http://test/nginx/video.mp4) 从 CherryPy 下载 X-accel。
使用 (1) 和 (2) 我在 Firefox 和 Chromium 中都有轻微的奇怪行为。 (1) 在 Firefox 上,正常运行时间为几天,我的下载速度始终保持在 ~5MiB/s 和一个满载的 CPU 内核。在新的 Firefox 上没有这样的行为。 (2) 在 Chromium 上导致了几个未完成的中断下载(一直在 1GiB 左右)。但总的来说,两种浏览器的 HDD 物理性能都在 50-70MiB/s 左右。
使用 (3) 时,两者都没有问题,同样的 50-70MiB/s 吞吐量,所以在我的小实验中不知何故,它最终成为最稳定的方式。
设置
app.py
#!/usr/bin/env python
# -*- coding: utf-8 -*-
import os
import cherrypy
DownloadPath = '/home/user/Videos'
config = {
'global' : {
'server.socket_host' : '127.0.0.1',
'server.socket_port' : 8080,
'server.thread_pool' : 8
}
}
class App:
@cherrypy.expose
def index(self):
return 'Download test'
@cherrypy.expose
def native(self, name):
basename = os.path.basename(name)
filename = os.path.join(DownloadPath, basename)
mime = 'application/octet-stream'
return cherrypy.lib.static.serve_file(filename, mime, basename)
@cherrypy.expose
def nginx(self, name):
basename = os.path.basename(name)
cherrypy.response.headers.update({
'X-Accel-Redirect' : '/download/{0}'.format(basename),
'Content-Disposition' : 'attachment; filename={0}'.format(basename),
'Content-Type' : 'application/octet-stream'
})
if __name__ == '__main__':
cherrypy.quickstart(App(), '/', config)
app.conf
server {
listen 80;
server_name test;
root /var/www/test/public;
location /resource {
# static files like images, css, js, etc.
access_log off;
}
location / {
proxy_pass http://127.0.0.1:8080;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
location /download {
internal;
alias /home/user/Videos;
}
}