Django/Nginx - 服务超过一定大小的媒体文件时出现错误 403 禁止

Django/Nginx - Error 403 Forbidden when serving media files over some size

当用户上传图片时,它存储在项目目录下的media文件夹中。问题是,当他们想在网站上看到它时,nginx return 403 Forbidden 图像超过大约 3 Mb 时出错。

我设置nginx.confclient_max_body_size为8M

http {

        ##
        # Basic Settings
        ##
        client_max_body_size 8M;
     ...

并且已经在 settings.py 中更改了内存大小:

FILE_UPLOAD_MAX_MEMORY_SIZE = 8388608

当我上传 3 MB 以下的图片时,没有问题,如果我上传超过 3 MB 的图片,我可以在 media 文件夹中看到它,但出现错误而不是提供图片:

GET https://example.com/media/images/dom.jpg 403 (Forbidden)

我注意到 3 MB 以下的文件具有不同的权限:

-rw-r--r-- 1 django www-data    4962 Jul 19 19:51 61682_3995232_IMG_01_0000.jpg.150x84_q85_crop.jpg
-rw-r--r-- 1 django www-data 1358541 Jul 20 09:32 byt.jpg
-rw------- 1 django www-data 3352841 Jul 20 09:32 dom.jpg
-rw-r--r-- 1 django www-data    5478 Jul 19 20:10 downloasd.jpeg.150x84_q85_crop.jpg
-rw-r--r-- 1 django www-data    3225 Jul  9 22:53 images.jpeg.100x56_q85_crop.jpg
-rw-r--r-- 1 django www-data    6132 Jul 19 20:00 NorthYorkHouse2.JPG.150x84_q85_crop.jpg

你知道问题出在哪里吗?

编辑:

VIEW

class NehnutelnostUploadImagesView(LoginRequiredMixin, ExclusiveMaklerDetailView, DetailView):
    template_name = "nehnutelnosti/nehnutelnost_image_upload.html"
    model = Nehnutelnost

    def post(self, request, *args, **kwargs):
        self.object = self.get_object()
        form = ImageUploadForm(self.request.POST, self.request.FILES, nehnutelnost=self.object)
        if form.is_valid():
            nehnutelnost_image = form.save()
            images_count = self.object.images.count()

            data = {'is_valid': True, 'row_html': image_row_renderer(nehnutelnost_image, self.request),
                    'name': nehnutelnost_image.image.name, 'url': nehnutelnost_image.image.url,}
        else:
            images_count = self.object.images.count()

            data = {'is_valid': False, 'errors': form.errors, 'images_count': images_count}
        return JsonResponse(data)

    def get_context_data(self, **kwargs):
        context = super(NehnutelnostUploadImagesView, self).get_context_data(**kwargs)
        context['images'] = self.object.images.all()
        context['podorys'] = self.object.podorys
        return context

我们使用https://github.com/blueimp/jQuery-File-Upload插件上传图片。

$(function () {

            $(".js-upload-photos").click(function () {
                $("#fileupload").click();
            });

            $("#fileupload").fileupload({
                dataType: 'json',
                sequentialUploads: true, /* 1. SEND THE FILES ONE BY ONE */
                start: function (e) {  /* 2. WHEN THE UPLOADING PROCESS STARTS, SHOW THE MODAL */
                    $(".modal").modal().show();
                },
                stop: function (e) {  /* 3. WHEN THE UPLOADING PROCESS FINALIZE, HIDE THE MODAL */
                    $(".modal").modal().hide();
                    $(".modal-backdrop").hide();

                },
                {#                TODO Chrome bug?#}
                progressall: function (e, data) {  /* 4. UPDATE THE PROGRESS BAR */
                    var progress = parseInt(data.loaded / data.total * 100, 10);
                    var strProgress = progress + "%";
                    $(".progress-bar").css({"width": strProgress});
                    $(".progress-bar").text(strProgress);
                },
                done: function (e, data) {
                    if (data.result.is_valid) {

                        $(".gridly").prepend(
                            data.result.row_html
                        )


                    }
                    var message = data.result.message;
                    addMessage('success', message);
                    var errors = data.result.errors;
                    if (errors) {
                        $.each(errors, function (fieldname, error_messages) {
                            $.each(error_messages, function (_, message) {
                                addMessage('danger', message);
                            })
                        })
                    }
                    var images_count_span = $('#images_count');
                    var images_count = data.result.images_count;
                    images_count_span.text(' - ' + images_count);
                    makegrid();

                }

            });

来自documentation

By default, if an uploaded file is smaller than 2.5 megabytes, Django will hold the entire contents of the upload in memory.

更具体地说,这意味着较小的文件使用 MemoryFileUploadHandler while larger files use the TemporaryFileUploadHandler。后者使用 tempfile 创建一个只有用户才能访问的临时文件。

在完成所有表单和模型验证等所有操作后,实际保存由 FileSystemStorage._save 方法执行。此时,文件仍然是 TemporaryUploadedFileInMemoryUploadedFile,具体取决于其大小。

现在,TemporaryUploadedFile 是一个实际文件,由 tempfile 创建,仅具有用户权限。

save 方法做了一件聪明的事情:如果给定一个临时文件(即 if hasattr(content, 'temporary_file_path')),它会移动它而不是复制它。这意味着它保留其仅限用户的权限,并且 www-data.

无法读取

问题不会出现在 InMemoryUploadedFile 中,它将简单地使用进程拥有的任何默认权限(在您的情况下,read/write 用于用户和组)。

如何解决?

如果请求,存储对象可以设置权限。对于默认存储对象,您可以使用 FILE_UPLOAD_PERMISSIONS 进行设置。这里,

FILE_UPLOAD_PERMISSIONS=0o640

…应该可以解决问题。

(对于 django 用户是 R/W,对于 nginx 是只读的)