2010-03-10 4 views
6

Ho un'azione del controller Pylon che deve restituire un file al client. (Il file è al di fuori della web root, quindi non si può semplicemente collegarsi direttamente ad esso.) Il modo più semplice è, naturalmente, questo:Streaming di un file alla risposta HTTP in piloni

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

che funziona, ma è evidentemente inefficiente per file di grandi dimensioni. Qual'è il miglior modo per farlo? Non sono stato in grado di trovare alcun metodo conveniente in Pylon per trasmettere il contenuto del file. Devo davvero scrivere il codice per leggere un chunk alla volta da zero?

+2

Per i file che servono, assicuratevi di aprirli in 'modalità rb', in modo da non si ottengono risultati straziati in esecuzione su un server Windows. – bobince

+0

Buon punto, bobince - cambiato in 'rb' – EMP

risposta

5

finalmente ho potuto farlo funzionare utilizzando la classe FileApp, grazie alla Chris Atlee e THC4k (da this answer). Questo metodo mi ha anche permesso di impostare l'intestazione Content-Length, something Pylons has a lot of trouble with, che consente al browser di mostrare una stima del tempo rimanente.

Ecco il codice 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

Lo strumento corretto da utilizzare è shutil.copyfileobj, che copia da uno all'altro un blocco alla volta.

Esempio di utilizzo:

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

Ciò non si tradurrà in molto grande utilizzo di memoria, e non richiede l'attuazione del codice da soli.

dovrebbe essere presa la dovuta cura e attenzione con le eccezioni - se si gestisce segnali (come ad esempio SIGCHLD) si deve gestire EINTR perché le scritture di risposta potrebbero essere interrotti, e IOError/OSError può avvenire per vari motivi nel fare di I/O .

+0

Questo è esattamente quello che stavo cercando - grazie! – EMP

+0

Beh, SEMBRA che funzionasse, ma l'ho provato recentemente con un file da 2 GB e ho scoperto che ci voleva ancora molto tempo per restituire qualsiasi cosa e l'utilizzo della memoria del processo era di 2.5 GB. Quindi sembra che la risposta Pylons memorizzi ancora l'intero file. – EMP

1

La chiave qui è che WSGI e piloni per estensione funzionano con risposte iterabili. Così si dovrebbe essere in grado di scrivere del codice, come (avvertimento, codice non testato qui sotto!):

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() 

Inoltre, paste.fileapp.FileApp è stato progettato per essere in grado di restituire i dati di file per voi, in modo da poter provare anche:

return FileApp(filepath) 

nel metodo del controller.

+0

Siamo spiacenti, questo non aiuta. Il metodo 'file_streamer' restituisce i dati, ma tutto viene ancora bufferizzato. Quando provo a restituire 'FileApp (filepath)' Ottengo "TypeError: 'FileApp' oggetto non iterable" – EMP

+0

Ah, sembra che abbia solo bisogno di un po 'più di codice, ma essenzialmente FileApp fa quello che voglio. Pubblicherò la risposta completa separatamente. Grazie! +1 – EMP

+0

return forward (FileApp (filepath)) –