将图像写入Django HttpResponse()的最佳方法

58

我需要仅向验证用户提供安全的图片访问方式(即不允许以静态文件的形式提供)。我目前在Django项目中具有以下Python视图,但它似乎效率不高。是否有更好的方法?

def secureImage(request,imagePath):
    response = HttpResponse(mimetype="image/png")
    img = Image.open(imagePath)
    img.save(response,'png')
    return response
(图像是从PIL导入的。)

4
正如Santia所评论的:“如果您尝试使用更新版本的Django(就像我一样...),从Django 1.7开始,HttpResponse()的关键字mimetype已更名为content_type”。 - kenorb
"red.save(response, "png")" 是如何工作的?我查看了源代码,发现'response'被传递给'save'作为'fd',但是什么也没有发生?有人可以告诉我吗?谢谢。 - Henning Lee
@HenningLee,响应就像文件描述符一样,您正在将文件“写入”响应对象。k-g-f完全正确,这非常低效。 - boatcoder
使用FileResponse来提供文件,而不是使用HttpResponse - user3064538
4个回答

91

有时候需要重新编码(例如在保持原始图像不变的情况下应用水印),但对于最简单的情况,您可以使用:

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

3
要确定文件的MIME类型,可以使用python-magic - Arjan
17
在Django 1.5中,mimetype已被弃用,现在该选项称为content_type - bjacobel
1
此外,我认为JPEG不支持RGBA,你可能需要将其编辑为“RGB”。 - sP_
如果您想使用RGBA,请使用image/png和PNG。 - Eli Front
如果您信任文件扩展名,可以使用内置的mimetypes.guess_type(filename)来确定MIME类型(无需安装)。 - dfrankow
显示剩余3条评论

10

利用FileResponse
这种方式更加简洁,我们不必担心Content-LengthContent-Type头部信息,因为它们会通过猜测open()中的内容自动添加。

from django.http import FileResponse

def send_file(response):

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

    response = FileResponse(img)

    return response

这会不会泄漏文件描述符? - dfrankow
2
哦,文档上说“文件将自动关闭..”。(https://docs.djangoproject.com/en/3.2/ref/request-response/#fileresponse-objects) - dfrankow

1

我刚刚偶然发现了一些对于生产环境不太好的建议,想提一下 X-Sendfile。它适用于 Apache、Nginx 和可能其他一些 Web 服务器。

https://pythonhosted.org/xsendfile/

现代Web服务器(如Nginx)通常能够比其托管的任何Web应用程序更快、更高效、更可靠地提供文件。这些服务器还能够按照其托管的Web应用程序指定的方式向客户端发送磁盘上的文件。该功能通常称为X-Sendfile。
这个简单的库使得任何WSGI应用程序都可以使用X-Sendfile,以便它们可以控制是否可以提供文件或在提供文件时要执行什么其他操作,而无需编写特定于服务器的扩展。使用案例包括:
- 将文档下载限制为经过身份验证的用户。 - 记录谁下载了文件。通过设置Content-Disposition标头,强制浏览器下载文件而不是渲染它,或者使用与磁盘上不同的名称提供它。
基本思想是打开文件并将句柄传回Web服务器,然后Web服务器将字节返回给客户端,从而释放Python代码来处理下一个请求。这比上面的解决方案更具性能,因为另一端的慢速客户端可能会挂起您的Python线程,直到下载文件完成。

这里有一个代码库展示了如何在各种Web服务器上实现此功能,尽管它相对较旧,但至少可以让你了解需要做什么。https://github.com/johnsensible/django-sendfile


似乎使用Django实现有点繁琐,GitHub的说明并不是很清晰。 - Conor

0
使用较新的视图类模式,这是我所做的。
class FileView(View):

    def get(self, request, *args, **kwargs):
        img = open('path/image.png', 'rb')
        response = FileResponse(img)
        return response

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接