在cherrypy中下载大文件

时间:2014-10-07 02:33:07

标签: python nginx download cherrypy uwsgi

我正在使用Cherrypy托管一个文件访问类型的网站,通过Raspberry Pi上的uwsgi和nginx。我注意到的一件事是,如果文件相当大(比方说,大约一千兆字节),uwsgi说它被信号9杀死。这可以通过放cherrypy.config.update({'tools.sessions.timeout': 1000000})来解决,但这并没有真正解决问题,就像它是一个糟糕的hacky解决方法,并没有真正起作用。它主要是通过使超时非常大来引起另一个问题。此外,浏览器无法准确估计需要多长时间,并且最终会挂起一段时间(阅读:硬连线连接上大约5分钟),然后快速开始下载。

开头

The file download

然后转到

The continued file download

我的下载代码非常简单,只包含这一行。

return cherrypy.lib.static.serve_file(path,"application/x-download",os.path.basename(path))

我之前的下载代码效果不佳。

f = file(path) cherrypy.response.headers['Content-Type'] = getType(path)[0] return f 有没有办法解决这个问题?

1 个答案:

答案 0 :(得分:5)

一般考虑

首先,我必须说它是如此堆积的配置, 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视频文件。有三种方式:

  1. 来自CherryPy(http://127.0.0.1:8080/native/video.mp4),
  2. 的非代理下载
  3. 通过Nginx(http://test/native/video.mp4),
  4. 从CherryPy下载代理
  5. 通过Nginx(http://test/nginx/video.mp4)从CherryPy下载X-accel。
  6. 使用(1)和(2)我在Firefox和Chromium中都有轻微的奇怪行为。 (1)在 Firefox 上连续几天的运行时间我总是有〜5MiB / s的下载速度和一个满载的CPU核心。在新的 Firefox 上没有这样的行为。 (2)在Chromium上导致了几个未完成的中断下载(所有时间都在1GiB左右)。但总的来说,两款浏览器都显示出硬盘的物理性能为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;
      }
    
    }