19

我正在实现一个 REST 风格的界面,并希望能够通过 HTTP PUT 请求创建(通过上传)文件。我想创建一个TemporaryUploadedFile或一个InMemoryUploadedFile,然后我可以将其传递给我现有的FileField.save()作为模型一部分的对象,从而存储文件。

我不太确定如何处理文件上传部分。具体来说,这是一个 put 请求,我无权访问,因为它在请求request.FILES中不存在。PUT

所以,一些问题:

  • 我可以利用HttpRequest类中的现有功能,特别是处理文件上传的部分吗?我知道直接PUT不是多部分 MIME 请求,所以我不这么认为,但值得一问。
  • 如何推断正在发送的 mime 类型?如果我没看错的话,PUT 主体就是没有前奏的文件。因此,我是否要求用户在其标题中指定 mime 类型?
  • 如何将其扩展到大量数据?我不想将它全部读入内存,因为那是非常低效的。理想情况下,我会做TemporaryUploadFile相关代码的工作 - 一次写一部分?

我查看了这个代码示例,它诱使 Django 将其PUT作为POST请求进行处理。如果我做对了,它只会处理表单编码数据。这是 REST,因此最好的解决方案是不假设存在表单编码数据。但是,我很高兴听到有关以某种方式使用 mime(而不是 multipart)的适当建议(但上传应该只包含一个文件)。

Django 1.3 是可以接受的。所以我可以用request.raw_post_dataor做一些事情request.read()(或者其他更好的访问方法)。有任何想法吗?

4

3 回答 3

8

Django 1.3 是可以接受的。所以我可以用 request.raw_post_data 或 request.read() 做一些事情(或者其他更好的访问方法)。有任何想法吗?

您不想触摸request.raw_post_data- 这意味着将整个请求正文读入内存,如果您正在谈论文件上传,这可能是一个非常大的数量,所以request.read()要走的路。您也可以使用 Django <= 1.2 执行此操作,但这意味着要深入研究HttpRequest以找出使用私有接口的正确方法,然后确保您的代码也与 Django >= 1.3 兼容是一个真正的麻烦.

