請求頭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。