如何在 Django 中通过 PUT 请求处理文件上传?

发布于 2024-11-02 15:40:47 字数 1043 浏览 1 评论 0原文

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

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

那么,有一些问题:

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

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

Django 1.3 是可以接受的。因此,我可以使用 request.raw_post_datarequest.read() (或者其他更好的访问方法)执行某些操作。有什么想法吗?

I'm implementing a REST-style interface and would like to be able to create (via upload) files via a HTTP PUT request. I would like to create either a TemporaryUploadedFile or a InMemoryUploadedFile which I can then pass to my existing FileField and .save() on the object that is part of the model, thereby storing the file.

I'm not quite sure about how to handle the file upload part. Specifically, this being a put request, I do not have access to request.FILES since it does not exist in a PUT request.

So, some questions:

  • Can I leverage existing functionality in the HttpRequest class, specifically the part that handles file uploads? I know a direct PUT is not a multipart MIME request, so I don't think so, but it is worth asking.
  • How can I deduce the mime type of what is being sent? If I've got it right, a PUT body is simply the file without prelude. Do I therefore require that the user specify the mime type in their headers?
  • How do I extend this to large amounts of data? I don't want to read it all into memory since that is highly inefficient. Ideally I'd do what TemporaryUploadFile and related code does - write it part at a time?

I've taken a look at this code sample which tricks Django into handling PUT as a POST request. If I've got it right though, it'll only handle form encoded data. This is REST, so the best solution would be to not assume form encoded data will exist. However, I'm happy to hear appropriate advice on using mime (not multipart) somehow (but the upload should only contain a single file).

Django 1.3 is acceptable. So I can either do something with request.raw_post_data or request.read() (or alternatively some other better method of access). Any ideas?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

情绪少女 2024-11-09 15:40:47

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. 确定请求的内容长度(在 HttpRequestMultiPartParser 中搜索 Content-Length 以查看执行此操作的正确方法。 )
  3. 确定上传文件的文件名,可以让客户端使用 url 的最后一个路径部分指定该文件名,也可以让客户端在 Content-Disposition 标头
  4. 对于每个处理程序,使用相关参数调用 handler.new_file (模拟字段名称)
  5. 使用 request.read() 并调用 分块读取请求正文每个块的 handler.receive_data_chunk()
  6. 对于每个处理程序调用handler.file_complete(),如果它返回一个值,则该值就是上传的文件。

我如何推断出什么的哑剧类型
正在发送?如果我没猜错的话,
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。好吧,事实证明,当要求创建 new_file 时,MemoryFileUploadHandler 将决定是否处理该文件(基于各种设置)。如果它决定这样做,它会抛出异常,否则它不会创建文件并让另一个处理程序接管。

我不确定计数器的目的是什么,但我没有透露它的来源。其余的应该很简单。

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!

Django 1.3 is acceptable. So I can
either do something with
request.raw_post_data or
request.read() (or alternatively some
other better method of access). Any
ideas?

You don't want to be touching request.raw_post_data - that implies reading the entire request body into memory, which if you're talking about file uploads might be a very large amount, so request.read() is the way to go. You can do this with Django <= 1.2 as well, but it means digging around in HttpRequest to figure out the the right way to use the private interfaces, and it's a real drag to then ensure your code will also be compatible with Django >= 1.3.

I'd suggest that what you want to do is to replicate the existing file upload behaviour parts of the MultiPartParser class:

  1. Retrieve the upload handers from request.upload_handlers (Which by default will be MemoryFileUploadHandler & TemporaryFileUploadHandler)
  2. Determine the request's content length (Search of Content-Length in HttpRequest or MultiPartParser to see the right way to do this.)
  3. Determine the uploaded file's filename, either by letting the client specify this using the last path part of the url, or by letting the client specify it in the "filename=" part of the Content-Disposition header.
  4. For each handler, call handler.new_file with the relevant args (mocking up a field name)
  5. Read the request body in chunks using request.read() and calling handler.receive_data_chunk() for each chunk.
  6. For each handler call handler.file_complete(), and if it returns a value, that's the uploaded file.

How can I deduce the mime type of what
is being sent? If I've got it right, a
PUT body is simply the file without
prelude. Do I therefore require that
the user specify the mime type in
their headers?

Either let the client specify it in the Content-Type header, or use python's mimetype module to guess the media type.

I'd be interested to find out how you get on with this - it's something I've been meaning to look into myself, be great if you could comment to let me know how it goes!


Edit by Ninefingers as requested, this is what I did and is based entirely on the above and the django source.

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

Since I'm defining the API here, cross browser support isn't a concern. As far as my protocol is concerned, not supplying the correct information is a broken request. I'm in two minds as to whether I want say image/jpeg; charset=binary or if I'm going to allow non-existent charsets. In any case, I'm putting setting Content-Type validly as a client-side responsibility.

Similarly, for my protocol, the file name is passed in. I'm not sure what the field_name parameter is for and the source didn't give many clues.

What happens below is actually much simpler than it looks. You ask each handler if it will handle the raw input. As the author of the above states, you've got MemoryFileUploadHandler & TemporaryFileUploadHandler by default. Well, it turns out MemoryFileUploadHandler will when asked to create a new_file decide whether it will or not handle the file (based on various settings). If it decides it's going to, it throws an exception, otherwise it won't create the file and lets another handler take over.

I'm not sure what the purpose of counters was, but I've kept it from the source. The rest should be straightforward.

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!
滥情哥ㄟ 2024-11-09 15:40:47

由于 https://gist.github.com/g00fy,较新的 Django 版本允许更轻松地处理此问题-/1161423

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

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() 的调用。

Newer Django versions allow for handling this a lot easier thanks to https://gist.github.com/g00fy-/1161423

I modified the given solution like this:

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()

to be able to access files and other data like in POST. You can remove the calls to .dict() if you want your data to be read-only.

听不够的曲调 2024-11-09 15:40:47

我在使用 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)

I hit this problem while working with Django 2.2, and was looking for something that just worked for uploading a file via PUT request.

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)
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文