Servir archivos grandes (con cargas altas) en Django

He estado usando un método para servir descargas pero como no era seguro, decidí cambiarlo. (El método era un enlace al archivo original almacenado, ¡pero el riesgo era que todos los que tenían el enlace pudieran haber descargado el archivo!), así que ahora sirvo el archivo a través de mis vistas, de esa manera solo los usuarios con permiso pueden descargar el archivo. pero estoy notando una alta carga en el servidor mientras hay muchas solicitudes de descarga simultáneas para los archivos. Aquí hay parte de mi código que maneja las descargas para los usuarios (considera que el archivo es una imagen)

image = Image.open ("the path to file") response = HttpResponse(mimetype = 'image/png' ) response['Content-Disposition'] = 'attachment: filename=%s.png' % filename image.save(response , "png") return response 

¿Existe alguna forma mejor de servir archivos mientras se mantiene la seguridad y se reduce la carga lateral del servidor? gracias por adelantado 🙂

Su apertura de la imagen la carga en la memoria y esto es lo que causa el aumento de la carga en uso intensivo. Como lo publicó Martin, la verdadera solución es entregar el archivo directamente.

Aquí hay otro enfoque, que transmitirá su archivo en trozos sin cargarlo en la memoria.

 import os import mimetypes from django.http import StreamingHttpResponse from django.core.servers.basehttp import FileWrapper def download_file(request): the_file = '/some/file/name.png' filename = os.path.basename(the_file) chunk_size = 8192 response = StreamingHttpResponse(FileWrapper(open(the_file, 'rb'), chunk_size), content_type=mimetypes.guess_type(the_file)[0]) response['Content-Length'] = os.path.getsize(the_file) response['Content-Disposition'] = "attachment; filename=%s" % filename return response 

Puede usar el método ‘sendfile’ como se describe en esta respuesta .

Prácticamente necesitas esto (c & p):

 response = HttpResponse(mimetype='application/force-download') response['Content-Disposition'] = 'attachment; filename=%s' % smart_str(file_name) response['X-Sendfile'] = smart_str(path_to_file) # It's usually a good idea to set the 'Content-Length' header too. # You can also set any other required headers: Cache-Control, etc. return response 

Esto requiere mod_xsendfile (que también es compatible con nginx o lighty )

FileWrapper no funcionará cuando se instale GZipMiddleware (Django 1.4 y versiones anteriores): https://code.djangoproject.com/ticket/6027

Si usa GZipMiddleware, una solución práctica es escribir una subclase de FileWrapper así:

 from wsgiref.util import FileWrapper class FixedFileWrapper(FileWrapper): def __iter__(self): self.filelike.seek(0) return self import mimetypes, os my_file = '/some/path/xy.ext' response = HttpResponse(FixedFileWrapper(open(my_file, 'rb')), content_type=mimetypes.guess_type(my_file)[0]) response['Content-Length'] = os.path.getsize(my_file) response['Content-Disposition'] = "attachment; filename=%s" % os.path.basename(my_file) return response 

A partir de Python 2.5, no es necesario importar FileWrapper desde Django.

A menos que vaya a atender un número muy pequeño de tales solicitudes, cualquier solución que requiera la entrega de su contenido a través de django no será escalable. Para que cualquier cosa se pueda escalar en el futuro, probablemente querrá trasladar su almacenamiento de contenido y su servicio a un servidor separado y esto no funcionará.

La forma recomendada sería mantener el contenido estático servido a través de un servidor más ligero (como nginx). Para agregar seguridad, pase un token del servidor estático desde django configurando la cookie o mediante los parámetros de obtención.

El token debe tener los siguientes valores: marca de tiempo, nombre de archivo, ID de usuario. Debe ser firmado a través de alguna clave por la aplicación django.

A continuación, escriba un pequeño módulo nginx que compruebe el token y que el usuario tenga acceso al archivo. También debe verificar que el token no sea lo suficientemente antiguo al verificar la marca de tiempo.

Es mejor usar FileRespose, es una subclase de StreamingHttpResponse optimizado para archivos binarios. Utiliza wsgi.file_wrapper si lo proporciona el servidor wsgi, de lo contrario, transmite el archivo en fragmentos pequeños.

 import os from django.http import FileResponse from django.core.servers.basehttp import FileWrapper def download_file(request): _file = '/folder/my_file.zip' filename = os.path.basename(_file) response = FileResponse(FileWrapper(file(filename, 'rb')), content_type='application/x-zip-compressed') response['Content-Disposition'] = "attachment; filename=%s" % _file return response