[英]How do I handle file upload via PUT request in Django?
我正在实现一个 REST 风格的界面,并希望能够通过 HTTP PUT 请求创建(通过上传)文件。 我想创建一个TemporaryUploadedFile
或一个InMemoryUploadedFile
,然后我可以将其传递给我现有的FileField
和作为模型一部分的对象上的.save()
,从而存储文件。
我不太确定如何处理文件上传部分。 具体来说,这是一个放置请求,我无权访问request.FILES
因为它不存在于PUT
请求中。
所以,一些问题:
HttpRequest
类中的现有功能,特别是处理文件上传的部分吗? 我知道直接PUT
不是多部分 MIME 请求,所以我不这么认为,但值得一问。TemporaryUploadFile
和相关代码所做的事情 - 一次写一部分? 我查看了这个代码示例,它诱使 Django 将PUT
作为POST
请求处理。 如果我做对了,它只会处理表单编码的数据。 这是 REST,因此最好的解决方案是不假设存在表单编码数据。 但是,我很高兴听到有关以某种方式使用 mime(不是多部分)的适当建议(但上传应该只包含一个文件)。
Django 1.3 是可以接受的。 所以我可以用request.raw_post_data
或request.read()
(或者其他更好的访问方法request.read()
做一些事情。 有任何想法吗?
Django 1.3 是可以接受的。 所以我可以用 request.raw_post_data 或 request.read() (或者其他更好的访问方法)做一些事情。 有任何想法吗?
您不想接触request.raw_post_data
- 这意味着将整个请求正文读入内存,如果您正在谈论文件上传,这可能是一个非常大的数量,所以request.read()
是要走的路。 你也可以用 Django <= 1.2 来做到这一点,但这意味着在HttpRequest
挖掘以找出使用私有接口的正确方法,然后确保你的代码也能与 Django 兼容是一个真正的拖累>= 1.3.
我建议您要做的是复制MultiPartParser
类的现有文件上传行为部分:
request.upload_handlers
检索上传处理程序(默认情况下将是MemoryFileUploadHandler
和TemporaryFileUploadHandler
)HttpRequest
或MultiPartParser
搜索 Content-Length 以查看执行此操作的正确方法。)Content-Disposition
header的“filename=”部分指定它。handler.new_file
(模拟字段名称)request.read()
并为每个块调用handler.receive_data_chunk()
以块的形式读取请求正文。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
我有两种想法image/jpeg; charset=binary
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!
由于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()
的调用。
我在使用 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)
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.