3

What is the best practice to serve image files in Django for production? I want to respond with static images and I'm deploying my Django app to Heroku.

Are there any significant drawbacks of using django.middleware.security.SecurityMiddleware instead of whitenoise.middleware.WhiteNoiseMiddleware in terms of efficiency or security?

Is the code below inefficient compared to using whitenoise? Is serving an image from settings.MEDIA_ROOT the same as serving a static file?

img =  os.path.join(settings.MEDIA_ROOT, filename)
try:
    with open(img, "rb") as f:
        return HttpResponse(f.read(), content_type="image/jpeg")
except IOError:
    failedResponse = '{"detail": "No image found"}'
    return HttpResponse(failedResponse)
wim
  • 338,267
  • 99
  • 616
  • 750
Manan Mehta
  • 5,501
  • 1
  • 18
  • 18
  • what the issues you are getting while add the django middileware – Cadmus Feb 16 '17 at 02:34
  • The whitenoise documentation [here](http://whitenoise.evans.io/en/stable/django.html) says that we should not use both the middlewares and I just wanted to know if there was a drawback of using whitenoise over django.security – Manan Mehta Feb 16 '17 at 02:47
  • 1
    Django has some built-in security mechanism, whatever request is coming to our system it will test the intruder actions whether any malware action and etc, so my suggestion follow up with Django middleware bcoz we can trust. But whitenoise is third party package we can't trust them until without knowledge. if you facing any issue related static files we can rectify – Cadmus Feb 16 '17 at 02:54
  • I want to build the backend restful api and the client-side UI (using angularjs) onto one server itself but from what I understand, django doesn't serve static pages if I set debug=False in settings.py. I could be wrong though. Do you have any suggestions as to how I should go about doing this? – Manan Mehta Feb 16 '17 at 03:00
  • 1
    Thats the problem... ok got it. can i added the solution in answer section ? – Cadmus Feb 16 '17 at 03:06
  • 2
    @MananMehta Could you take a look at my answer? I don't think SnakeFcz's suggestion is a good idea. – Alex L Feb 16 '17 at 03:39

1 Answers1

10

Your comment:

The whitenoise documentation here says that we should not use both the middlewares and I just wanted to know if there was a drawback of using whitenoise over django.security – Manan Mehta 30 mins ago

No, it doesn't say that - that section of the documentation refers to the order of MIDDLEWARE_CLASSES. You can happily use Whitenoise with Django's security middleware.

Excerpt from the docs below:

Edit your settings.py file and add WhiteNoise to the MIDDLEWARE_CLASSES list, above all other middleware apart from Django’s SecurityMiddleware:

MIDDLEWARE_CLASSES = [  
    # 'django.middleware.security.SecurityMiddleware',
    'whitenoise.middleware.WhiteNoiseMiddleware',  
    # ...
]

SnakeFcz's recommendation to force Django to serve static is not a good idea - the Whitenoise package is designed as a high performance method to serve static through Django. See the heroku docs and the Whitenoise docs.


Reply to your edit:

In Django, static refers to things like JS/CSS/images that you create during development that don't change (remains static) when your app is deployed. Media are things like user uploaded images and video, or generated images (e.g. thumbnails).

Django recommends storing static and media separately, in STATIC_ROOT and MEDIA_ROOT directories. Then in production you typically configure a web server at these directories for the URLs STATIC_URL and MEDIA_URL. Whitenoise simplifies things a bit by serving files from these folders correctly, without having to configure a web server.

The main concerns for getting Django to serve images correctly (static or media) are as follows:

  • Performance - Django is not optimised for serving assets, so it'll be slower than serving through a web server such as Nginx/Apache. Lots of image requests will also slow down standard page requests, because they'll queue up and cause a longer response time. This might not matter when your website is small, but changing the way your website works when you have traffic is tricky!

  • Caching headers - Django doesn't know to add cache-control headers to images when they're returned, whereas packages like Whitenoise add sensible caching headers (the most important being the cache expiry, e.g. how long for user's browsers to hang onto your images).

There are some other headers that Whitenoise handles that Django might not, depending on the way you return an image:

  • Media types - browsers need to know how to deal with the response they're getting, so there's a header called Content-Type. With your code above, you're returning every file as an image - what if the user asks for a PNG?

  • Content length - browsers use the content length (size of the response) to show progress bars, and other optimisation (such as reading the response in chunks).

  • Compression - most browsers and web servers (and Whitenoise) support compression methods such as gzip or more recently brotli (built by google). The web server compresses the file (usually once, and then the compressed file is cached) to minimise bandwidth during transfer. Depending on the image and format, you can often compress images down to around 60-70% of their size.

    Demo on lena bitmap:

    ❯ brew install gzip brotli
    
    ❯ gzip -k -v lena.bmp
    lena.bmp:      18.3% -- replaced with lena.bmp.gz
    
    ❯ bro --input lena.bmp --output lena.bmp.bro
    
    ❯ ls -lh lena*
    -rw-r--r--@ 1 alex  staff   768K Feb 16 21:41 lena.bmp
    -rw-------  1 alex  staff   527K Feb 16 21:45 lena.bmp.bro
    -rw-r--r--@ 1 alex  staff   627K Feb 16 21:41 lena.bmp.gz
    
  • Security - another reason to leave serving static assets to the web server is the potential for security exploits!

    Let's assume the code in your view to serve images is as follows, and the url is set up at static/<filename>.

    img = os.path.join(settings.MEDIA_ROOT, filename)
    with open(img, "rb") as f:
        return HttpResponse(f.read(), content_type="image/jpeg")
    

    Imagine if a malicious user navigated to yoursite.com/static//Users/alex/.ssh/id_rsa. Then filename becomes /Users/alex/.ssh/id_rsa:

    filename = '/Users/alex/.ssh/id_rsa'
    os.path.join(settings.MEDIA_ROOT, filename)
    # '/Users/alex/.ssh/id_rsa'
    

    Then the view reads in your web server's private key, and return it to the malicious user. Whoops! Now they can ssh into your server.


Media on Heroku:

One thing to bear in mind if you're deploying to Heroku is the way their dynos work. Heroku dynos are created and destroyed quite often (every time you deploy, and at least every day) so you can't rely on the file system to stay around. You can also run two or more dynos at once - these are completely separate containers run on different hosts in a data centre, they don't share a filesystem. Usually if you want to handle user uploaded media, you'll use Django-storages to store images on S3 (AWS's storage service) instead of the filesystem. You could also store images in your database, but that doesn't scale as nicely. See https://github.com/eknuth/django-heroku-s3-bootstrap-demo for an example Django app set up to store media on S3.

Alex L
  • 8,748
  • 5
  • 49
  • 75
  • coding we can do anyway right, I trying to use the Django built-in middleware, I think they may don't have the performance issues – Cadmus Feb 16 '17 at 03:30
  • 3
    @SnakeFcz No, any way is not "right" - Django is not designed to serve static files in production and Whitenoise *is*. I don't think you should make recommendations to beginner users without understanding the packages they're trying to use. – Alex L Feb 16 '17 at 03:38
  • mostly I will not suggest third party packages – Cadmus Feb 16 '17 at 03:40
  • 2
    @SnakeFcz The [Heroku documentation](https://devcenter.heroku.com/articles/django-assets#whitenoise) is pretty clear - "Django does not support serving static files in production. However, the fantastic WhiteNoise project can integrate into your Django application, and was designed with exactly this purpose in mind." – Alex L Feb 16 '17 at 03:41
  • thats only for heroku. tell me why we need to go with third party package for serving static files insted using something our own – Cadmus Feb 16 '17 at 03:47
  • 4
    @SnakeFcz The op is using Heroku! From the Q: "I want to respond with static images and I'm deploying my django app to Heroku." – Alex L Feb 16 '17 at 03:48
  • @AlexL I have edited the question for it to be more useful for others looking for answers. Also, can you please suggest something about the code I have added to my question? – Manan Mehta Feb 16 '17 at 09:55
  • 1
    @MananMehta I've expanded my answer a little, hope it helps! – Alex L Feb 16 '17 at 14:10