我正在实现一个 REST 风格的界面,并希望能够通过 HTTP PUT 请求创建(通过上传)文件。我想创建一个 TemporaryUploadedFile
或一个 InMemoryUploadedFile
然后我可以将其传递给我现有的 FileField
和 .save()
在作为模型一部分的对象上,从而存储文件。
我不太确定如何处理文件上传部分。具体来说,这是一个 put 请求,我无权访问 request.FILES
,因为它在 PUT
请求中不存在。
所以,一些问题:
- 我可以利用
HttpRequest
类中的现有功能,特别是处理文件上传的部分吗?我知道直接PUT
不是多部分 MIME 请求,所以我不这么认为,但值得一问。 - 如何推断所发送内容的 MIME 类型?如果我没看错的话,PUT 主体就是没有前奏的文件。因此,我是否要求用户在其 header 中指定 MIME 类型?
- 如何将其扩展到大量数据?我不想将它全部读入内存,因为那是非常低效的。理想情况下,我会做
TemporaryUploadFile
和相关代码所做的事情 - 一次写一部分?
我看过 this code sample这会欺骗 Django 将 PUT
作为 POST
请求进行处理。如果我做对了,它只会处理表单编码数据。这是 REST,因此最好的解决方案是不假设存在表单编码数据。不过,我很高兴听到有关以某种方式使用 mime(而不是 multipart)的适当建议(但上传应该只包含一个文件)。
Django 1.3 是可以接受的。所以我可以使用 request.raw_post_data
或 request.read()
(或者其他更好的访问方法)来做一些事情。有什么想法吗?
最佳答案
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?
您不想接触 request.raw_post_data
- 这意味着将整个请求正文读取到内存中,如果您正在谈论文件上传可能会非常大,所以 request.read()
是要走的路。您也可以使用 Django <= 1.2 来执行此操作,但这意味着在 HttpRequest
中挖掘以找出使用私有(private)接口(interface)的正确方法,然后确保您的代码能够也兼容 Django >= 1.3。
我建议您要做的是复制 existing file upload behaviour parts of the MultiPartParser
class :
- 从
request.upload_handlers
获取上传处理程序(默认为MemoryFileUploadHandler
&TemporaryFileUploadHandler
) - 确定请求的内容长度(在
HttpRequest
或MultiPartParser
中搜索 Content-Length 以查看执行此操作的正确方法。) - 确定上传文件的文件名,方法是让客户端使用 url 的最后一个路径部分指定它,或者让客户端在 the
Content-Disposition
header 的“filename=”部分指定它. - 对于每个处理程序,使用相关参数调用
handler.new_file
(模拟字段名称) - 使用
request.read()
分 block 读取请求正文,并为每个 block 调用handler.receive_data_chunk()
。 - 对于每个处理程序调用
handler.file_complete()
,如果它返回一个值,那就是上传的文件。
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?
要么让客户端在 Content-Type header 中指定它,要么使用 python's mimetype module猜测媒体类型。
我很想知道你是如何处理这件事的——这是我一直想要调查自己的事情,如果你能发表评论让我知道进展如何,那就太好了!
按要求由 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,因此无需担心跨浏览器支持。就我的协议(protocol)而言,不提供正确的信息是一个错误的请求。我对是否要说 image/jpeg; 有两种看法。 charset=binary
或者如果我要允许不存在的字符集。无论如何,我将设置 Content-Type
有效地作为客户端的责任。
同样,对于我的协议(protocol),文件名是传入的。我不确定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!
关于python - 如何在 Django 中通过 PUT 请求处理文件上传?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/5731984/