在 Django 中提供静态文件的最佳实践

Best practice to serve static files in Django

在 Django 中为生产提供图像文件的最佳做法是什么?我想用静态图像响应,我正在将我的 Django 应用程序部署到 Heroku。

使用 django.middleware.security.SecurityMiddleware 而不是 whitenoise.middleware.WhiteNoiseMiddleware 在效率或安全方面有什么明显的缺点吗?

与使用 whitenoise 相比,下面的代码是否效率低下?提供来自 settings.MEDIA_ROOT 的图像与提供静态文件一样吗?

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)

您的评论:

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

不,它没有这么说 - 文档的那部分指的是 MIDDLEWARE_CLASSES 的顺序。配合Django的安全中间件就可以愉快的使用Whitenoise了。

摘自以下文档:

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 强制 Django 提供静态服务的建议不是一个好主意 - Whitenoise 包被设计为通过 Django 提供静态服务的高性能方法。参见heroku docs and the Whitenoise docs


回复您的编辑:

在 Django 中,静态指的是您在开发过程中创建的类似 JS/CSS/images 的内容,这些内容在部署应用程序时不会更改(保持 static)。媒体是用户上传的图像和视频,或生成的图像(例如缩略图)。

Django 建议将静态和媒体分开存储在 STATIC_ROOT 和 MEDIA_ROOT 目录中。然后在生产中,您通常会在这些目录中为 URL STATIC_URL 和 MEDIA_URL 配置 Web 服务器。 Whitenoise 通过正确地提供这些文件夹中的文件来简化事情,而无需配置 Web 服务器。

让 Django 正确提供图像(静态或媒体)的主要问题如下:

  • 性能 - Django 未针对服务资产进行优化,因此它会比通过 Nginx/Apache 等网络服务器提供服务慢。大量图像请求也会减慢标准页面请求,因为它们会 queue 并导致更长的响应时间。当您的网站较小时,这可能无关紧要,但当您有流量时,改变网站的工作方式就很棘手了!

  • 缓存 headers - Django 不知道在 returned 时将 cache-control headers 添加到图像,而像 Whitenoise 这样的软件包添加了合理的缓存 headers(最重要的是缓存过期,例如用户的浏览器在您的图像上停留多长时间)。

还有一些其他的 header Whitenoise 可以处理 Django 可能不会处理的问题,这取决于您 return 图片的方式:

  • 媒体类型 - 浏览器需要知道如何处理他们收到的响应,所以有一个 header 叫做 Content-Type。使用上面的代码,您 return 将每个文件都作为图像 - 如果用户要求 PNG 怎么办?

  • 内容长度 - 浏览器使用内容长度(响应的大小)来显示进度条和其他优化(例如读取块中的响应)。

  • 压缩 - 大多数浏览器和网络服务器(和 Whitenoise)支持压缩方法,例如 gzip 或最近的 brotli(由 google 构建)。 Web 服务器压缩文件(通常一次,然后压缩文件被缓存)以最小化传输期间的带宽。根据图像和格式,您通常可以将图像压缩到其大小的 60-70% 左右。

    lena 位图上的演示:

    ❯ 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
    
  • 安全性 - 将静态资产服务留给 Web 服务器的另一个原因是可能存在安全漏洞!

    假设您视图中用于提供图像的代码如下,并且 url 设置在 static/<filename>

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

    想象一下,如果恶意用户导航到 yoursite.com/static//Users/alex/.ssh/id_rsa。然后文件名变成 /Users/alex/.ssh/id_rsa:

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

    然后视图读取您的网络服务器的私钥,并return它给恶意用户。哎呀!现在他们可以通过 ssh 进入您的服务器。


Heroku 上的媒体:

如果您要部署到 Heroku,要记住的一件事是他们的 dynos 的工作方式。 Heroku dynos 的创建和销毁非常频繁(每次部署时,至少每天),因此您不能依赖文件系统来维持运行。您还可以同时 运行 两个或多个测功机 - 这些是完全独立的容器 运行 在数据中心的不同主机上,它们不共享文件系统。通常,如果您想处理用户上传的媒体,您将使用 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 作为示例 Django 应用程序,设置为在 S3 上存储媒体。