Django之Ajax文件上传
请求头contenttype
contenttype指的是请求体的编码类型,常见的类型共有3种:
1 application/x-www-form-urlencoded(看下图)
这应该是最常见的 post 提交数据的方式了。浏览器的原生 <form> 表单,如果不设置 enctype
属性,那么最终就会以 默认格式application/x-www-form-urlencoded 方式提交数据,ajax默认也是这个。请求类似于下面这样(无关的请求头在本文中都省略掉了):
post http://www.example.com http/1.1 content-type: application/x-www-form-urlencoded;charset=utf-8 user=yuan&age=22 #这就是上面这种contenttype规定的数据格式,后端对应这个格式来解析获取数据,不管是get方法还是post方法,都是这样拼接数据,大家公认的一种数据格式,但是如果你contenttype指定的是urlencoded类型,但是post请求体里面的数据是下面那种json的格式,那么就出错了,服务端没法解开数据。
看network来查看我们发送的请求体:
点击一下上面红框的内容,你就会看到,这次post请求发送数据的原始格式
2 multipart/form-data
这又是一个常见的 post 数据提交的方式。我们使用表单上传文件时,必须让 <form> 表单的 enctype
等于 multipart/form-data,form表单不支持发json类型的contenttype格式的数据,而ajax什么格式都可以发,也是ajax应用广泛的一个原因。直接来看一个请求示例:(了解)
post http://www.example.com http/1.1 content-type:multipart/form-data; boundary=----webkitformboundaryrgkcby7qhfd3trwa ------webkitformboundaryrgkcby7qhfd3trwa content-disposition: form-data; name="user" chao ------webkitformboundaryrgkcby7qhfd3trwa content-disposition: form-data; name="file"; filename="chrome.png" content-type: image/png png ... content of chrome.png ... ------webkitformboundaryrgkcby7qhfd3trwa--
这个例子稍微复杂点。首先生成了一个 boundary 用于分割不同的字段,为了避免与正文内容重复,boundary 很长很复杂。然后 content-type 里指明了数据是以 multipart/form-data 来编码,本次请求的 boundary 是什么内容。消息主体里按照字段个数又分为多个结构类似的部分,每部分都是以 --boundary
开始,紧接着是内容描述信息,然后是回车,最后是字段具体内容(文本或二进制)。如果传输的是文件,还要包含文件名和文件类型信息。消息主体最后以 --boundary--
标示结束。
这种方式一般用来上传文件,各大服务端语言对它也有着良好的支持。
上面提到的这两种 post 数据的方式,都是浏览器原生支持的,而且现阶段标准中原生 <form> 表单也只支持这两种方式(通过 <form> 元素的 enctype
属性指定,默认为 application/x-www-form-urlencoded
。其实 enctype
还支持 text/plain
,不过用得非常少)。
随着越来越多的 web 站点,尤其是 webapp,全部使用 ajax 进行数据交互之后,我们完全可以定义新的数据提交方式,给开发带来更多便利。
3 application/json
application/json 这个 content-type 作为响应头大家肯定不陌生。实际上,现在越来越多的人把它作为请求头,用来告诉服务端消息主体是序列化后的 json 字符串。由于 json 规范的流行,除了低版本 ie 之外的各大浏览器都原生支持 json.stringify,服务端语言也都有处理 json 的函数,使用 json 不会遇上什么麻烦。
json 格式支持比键值对复杂得多的结构化数据,这一点也很有用。记得以前做过一个项目时,需要提交的数据层次非常深,我就是把数据 json 序列化之后来提交的。不过当时我是把 json 字符串作为 val,仍然放在键值对里,以 x-www-form-urlencoded 方式提交。
如果在ajax里面写上这个contenttype类型,那么data参数对应的数据,就不能是个object类型数据了,必须是json字符串,contenttype:'json',简写一个json,它也能识别是application/json类型
服务端接受到数据之后,通过contenttype类型的值来使用不同的方法解析数据,其实就是服务端框架已经写好了针对这几个类型的不同的解析数据的方法,通过contenttype值来找对应方法解析,如果有一天你写了一个contenttype类型,定义了一个消息格式,各大语言及框架都支持,那么别人也会写一个针对你的contenttype值来解析数据的方法,django里面不能帮我们解析contenttype值为json的数据格式,你知道他能帮你解析application/x-www-form-urlencoded 和multipart/form-data(文件上传会用到)就行了,如果我们传json类型的话,需要我们自己来写一个解析数据的方法,其实不管是什么类型,我们都可以通过原始发送来的数据来进行加工处理,解析出自己想要的数据,这个事情我们在前面自己写web框架的时候在获取路径那里就玩过了,还记得吗?
$.ajax({ url:"{% url 'home' %}", type:'post', headers:{ "x-csrftoken":$.cookie('csrftoken'), #现在先记住,等学了cookies你就明白了 contenttype:'json', }, data:json.stringify({ //如果我们发送的是json数据格式的数据,那么csrf_token就不能直接写在data里面了,没有效果,必须通过csrf的方式3的形式来写,写在hearders(请求头,可以写一些自定制的请求头)里面,注意,其实contenttype也是headers里面的一部分,写在里面外面都可以 name:name, //csrfmiddlewaretoken:$("[name='csrfmiddlewaretoken']").val(), }), success:function (response) { } })
基于form表单的文件上传
# upload.html {% load static %} <!doctype html> <html lang="en"> <head> <meta charset="utf-8"> <title>form上传文件</title> <link rel="stylesheet" href="{% static 'bootstrap-3.3.7-dist/css/bootstrap.min.css' %}"> </head> <body> <div class="container"> <div class="row"> <div style="margin-top: 200px"></div> <div class="col-md-6 col-md-offset-3"> <form class="form-horizontal" method="post" action="{% url 'upload' %}" enctype="multipart/form-data"> {% csrf_token %} <div class="form-group"> <label for="exampleinputemail1">用户名</label> <input type="text" class="form-control" id="username" placeholder="用户名" name="username"> </div> <div class="form-group"> <label for="exampleinputfile">选择文件</label> <input type="file" id="file_obj" name="file_obj"> </div > <div class="form-group"> <button type="submit" class="btn btn-default">上传</button> </div> </form> </div> </div> </div> </body> </html>
#views.py import os from django.shortcuts import render, httpresponse from content_upload import settings # create your views here. def upload(request): if request.method == 'get': return render(request, 'upload.html') else: username = request. post.get('username') file_obj = request.files.get('file_obj') print(request.files) print(username) print('>>>>>>>>>>>>>>>>', file_obj) file_name = file_obj.name print(file_name) path = os.path.join(settings.base_dir, 'files', file_name) # 将文件放入只能文件夹中 # 读文件方式 with open(path, 'wb') as f: for i in file_obj: f.write(i) # # django提供的chunks方法 # from django.core.files.uploadedfile import inmemoryuploadedfile # with open(path, 'wb') as f: # for chunk in file_obj.chunks(): # f.write(chunk) return httpresponse('ok')
# urls.py from django.conf.urls import url from django.contrib import admin from app01 import views urlpatterns = [ # url(r'^admin/', admin.site.urls), url(r'^upload/', views.upload, name='upload'), ]
基于ajax的文件上传(js)
# ajax_upload.html {% load static %} <!doctype html> <html lang="en"> <head> <meta charset="utf-8"> <title>title</title> <link rel="stylesheet" href="{% static 'bootstrap-3.3.7-dist/css/bootstrap.min.css' %}"> </head> <body> <div class="container"> <div class="row"> <div style="margin-top: 200px"></div> <div class="col-md-6 col-md-offset-3"> {% csrf_token %} <div class="form-group"> <label for="exampleinputemail1">用户名</label> <input type="text" class="form-control" id="username" placeholder="用户名" name="username"> </div> <div class="form-group"> <label for="exampleinputfile">选择文件</label> <input type="file" id="file_obj" name="file_obj"> </div > <div class="form-group"> <button id="sub" type="submit" class="btn btn-default">上传</button> </div> </div> </div> </div> </body> <script src="{% static 'js/jquery.js' %}"></script> <script src="{% static 'js/jquery.cookie.js' %}"></script> <script> $('button[type=submit]').click(function () { var name = $('input[name=username]').val(); var file = $('input[type=file]')[0].files[0]; var csrf = $('input[name=csrfmiddlewaretoken]').val(); var formdata = new formdata(); formdata.append('username', name); formdata.append('file_obj', file); formdata.append('csrfmiddlewaretoken', csrf); $.ajax({ url: "{% url 'ajax_upload' %}", type: 'post', data: formdata, processdata: false, // 不处理数据( 必须有) contenttype:false, //不设置内容类型 ( 必须要) header:{ 'x-csrftoken': $.cookie('csrftoken'), }, success:function (res) { } }) }) </script> </html>
# views.py def ajax_upload(request): if request.method == 'get': return render(request, 'ajax_upload.html') else: username = request.post.get('username') print(username) file_obj = request.files.get('file_obj') file_name = file_obj.name path = os.path.join(settings.base_dir, 'files', file_name) # 读取文件形式 with open(path, 'wb') as f: for i in file_obj: f.write(i) # django 提供的chunks方法 from django.core.files.uploadedfile import inmemoryuploadedfile with open(path, 'wb') as f: for chunk in file_obj.chunks(): f.write(chunk) return httpresponse('ok')
# urls.py from django.conf.urls import url from django.contrib import admin from app01 import views urlpatterns = [ # url(r'^admin/', admin.site.urls), url(r'^ajax_upload/', views.ajax_upload, name='ajax_upload'), ]
检查浏览器的请求头:
content-type: multipart/form-data; boundary=----webkitformboundaryawl9k5zmitazx3ft
关于django后端代码接受上传文件的方法
当django处理上传一个文件的时候,文件数据被放在request.files中。这个文档解释文件怎么样被存储在磁盘上或者内存中,怎样定制默认的行为。 基本文件上传 考虑一个包含filefield的简单的表单: from django import forms classuploadfileform(forms.form): title=forms.charfield(max_length=50) file=forms.filefield() 一个处理这个表单的视图将在request.files中接受文件数据 ,request.files是一个字典,它对每个filefield(或者是imagefield,或者是其他的filefield的子类)都包含一个key.所以 从上面的表单中来的数据将可以通过request.files['file']键来访问. 注意request.files只有 在request方法是post并且发出post请求的 有属性enctype="multipart/form-data".否则,request。files将是空的。 看另一个简单的; from fdjango.http improt httpresponseredirect from django.shortcuts import render_to_response from somewhere import handle_uploaded_file def upload_file(request): if request.method == 'post': form = uploadfileform(rquest.post,request.files) if form.is_valid(): handle_uploaded_file(request.files['file']) return httpresponseredirect('/success/ur/') else: form = uploadfileform() return render_to_response('upload.html',{'form':form}) 要注意,我们必须将request.files传递到表单的构造器中;这就是文件数据怎样和表单沾上边的 。 处理上传的文件 最后的难题是怎样处理从request.files中获得的真实的文件。这个字典的每个输入都是一个uploadedfile对象——一个上传之后的文件的简单的包装。 你通常会使用下面的几个方法来访问被上传的内容: uploadedfile.read():从文件中读取整个上传的数据。小心整个方法:如果这个文件很大,你把它读到内存中会弄慢你的系统。你可以想要使用chunks()来代替,看下面; uploadedfile.multiple_chunks():如果上传的文件足够大需要分块就返回真。默认的这个值是2.5兆,当然这个值是可以调节的,看下面的uploadedfile.chunks():一个产生器,返回文件的块。如果multiple_chunks()是真的话,你应该在一个循环中使用这个方法,而不是使用read(); uploadedfile.name:上传文件的名字(比如m_file.txt) uploadedfile.size:以bytes表示的上传的文件的大小。 还有其他的几个方法和属性。你可以自己去查。 把他们放在一起,这里是一个你处理上传文件的通常方法: def handle_uploaded_file(f): destination = open('some/file/name.txt','wb+') for chunk in f.chunks(): destination.write(chunk) destination.close() 在uploadedfile.chunks()上循环而不是用read()保证大文件不会大量使用你的系统内存。 上传的数据存在哪里? 在你保存上传的文件之前,数据需要被保存在某些地方。默认呢的,如果一个上传的文件小于2.5兆,django会将上传的东西放在内存里。这意味着只要从内存读取数据并保存到硬盘上,所以很快。然而,如果一个上传的文件太大,django将将上传的文件写到一个临时的文件中,这个文件在你的临时文件路径中。在unix-like的平台上意味着你可以预见django产生一个文件保存为/tmp/tmpzfp6i6.upload的文件。如果这个文件足够大,你可以观察到这个文件的大小在增大。 很多细节--2.5m;/tmp;等 等 都是简单的看上去合理的默认值。继续阅读看看你怎么样个性化或者完全替代掉上传行为。 改变上传处理行为 三个设置改变django的上传处理行为: file_upload_max_memory_size:以bytes为单位的到内存中的最大大小,。比这个值大的文件将被先存到磁盘上。默认是2.5兆 file_upload_temp_dir:比file_upload_max_memory_size大的文件将被临时保存的地方。默认是系统标准的临时路径。 file_upload_permissions:如果这个没有给出或者是none,你将获得独立于系统的行为。大多数平台,临时文件有一个0600模式,从内存保存的文件将使用系统标准umask。 file_upload_handlers:上传文件的处理器。改变这个设置允许完全个性化——甚至代替——django的上传过程。 默认是: ("django.core.files.uploadhandler.memoryfileuploadhandler", "django.core.files.uploadhandler.temporaryfileuploadhandler",) uploadedfile 对象 class uploadedfile 作为那些重file继承的补充,素有的uploadedfile对象定义了下面的方法和属性: uploadedfile.content_type 文件的content_type头(比如text/plain orapplication/pdf )。像用户提供的任何数据一样,你不应该信任上传的数据就是这个类型。你仍然要验证这个文件包含这个头声明的content-type——“信任但是验证”。 uploadedfile.charset 对于text/*的content-types,浏览器提供的字符集。再次,“信任但是验证”是最好的策略。 uploadedfile.temporary_file_path():只有被传到磁盘上的文件才有这个方法,它返回临时上传文件的全路径。 注意: 像通常的python文件,你可以迭代上传的文件来一行一行得读取文件: for line in uploadedfile: do_something_with(line) 然而,不同于标准python文件,uploadedfile值懂得/n(也被称为unix风格)的结尾。如果你知道你需要处理有不同风格结尾的文件的时候,你要在你的视图中作出处理。 上传处理句柄: 当一个用户上传一个文件,django敬爱那个这个文件数据传递给上传处理句柄——一个处理随着文件上传处理文件的小类。上传处理句柄被file_upload_handlers初始化定义,默认是: ( "django.core.files.uploadhandler.memoryfileuploadhandler" , "django.core.files.uploadhandler.temporaryfileuploadhandler" ,) 这两个提供了django处理小文件和大文件的默认上产行为。 你可以个性化处理句柄来个性化django处理文件的行为。比如你可以使用个性化的处理句柄来强制用户配额,实时地压缩数据,渲染进度条,甚至在保存在本地的同时向另一个存储地发送数据。 实时修改上传处理句柄 有的时候某些视图要使用不同的上传行为。这种情况下,你可以重写一个上传处理句柄,通过request.upload_handlers来修改。默认的,这个列表包含file_upload_handlers提供的处理句柄,但是你可以像修改其他列表一样修改这个列表。 比如,加入你写了一个叫做 progressbaruploadhandler 的处理句柄。你可以通过下面的形式加到你的上传处理句柄中: request.upload_handlers.insert(0,progressbaruploadhandler()) 你赢使用list.insert()在这种情况下。因为进度条处理句柄需要首先执行。记住,处理句柄按照顺序执行。 如果你像完全代替掉上传处理句柄,你可以赋值一个新的列表: request.upload_handlers=[progressbaruploadhandler()] 注意:你只能在访问request.post或者request.files之前修改上传处理句柄。——如果上传处理开始后再改就没用了。如果你在修改reqeust.uplaod_handlers之前访问了request.post or request.files ,django将抛出一个错误。 所以,在你的视图中尽早的修改上传处理句柄。 写自定义的上传处理句柄: 所有的上传处理句柄都应 是 django.core.files.uploadhandler.fileuploadhandler的子类。你可以在任何你需要的地方定义句柄。 需要的方法: 自定义的上传处理句柄必须定义一下的方法: fileuploadhandler.receive_data_chunk(self,raw_data,start):从文件上传中接收块。 raw_data是已经上传的字节流 start是raw_data块开始的位置 你返回的数据将被传递到下一个处理句柄的receive_data_chunk方法中。这样一个处理句柄就是另一个的过滤器了。 返回none将阻止后面的处理句柄获得这个块,当你 自己存储这个数据,而不想其他处理句柄存储拷贝时很有用。 如果你触发一个stopupload或者skipfile异常,上传将被放弃或者文件被完全跳过。 fileuploadhandler.file_complete(self, file_size) 当 文件上传完毕时调用。 处理句柄应该返回一个uploadfile对象,可以存储在request.files中。处理句柄也可以返回none来使得uploadfile对象应该来自后来的上传处理句柄。 剩下的就是可选的一些方法实现。 file_upload_max_memory_size = 209715200 file_upload_max_memory_size = 209715200 在你本机先好好测试一下,它是如何占用内存,什么时候开始存入temp目录,怎么迁移到upload目录底下的 文件上传的时候,如果一个上传的文件小于2.5兆,django会将上传的东西放在内存里,如果上传的文件大于2.5m,django将整个上传的文件写到一个临时的文件中,这个文件在临时文件路径中。上传完毕后,将调用view中的_upload()方法将临时文件夹中的临时文件分块写到上传文件的存放路径下,每块的大小为64k,写完后临时文件将被删除。 uploadedfile.multiple_chunks():如果上传的文件足够大需要分块就返回真。默认的这个值是2.5兆,当然这个值是可以调节的,看下面的uploadedfile.chunks():一个产生器,返回文件的块。如果multiple_chunks()是真的话,你应该在一个循环中使用这个方法,而不是使用read(); 在你保存上传的文件之前,数据需要被保存在某些地方。默认呢的,如果一个上传的文件小于2.5兆,django会将上传的东西放在内存里。这意味着只要从内存读取数据并保存到硬盘上,所以很快。然而,如果一个上传的文件太大,django将上传的文件写到一个临时的文件中,这个文件在你的临时文件路径中。在unix-like的平台上意味着你可以预见django产生一个文件保存为/tmp/tmpzfp6i6.upload的文件。如果这个文件足够大,你可以观察到这个文件的大小在增大。 三个设置改变django的上传处理行为: file_upload_max_memory_size:以bytes为单位的到内存中的最大大小,。比这个值大的文件将被先存到磁盘上。默认是2.5兆 file_upload_temp_dir:比file_upload_max_memory_size大的文件将被临时保存的地方。默认是系统标准的临时路径。 file_upload_permissions:如果这个没有给出或者是none,你将获得独立于系统的行为。大多数平台,临时文件有一个0600模式,从内存保存的文件将使用系统标准umask。