我建议您要做的是复制该类的现有文件上传行为部分MultiPartParser

  1. 从中检索上传处理request.upload_handlers程序(默认情况下为MemoryFileUploadHandler& TemporaryFileUploadHandler
  2. 确定请求的内容长度(在HttpRequest或中搜索 Content-LengthMultiPartParser以查看执行此操作的正确方法。)
  3. 确定上传文件的文件名,方法是让客户端使用 url 的最后一个路径部分指定它,或者让客户端Content-Dispositionheader的 "filename=" 部分指定它。
  4. 对于每个处理程序,handler.new_file使用相关参数调用(模拟字段名称)
  5. request.read()使用和调用handler.receive_data_chunk()每个块读取请求正文。
  6. 对于每个处理程序调用handler.file_complete(),如果它返回一个值,那就是上传的文件。

如何推断正在发送的 mime 类型?如果我没看错的话,PUT 主体就是没有前奏的文件。因此,我是否要求用户在其标题中指定 mime 类型?

要么让客户端在 Content-Type 标头中指定它,要么使用python 的 mimetype 模块来猜测媒体类型。

我很想知道你是如何处理这件事的——这是我一直想审视自己的事情,如果你能发表评论让我知道进展如何,那就太好了!


Ninefingers根据要求进行编辑,这就是我所做的,完全基于上述内容和 django 源。

upload_handlers = request.upload_handlers
content_type   = str(request.META.get('CONTENT_TYPE', ""))
content_length = int(request.META.get('CONTENT_LENGTH', 0))

if content_type == "":
    return HttpResponse(status=400)
if content_length == 0:
    # both returned 0
    return HttpResponse(status=400)

content_type = content_type.split(";")[0].strip()
try:
    charset = content_type.split(";")[1].strip()
except IndexError:
    charset = ""

# we can get the file name via the path, we don't actually
file_name = path.split("/")[-1:][0]
field_name = file_name

因为我在这里定义了 API,所以跨浏览器支持不是问题。就我的协议而言,不提供正确的信息是一个错误的请求。对于是否要说image/jpeg; charset=binary或是否要允许不存在的字符集,我有两种看法。无论如何,我将Content-Type有效地设置为客户端责任。

同样,对于我的协议,文件名是传入的。我不确定field_name参数是什么,来源也没有给出太多线索。

下面发生的事情实际上比看起来要简单得多。您询问每个处理程序是否会处理原始输入。正如上述状态的作者,你有MemoryFileUploadHandler&TemporaryFileUploadHandler默认情况下。好吧,事实证明,MemoryFileUploadHandler当被要求创建一个new_file决定是否处理文件时(基于各种设置)。如果它决定要这样做,它会抛出一个异常,否则它不会创建文件并让另一个处理程序接管。

我不确定它的目的counters是什么,但我从源头上保留了它。其余的应该是直截了当的。

counters = [0]*len(upload_handlers)

for handler in upload_handlers:
    result = handler.handle_raw_input("",request.META,content_length,"","")

for handler in upload_handlers:

    try:
        handler.new_file(field_name, file_name, 
                         content_type, content_length, charset)
    except StopFutureHandlers:
        break

for i, handler in enumerate(upload_handlers):
    while True:
        chunk = request.read(handler.chunk_size)
        if chunk:

            handler.receive_data_chunk(chunk, counters[i])
            counters[i] += len(chunk)
        else:
            # no chunk
            break

for i, handler in enumerate(upload_handlers):
    file_obj = handler.file_complete(counters[i])
    if not file_obj:
        # some indication this didn't work?
        return HttpResponse(status=500) 
    else:
        # handle file obj!
于 2011-04-23T01:19:30.510 回答
1

得益于https://gist.github.com/g00fy-/1161423 ,较新的 Django 版本可以更轻松地处理此问题

我修改了给定的解决方案,如下所示:

if request.content_type.startswith('multipart'):
    put, files = request.parse_file_upload(request.META, request)
    request.FILES.update(files)
    request.PUT = put.dict()
else:
    request.PUT = QueryDict(request.body).dict()

能够访问文件和其他数据,如 POST。.dict()如果您希望您的数据是只读的,您可以删除对的调用。

于 2017-02-28T15:50:13.887 回答
0

我在使用 Django 2.2 时遇到了这个问题,并且正在寻找可以通过 PUT 请求上传文件的东西。

from django.http import QueryDict
from django.http.multipartparser import MultiValueDict
from django.core.files.uploadhandler import (
    SkipFile,
    StopFutureHandlers,
    StopUpload,
)


class PutUploadMiddleware(object):
    def __init__(self, get_response):
        self.get_response = get_response

    def __call__(self, request):
        method = request.META.get("REQUEST_METHOD", "").upper()
        if method == "PUT":
            self.handle_PUT(request)
        return self.get_response(request)

    def handle_PUT(self, request):
        content_type = str(request.META.get("CONTENT_TYPE", ""))
        content_length = int(request.META.get("CONTENT_LENGTH", 0))
        file_name = request.path.split("/")[-1:][0]
        field_name = file_name
        content_type_extra = None

        if content_type == "":
            return HttpResponse(status=400)
        if content_length == 0:
            # both returned 0
            return HttpResponse(status=400)

        content_type = content_type.split(";")[0].strip()
        try:
            charset = content_type.split(";")[1].strip()
        except IndexError:
            charset = ""

        upload_handlers = request.upload_handlers

        for handler in upload_handlers:
            result = handler.handle_raw_input(
                request.body,
                request.META,
                content_length,
                boundary=None,
                encoding=None,
            )
        counters = [0] * len(upload_handlers)
        for handler in upload_handlers:
            try:
                handler.new_file(
                    field_name,
                    file_name,
                    content_type,
                    content_length,
                    charset,
                    content_type_extra,
                )
            except StopFutureHandlers:
                break

        for chunk in request:
            for i, handler in enumerate(upload_handlers):
                chunk_length = len(chunk)
                chunk = handler.receive_data_chunk(chunk, counters[i])
                counters[i] += chunk_length
                if chunk is None:
                    # Don't continue if the chunk received by
                    # the handler is None.
                    break

        for i, handler in enumerate(upload_handlers):
            file_obj = handler.file_complete(counters[i])
            if file_obj:
                # If it returns a file object, then set the files dict.
                request.FILES.appendlist(file_name, file_obj)
                break
        any(handler.upload_complete() for handler in upload_handlers)
于 2020-05-16T15:29:24.357 回答