2011-07-27 21 views
8

Así que he estado jugando con raw WSGI, cgi.FieldStorage y cargas de archivos. Y simplemente no puedo entender cómo se trata con las cargas de archivos.¿Cómo almacena cgi.FieldStorage los archivos?

Al principio parecía que almacena todo el archivo en la memoria. Y pensé, hm, eso debería ser fácil de probar: ¡un archivo grande debería obstruir la memoria! ... Y no fue así. Aún así, cuando solicito el archivo, es una cadena, no un iterador, objeto de archivo ni nada.

He intentado leer la fuente del módulo cgi y he encontrado algunas cosas sobre los archivos temporales, pero devuelve una cadena maldita, no un objeto de archivo (-like). Entonces ... ¿cómo funciona el trabajo?

Aquí está el código que he utilizado:

import cgi 
from wsgiref.simple_server import make_server 

def app(environ,start_response): 
    start_response('200 OK',[('Content-Type','text/html')]) 
    output = """ 
    <form action="" method="post" enctype="multipart/form-data"> 
    <input type="file" name="failas" /> 
    <input type="submit" value="Varom" /> 
    </form> 
    """ 
    fs = cgi.FieldStorage(fp=environ['wsgi.input'],environ=environ) 
    f = fs.getfirst('failas') 
    print type(f) 
    return output 


if __name__ == '__main__' : 
    httpd = make_server('',8000,app) 
    print 'Serving' 
    httpd.serve_forever() 

Gracias de antemano! :)

Respuesta

6

Inspeccionando el cgi module description, hay un párrafo que explica cómo manejar las cargas de archivos.

Si un campo representa un archivo subido, acceder al valor medio del atributo de valor o el método getvalue() lee todo el archivo en la memoria como una cadena. Esto puede no ser lo que quieres. Puede probar un archivo cargado al probar el atributo de nombre de archivo o el , atributo. A continuación, puede leer los datos en el ocio desde el atributo de archivo:

fileitem = form["userfile"] 
if fileitem.file: 
    # It's an uploaded file; count lines 
    linecount = 0 
    while 1: 
     line = fileitem.file.readline() 
     if not line: break 
     linecount = linecount + 1 

En cuanto a su ejemplo, getfirst() es sólo una versión de getvalue(). intentar sustituir

f = fs.getfirst('failas') 

con

f = fs['failas'].file 

Esto devolverá un objeto de tipo fichero que se puede leer "en el ocio".

+0

Gracias :) Estoy usando principalmente Django pero a veces me gusta jugar un poco con esas cosas de bajo nivel :) – Justinas

5

La mejor manera es NO leer el archivo (o incluso cada línea a la vez como gimel sugerido).

Puede usar algo de herencia y extender una clase desde FieldStorage y luego anular la función make_file. Se llama a make_file cuando FieldStorage es de tipo archivo.

Para su referencia, make_file por defecto tiene el siguiente aspecto:

def make_file(self, binary=None): 
    """Overridable: return a readable & writable file. 

    The file will be used as follows: 
    - data is written to it 
    - seek(0) 
    - data is read from it 

    The 'binary' argument is unused -- the file is always opened 
    in binary mode. 

    This version opens a temporary file for reading and writing, 
    and immediately deletes (unlinks) it. The trick (on Unix!) is 
    that the file can still be used, but it can't be opened by 
    another process, and it will automatically be deleted when it 
    is closed or when the current process terminates. 

    If you want a more permanent file, you derive a class which 
    overrides this method. If you want a visible temporary file 
    that is nevertheless automatically deleted when the script 
    terminates, try defining a __del__ method in a derived class 
    which unlinks the temporary files you have created. 

    """ 
    import tempfile 
    return tempfile.TemporaryFile("w+b") 

en lugar de crear temporaryfile, crear de forma permanente el archivo donde desee.

2

Usando una respuesta por @hasanatkazmi (utilizado en una aplicación torcida) Tengo algo como:

#!/usr/bin/env python2 
# -*- coding: utf-8 -*- 
# -*- indent: 4 spc -*- 
import sys 
import cgi 
import tempfile 


class PredictableStorage(cgi.FieldStorage): 
    def __init__(self, *args, **kwargs): 
     self.path = kwargs.pop('path', None) 
     cgi.FieldStorage.__init__(self, *args, **kwargs) 

    def make_file(self, binary=None): 
     if not self.path: 
      file = tempfile.NamedTemporaryFile("w+b", delete=False) 
      self.path = file.name 
      return file 
     return open(self.path, 'w+b') 

Tenga en cuenta, que el archivo es no siempre creado por el módulo cgi.De acuerdo con estos cgi.py líneas que sólo se creará si el contenido excede de 1000 bytes:

if self.__file.tell() + len(line) > 1000: 
    self.file = self.make_file('') 

Por lo tanto, usted tiene que comprobar si el archivo fue creado en realidad con una consulta a una clase personalizada path campo de este modo:

if file_field.path: 
    # Using an already created file... 
else: 
    # Creating a temporary named file to store the content. 
    import tempfile 
    with tempfile.NamedTemporaryFile("w+b", delete=False) as f: 
     f.write(file_field.value) 
     # You can save the 'f.name' field for later usage. 

Si el Content-Length también están destinados para el campo, que parece rara vez, el archivo debe también ser creado por cgi.

Eso es todo. De esta forma, puede almacenar el archivo de forma predecible, disminuyendo el espacio de uso de memoria de su aplicación.

Cuestiones relacionadas