58

I need to serve images securely to validated users only (i.e. they can't be served as static files). I currently have the following Python view in my Django project, but it seems inefficient. Any ideas for a better way?

def secureImage(request,imagePath):
    response = HttpResponse(mimetype="image/png")
    img = Image.open(imagePath)
    img.save(response,'png')
    return response

(Image is imported from PIL.)

Sumithran
  • 6,217
  • 4
  • 40
  • 54
k-g-f
  • 1,123
  • 2
  • 10
  • 11
  • 4
    As Santia commented: "In case you try this with a more recent version of Django (as I did...) As of Django 1.7, the keyword mimetype was renamed to content_type for `HttpResponse()`" – kenorb May 29 '15 at 11:46
  • how 'red.save(response, "png")' works, I check the source code 'response' is passed to 'save' as 'fd', but it works nothing? Can someone tell me please? thanks – Henning Lee Jul 02 '19 at 12:09
  • @HenningLee, response acts like a file descriptor and you are "writing" the file to the response object. k-g-fis completely right about this being terribly inefficient – boatcoder Aug 30 '19 at 17:04
  • Use [`FileResponse`](https://docs.djangoproject.com/en/stable/ref/request-response/#fileresponse-objects) for serving files instead of `HttpResponse`. – Boris Verkhovskiy Nov 23 '20 at 19:18

3 Answers3

91

Well, re-encoding is needed sometimes (i.e. applying an watermark over an image while keeping the original untouched), but for the most simple of cases you can use:

try:
    with open(valid_image, "rb") as f:
        return HttpResponse(f.read(), content_type="image/jpeg")
except IOError:
    red = Image.new('RGBA', (1, 1), (255,0,0,0))
    response = HttpResponse(content_type="image/jpeg")
    red.save(response, "JPEG")
    return response
Thane Brimhall
  • 9,256
  • 7
  • 36
  • 50
StefanNch
  • 2,569
  • 24
  • 31
  • 3
    To determine the MIME type of the file, you can use [python-magic](https://github.com/ahupp/python-magic). – Arjan Apr 29 '13 at 13:14
  • 17
    `mimetype` was deprecated in Django 1.5, [this option is now called `content_type`](https://docs.djangoproject.com/en/1.5/ref/request-response/#django.http.HttpResponse.__init__). – bjacobel Oct 09 '14 at 04:44
  • 1
    Also, I think JPEG doesn't support RGBA, you might want to edit it to 'RGB'. – sP_ Jun 26 '18 at 17:20
  • @sP_ been a while since I've touched python, but I'm pretty sure that when you call save any transparency is ignored (if you save it as .jpg). Did you got errors using it? I deliberately used RGBA because was taken from a script that applied transparent watermarks over images. If you are to use it in RGB mode cut the last "0" also `red = Image.new('RGB', (1, 1), (255,0,0))` – StefanNch Jun 27 '18 at 09:50
  • how 'red.save(response, "JPEG")' works, I check the source code 'response' is passed to 'save' as 'fd', but it works nothing? Can someone tell me please? thanks – Henning Lee Jul 02 '19 at 12:08
  • If you want to use RGBA then use image/png & PNG. – Eli Front Feb 19 '21 at 02:27
  • If you trust the file extensions, you can determine MIME type with `mimetypes.guess_type(filename)`, which is built in (no install). – dfrankow Nov 03 '21 at 21:08
  • `Unable to import 'Image'`. – ar2015 Oct 03 '22 at 06:38
10

Make use of FileResponse
A cleaner way, here we dont have to worry about the Content-Length and Content-Type headers, they are automatically added by guessing the contents of open().

from django.http import FileResponse

def send_file(response):

    img = open('media/hello.jpg', 'rb')

    response = FileResponse(img)

    return response
Sumithran
  • 6,217
  • 4
  • 40
  • 54
1

Just stumbled on the somewhat bad advice (for production) and thought I would mention X-Sendfile which works with both Apache and Nginx and probably other webservers too.

https://pythonhosted.org/xsendfile/

Modern Web servers like Nginx are generally able to serve files faster, more efficiently and more reliably than any Web application they host. These servers are also able to send to the client a file on disk as specified by the Web applications they host. This feature is commonly known as X-Sendfile.

This simple library makes it easy for any WSGI application to use X-Sendfile, so that they can control whether a file can be served or what else to do when a file is served, without writing server-specific extensions. Use cases include:

  • Restrict document downloads to authenticated users.

  • Log who’s downloaded a file. Force a file to be downloaded instead of rendered by the browser, or serve it with a name different from the one on disk, by setting the Content-Disposition header.

The basic idea is you open the file and pass that handle back to the webserver which then returns the bytes to the client, freeing your python code to handle the next request. This is far more performant than the solution above since a slow client on the other end could hang your python thread for as long as it takes to download the file.

Here is a repo that shows how to do this for various webservers and although it is pretty old, it will at least give you an idea of what you need to do. https://github.com/johnsensible/django-sendfile

Community
  • 1
  • 1
boatcoder
  • 17,525
  • 18
  • 114
  • 178
  • it seems a little too tedious to implement with django, the github instructions are not really clear – Conor Sep 12 '22 at 13:39