2010-03-10 17 views
6

Tengo una acción de controlador Pylons que necesita devolver un archivo al cliente. (El archivo está fuera de la raíz de la web, por lo que no puede simplemente enlazar directamente a ella.) La forma más sencilla es, por supuesto, esto:Transmitir un archivo a la respuesta HTTP en Pylons

with open(filepath, 'rb') as f: 
     response.write(f.read()) 

que funciona, pero es obviamente ineficiente para archivos de gran tamaño. ¿Cuál es la mejor manera de hacer esto? No he podido encontrar ningún método conveniente en Pylons para transmitir el contenido del archivo. ¿Realmente tengo que escribir el código para leer un fragmento a la vez desde cero?

+2

Para los archivos que sirven, asegúrese de que las abra en el modo 'rb', por lo que no obtiene resultados destrozados que se ejecutan en un servidor Windows. – bobince

+0

Buen punto, bobince: lo cambié a 'rb' – EMP

Respuesta

5

finalmente conseguí que funcione utilizando la clase FileApp, gracias a Chris AtLee y THC4k (de this answer). Este método también me permitió configurar el encabezado Content-Length, something Pylons has a lot of trouble with, que permite que el navegador muestre una estimación del tiempo restante.

Aquí está el código completo:

def _send_file_response(self, filepath): 
    user_filename = '_'.join(filepath.split('/')[-2:]) 
    file_size = os.path.getsize(filepath) 

    headers = [('Content-Disposition', 'attachment; filename=\"' + user_filename + '\"'), 
       ('Content-Type', 'text/plain'), 
       ('Content-Length', str(file_size))] 

    from paste.fileapp import FileApp 
    fapp = FileApp(filepath, headers=headers) 

    return fapp(request.environ, self.start_response) 
7

La herramienta correcta para usar es shutil.copyfileobj, que copia de uno a otro un fragmento a la vez.

Ejemplo de uso:

import shutil 
with open(filepath, 'r') as f: 
    shutil.copyfileobj(f, response) 

esto no va a resultar en el uso de memoria muy grande, y no requiere de la implementación del código mismo.

Se debe tener cuidado habitual con excepciones: si maneja señales (como SIGCHLD) debe manejar EINTR porque las escrituras en respuesta podrían interrumpirse, y IOError/OSError puede ocurrir por varias razones al hacer E/S .

+0

Eso es exactamente lo que estaba buscando, ¡gracias! – EMP

+0

Bueno, PARECÍA que funcionaba, pero lo intenté con un archivo de 2GB recientemente y descubrí que aún me llevó mucho tiempo devolver algo y el uso de memoria del proceso fue de 2.5GB. Parece que la respuesta de Pylons aún almacena el archivo completo. – EMP

1

La clave aquí es que WSGI, y los pilones por extensión, funcionan con respuestas iterables. Por lo que debe ser capaz de escribir algo de código como (advertencia, código no probado abajo!):

def file_streamer(): 
    with open(filepath, 'rb') as f: 
     while True: 
      block = f.read(4096) 
      if not block: 
       break 
      yield block 
response.app_iter = file_streamer() 

Además, paste.fileapp.FileApp está diseñado para ser capaz de devolver los datos de archivo para usted, por lo que también puede probar:

return FileApp(filepath) 

en su método de controlador.

+0

Disculpa, esto no ayuda. El método 'file_streamer' devuelve los datos, pero aún así se almacena en el búfer. Cuando trato de devolver 'FileApp (filepath)' Obtengo "TypeError: 'FileApp' el objeto no es iterable" – EMP

+0

Ah, parece que solo necesita un poco más de código que eso, pero esencialmente 'FileApp' hace lo que quiero. Publicaré la respuesta completa por separado. ¡Gracias! +1 – EMP

+0

return forward (FileApp (filepath)) –

Cuestiones relacionadas