09.Django基礎七之Ajax

  • 2019 年 10 月 3 日
  • 筆記

一 Ajax簡介

1.簡介

    AJAX(Asynchronous Javascript And XML)翻譯成中文就是「非同步的Javascript和XML」。即使用Javascript語言與伺服器進行非同步交互,傳輸的數據為XML(當然,傳輸的數據不只是XML,現在更多使用json數據)。

    AJAX 不是新的程式語言,而是一種使用現有標準的新方法。

    AJAX 最大的優點是在不重新載入整個頁面的情況下,可以與伺服器交換數據並更新部分網頁內容。(這一特點給用戶的感受是在不知不覺中完成請求和響應過程)

    AJAX 不需要任何瀏覽器插件,但需要用戶允許JavaScript在瀏覽器上執行。

      a.同步交互:客戶端發出一個請求後,需要等待伺服器響應結束後,才能發出第二個請求;

      b.非同步交互:客戶端發出一個請求後,無需等待伺服器響應結束,就可以發出第二個請求。

  AJAX除了非同步的特點外,還有一個就是:瀏覽器頁面局部刷新;(這一特點給用戶的感受是在不知不覺中完成請求和響應過程

2.示例

    頁面輸入兩個整數,通過AJAX傳輸到後端計算出結果並返回。

    html文件名稱為ajax_demo1.html,內容如下

<!DOCTYPE html>  <html lang="en">  <head>    <meta charset="UTF-8">    <meta http-equiv="x-ua-compatible" content="IE=edge">    <meta name="viewport" content="width=device-width, initial-scale=1">    <title>AJAX局部刷新實例</title>  </head>  <body>    <input type="text" id="i1">+  <input type="text" id="i2">=  <input type="text" id="i3">  <input type="button" value="AJAX提交" id="b1">    <script src="/static/jquery-3.2.1.min.js"></script>  <script>    $("#b1").on("click", function () {      $.ajax({        url:"/ajax_add/", //別忘了加雙引號        type:"GET",        data:{"i1":$("#i1").val(),"i2":$("#i2").val()}, //object類型,鍵值形式的,可以不給鍵加引號        success:function (data) {          $("#i3").val(data);        }      })    })  </script>  </body>  </html>

    views.py裡面的內容:

def ajax_demo1(request):      return render(request, "ajax_demo1.html")      def ajax_add(request):    #time.sleep(10)  #不影響頁面發送其他的請求      i1 = int(request.GET.get("i1"))      i2 = int(request.GET.get("i2"))      ret = i1 + i2      return JsonResponse(ret, safe=False)    #return render(request,'index.html')  #返回一個頁面沒有意義,就是一堆的字元串,拿到了這個頁面,你怎麼處理,你要做什麼事情,根本就沒有意義

    urls.py裡面的內容

urlpatterns = [      ...      url(r'^ajax_add/', views.ajax_add),      url(r'^ajax_demo1/', views.ajax_demo1),      ...  ]

   啟動django項目,然後運行看看效果,頁面不刷新

3.AJAX常見應用情景

    搜索引擎根據用戶輸入的關鍵字,自動提示檢索關鍵字。

    還有一個很重要的應用場景就是註冊時候的用戶名的查重。

    其實這裡就使用了AJAX技術!當文件框發生了輸入變化時,使用AJAX技術向伺服器發送一個請求,然後伺服器會把查詢到的結果響應給瀏覽器,最後再把後端返回的結果展示出來。

      a.整個過程中頁面沒有刷新,只是刷新頁面中的局部位置而已!

      b.當請求發出後,瀏覽器還可以進行其他操作,無需等待伺服器的響應!

​     img

    當輸入用戶名後,把游標移動到其他表單項上時,瀏覽器會使用AJAX技術向伺服器發出請求,伺服器會查詢名為lemontree7777777的用戶是否存在,最終伺服器返回true表示名為lemontree7777777的用戶已經存在了,瀏覽器在得到結果後顯示「用戶名已被註冊!」。

    a.整個過程中頁面沒有刷新,只是局部刷新了;

    b.在請求發出後,瀏覽器不用等待伺服器響應結果就可以進行其他操作;

4.AJAX的優缺點

優點:

      1.AJAX使用JavaScript技術向伺服器發送非同步請求;

      2.AJAX請求無須刷新整個頁面;

      3.因為伺服器響應內容不再是整個頁面,而是頁面中的部分內容,所以AJAX性能高;

5.作業

    寫一個登陸認證頁面,登陸失敗不刷新頁面,提示用戶登陸失敗,登陸成功自動跳轉到網站首頁。

    login.html,內容如下:

{% load static %}  <!DOCTYPE html>  <html lang="en">  <head>      <meta charset="UTF-8">      <title>Title</title>  </head>  <body>    <div>      用戶名:<input type="text" id="username">      密碼:<input type="text" id="pwd">      {% csrf_token %}      <button id="sub">提交</button>      <span style="color: red;font-size: 12px;" id="error"></span>  </div>    <script src="{% static 'jquery.js' %}"></script>    <script>      $('#sub').click(function () {          $.ajax({              url:"{% url 'login' %}",              type:'post',              data:{username:$('#username').val(),pwd:$('#pwd').val(),csrfmiddlewaretoken:$('[name=csrfmiddlewaretoken]').val()},                success:function (data) {                  data = JSON.parse(data);                  console.log(data,typeof data);                  if (data['status']){                        location.href=data['home_url'];                  }                  else {                      $('#error').text('用戶名或者密碼錯誤!')                  }              }            })          })        </script>    </body>  </html>

    base.html,內容如下:

<!DOCTYPE html>  <html lang="en">  <head>      <meta charset="UTF-8">      <title>Title</title>  </head>  <body>    <h1>      歡迎來到xxx官網  </h1>  </body>  </html>

    urls.py,內容如下

url(r'^login/', views.login,name='login'),  url(r'^home/', views.home,name='home'),

    views.py,內容如下

def login(request):      res_dict = {'status':None,'home_url':None}      if request.method == 'GET':          return render(request,'login.html')      else:          uname = request.POST.get('username')          pwd = request.POST.get('pwd')            user_obj = models.UserInfo.objects.filter(name=uname,password=pwd).exists()          import json          if user_obj:                res_dict['status'] = True              res_dict['home_url'] = reverse('home')              res_json_dict = json.dumps(res_dict)                return HttpResponse(res_json_dict) #直接回復字典格式是不可以的,必須轉換成json字元串,轉換成普通字元串也是不行的,因為前端需要對json進行反序列獲得這個字典,在通過字典的形式來操作數據。          else:              res_dict['status'] = False              res_json_dict = json.dumps(res_dict)              return HttpResponse(res_json_dict)        # 如果你就是不使用JsonResponse的話,也可以給HttpResponse添加一個參數,content_type='application/json',那麼前端ajax拿到數據之後,也是不需要反序列化的,ajax的回調函數就收到的就是一個反序列化之後的一個對象,因為ajax接受到數據後,通過這個data_type或者content_type發現你發送來的是個json格式的數據,那麼ajax內容就自動將這個數據反序列化得到了js的數據對象,然後通過對象可以直接操作數據。      # return HttpResponse(res_json_dict,data_type='application/json')      # return JsonResponse(res_dict)  def home(request):        return render(request,'base.html')

    還有一點注意一下,如果你想通過ajax來刪除表格中某條記錄,並且ajax裡面的url不寫死的情況下(url反向解析),那麼就需要下面這種方式,實現url裡面參數的動態:

        img

    還有一個細節要注意:

      img

      並且刪除一條數據的時候,後端刪除成功之後,你通過後端給你的返回值判斷後端是否刪除成功,如果刪除成功,你有兩種方式來刪除前端頁面的對應一行的記錄,1:刷新頁面,2:如果不讓刷新頁面,那麼你就需要找到你點擊的這個按鈕的那一行的tr標籤,通過dom操作把它刪除

     ajax裡面寫$(this)時要注意的問題:還有一點注意,如果你添加某些dom對象的時候,如果你想在不刷新頁面的情況下來添加這個對象,那麼你要注意,如果這個對象也需要綁定事件的話,你需要用on來給和他相同的標籤對象來綁定事件。

      img

  在這裡補充個事情:

    settings配置文件裡面加上下面這句話,意思是說,告訴django,如果別人請求我的路徑的時候,你不要自己處理別人輸入的路徑最後面的/了,如果這個值為True,而我們假如寫了一個url為url(‘^index/’,views.test),如果用戶輸入的時127.0.0.1:8000/index的話,django會讓瀏覽器重新再發一次請求,並且在這個路徑後面加上/,也就成了127.0.0.1:8000/index/,此時和我們的url就能匹配上了,因為我們的url正則寫的就加了/,如果你將下面這個值設置成false,那麼django就不會自動幫你做這個事情了,那麼用戶在輸入127.0.0.1:8000/index,沒有最後那個斜杠的路徑時,就無法和我們的url正則匹配上了,所以就找不到url了,就會報錯,但是注意,django只能幫你重定向讓瀏覽器再發一個get請求,如果你是post請求(非get請求),django就沒有辦法了,他還是幫你重新定向發送get請求,不能滿足你的需求,所以如果你用post方法提交數據的時候,就像上面這個ajax裡面的那個url寫的必須和你後端配置的那個url對應好,所以別忘了如果你後端url上url(‘^index/’,views.test)這個index後面加了/,那麼你寫ajax往這個路徑下提交數據的時候,ajax裡面的url參數後面別忘了寫上/,讓這個url和後端url正則規則對應好。

      img

    

二 Ajax的使用

1.基於jQuery的實現

    看程式碼:

<button class="send_Ajax">send_Ajax</button>  <script>           $(".send_Ajax").click(function(){               $.ajax({                 url:"/handle_Ajax/",                 type:"POST",                 data:{username:"chao",password:123},                 success:function(data){                     console.log(data)                 },                                  error: function (jqXHR, textStatus, err) {                          console.log(arguments);                      },                   complete: function (jqXHR, textStatus) {                          console.log(textStatus);                  },                   statusCode: {                      '403': function (jqXHR, textStatus, err) {                            console.log(arguments);                       },                        '400': function (jqXHR, textStatus, err) {                          console.log(arguments);                      }                  }               })           })    </script>

2.基於原生js實現

    看程式碼

var b2 = document.getElementById("b2");    b2.onclick = function () {      // 原生JS      var xmlHttp = new XMLHttpRequest();      xmlHttp.open("POST", "/ajax_test/", true);      xmlHttp.setRequestHeader("Content-type", "application/x-www-form-urlencoded");      xmlHttp.send("username=chao&password=123456");      xmlHttp.onreadystatechange = function () {        if (xmlHttp.readyState === 4 && xmlHttp.status === 200) {          alert(xmlHttp.responseText);        }      };    };

3.Ajax-伺服器-Ajax流程圖

     img

4.ajax參數

    請求參數:

######################------------data---------################           data: 當前ajax請求要攜帶的數據,是一個json的object對象,ajax方法就會默認地把它編碼成某種格式               (urlencoded:?a=1&b=2)發送給服務端;此外,ajax默認以get方式發送請求。                 function testData() {                 $.ajax("/test",{     //此時的data是一個json形式的對象                    data:{                      a:1,                      b:2                    }                 });                   //?a=1&b=2  ######################------------processData---------################    processData:聲明當前的data數據是否進行轉碼或預處理,默認為true,即預處理;if為false,               那麼對data:{a:1,b:2}會調用json對象的toString()方法,即{a:1,b:2}.toString()               ,最後得到一個[object,Object]形式的結果。    ######################------------contentType---------################    contentType:默認值: "application/x-www-form-urlencoded"。發送資訊至伺服器時內容編碼類型。               用來指明當前請求的數據編碼格式;urlencoded:?a=1&b=2;如果想以其他方式提交數據,               比如contentType:"application/json",即向伺服器發送一個json字元串:                 $.ajax("/ajax_get",{                      data:JSON.stringify({                         a:22,                         b:33                     }),                     contentType:"application/json",                     type:"POST",                   });                          //{a: 22, b: 33}                 注意:contentType:"application/json"一旦設定,data必須是json字元串,不能是json對象                 views.py:   json.loads(request.body.decode("utf8"))      ######################------------traditional---------################    traditional:一般是我們的data數據有數組時會用到 :data:{a:22,b:33,c:["x","y"]},                traditional為false會對數據進行深層次迭代;  

    響應參數:

dataType:  預期伺服器返回的數據類型,伺服器端返回的數據會根據這個值解析後,傳遞給回調函數。              默認不需要顯性指定這個屬性,ajax會根據伺服器返回的content Type來進行轉換;              比如我們的伺服器響應的content Type為json格式,這時ajax方法就會對響應的內容              進行一個json格式的轉換,if轉換成功,我們在success的回調函數里就會得到一個json格式              的對象;轉換失敗就會觸發error這個回調函數。如果我們明確地指定目標類型,就可以使用              data Type。              dataType的可用值:html|xml|json|text|script              見下dataType實例

三 Ajax請求設置csrf_token

方式1

    通過獲取隱藏的input標籤中的csrfmiddlewaretoken值,放置在data中發送。

$.ajax({    url: "/cookie_ajax/",    type: "POST",    data: {      "username": "chao",      "password": 123456,      "csrfmiddlewaretoken": $("[name = 'csrfmiddlewaretoken']").val()  // 使用jQuery取出csrfmiddlewaretoken的值,拼接到data中    },    success: function (data) {      console.log(data);    }  })

方式2

$.ajaxSetup({      data: {csrfmiddlewaretoken: '{{ csrf_token }}' },  });

方式3(後面再說)

    通過獲取返回的cookie中的字元串 放置在請求頭中發送。

    注意:需要引入一個jquery.cookie.js插件。

<script src="{% static 'js/jquery.cookie.js' %}"></script>    $.ajax({    headers:{"X-CSRFToken":$.cookie('csrftoken')}, #其實在ajax裡面還有一個參數是headers,自訂製請求頭,可以將csrf_token加在這裡,我們發contenttype類型數據的時候,csrf_token就可以這樣加    })

  詳述CSRF(Cross-site request forgery),中文名稱:跨站請求偽造,也被稱為:one click attack/session riding,縮寫為:CSRF/XSRF。攻擊者通過HTTP請求江數據傳送到伺服器,從而盜取回話的cookie。盜取回話cookie之後,攻擊者不僅可以獲取用戶的資訊,還可以修改該cookie關聯的賬戶資訊。

  img

  所以解決csrf攻擊的最直接的辦法就是生成一個隨機的csrftoken值,保存在用戶的頁面上,每次請求都帶著這個值過來完成校驗。

  那麼django中csrf認證怎麼玩的呢?

    官方文檔中說到,檢驗token時,只比較secret是否和cookie中的secret值一樣,而不是比較整個token。
    我又有疑問了,同一次登錄,form表單中的token每次都會變,而cookie中的token不便,django把那個salt存儲在哪裡才能保證驗證通過呢。直到看到源碼。

def _compare_salted_tokens(request_csrf_token, csrf_token):      # Assume both arguments are sanitized -- that is, strings of      # length CSRF_TOKEN_LENGTH, all CSRF_ALLOWED_CHARS.      return constant_time_compare(          _unsalt_cipher_token(request_csrf_token),          _unsalt_cipher_token(csrf_token),      )    def _unsalt_cipher_token(token):      """      Given a token (assumed to be a string of CSRF_ALLOWED_CHARS, of length      CSRF_TOKEN_LENGTH, and that its first half is a salt), use it to decrypt      the second half to produce the original secret.      """      salt = token[:CSRF_SECRET_LENGTH]      token = token[CSRF_SECRET_LENGTH:]      chars = CSRF_ALLOWED_CHARS      pairs = zip((chars.index(x) for x in token), (chars.index(x) for x in salt))      secret = ''.join(chars[x - y] for x, y in pairs)  # Note negative values are ok      return secret

  

    token字元串的前32位是salt, 後面是加密後的token, 通過salt能解密出唯一的secret。
    django會驗證表單中的token和cookie中token是否能解出同樣的secret,secret一樣則本次請求合法。
    同樣也不難解釋,為什麼ajax請求時,需要從cookie中拿取token添加到請求頭中。

    Cookies Hashing:每一個表單請求中都加入隨機的Cookie,由於網站中存在XSS漏洞而被偷竊的危險。      HTTP refer:可以對伺服器獲得的請求來路進行欺騙以使得他們看起來合法,這種方法不能夠有效防止攻擊。      驗證碼:用戶提交的每一個表單中使用一個隨機驗證碼,讓用戶在文本框中填寫圖片上的隨機字元串,並且在提交表單後對其進行檢測。      令牌Token:一次性令牌在完成他們的工作後將被銷毀,比較安全。      ...等等吧,還有很多其他的。

    

$.ajax({    url: "/cookie_ajax/",    type: "POST",    headers: {"X-CSRFToken": $.cookie('csrftoken')},  // 從Cookie取csrftoken,並設置到請求頭中    data: {"username": "chao", "password": 123456},    success: function (data) {      console.log(data);    }  })  

    或者用自己寫一個getCookie方法:

function getCookie(name) {      var cookieValue = null;      if (document.cookie && document.cookie !== '') {          var cookies = document.cookie.split(';');          for (var i = 0; i < cookies.length; i++) {              var cookie = jQuery.trim(cookies[i]);              // Does this cookie string begin with the name we want?              if (cookie.substring(0, name.length + 1) === (name + '=')) {                  cookieValue = decodeURIComponent(cookie.substring(name.length + 1));                  break;              }          }      }      return cookieValue;  }  var csrftoken = getCookie('csrftoken');  

    每一次都這麼寫太麻煩了,可以使用$.ajaxSetup()方法為ajax請求統一設置。

function csrfSafeMethod(method) {    // these HTTP methods do not require CSRF protection    return (/^(GET|HEAD|OPTIONS|TRACE)$/.test(method));  }    $.ajaxSetup({    beforeSend: function (xhr, settings) {      if (!csrfSafeMethod(settings.type) && !this.crossDomain) {        xhr.setRequestHeader("X-CSRFToken", csrftoken);      }    }  });  

    注意:

      如果使用從cookie中取csrftoken的方式,需要確保cookie存在csrftoken值。

      如果你的視圖渲染的HTML文件中沒有包含 {% csrf_token %},Django可能不會設置CSRFtoken的cookie。

      這個時候需要使用ensure_csrf_cookie()裝飾器強制設置Cookie。

django.views.decorators.csrf import ensure_csrf_cookie      @ensure_csrf_cookie  def login(request):      pass  

    更多細節詳見:Djagno官方文檔中關於CSRF的內容

四 Ajax文件上傳

請求頭ContentType

    ContentType指的是請求體的編碼類型,常見的類型共有3種:

1 application/x-www-form-urlencoded(看下圖)

      這應該是最常見的 POST 提交數據的方式了。瀏覽器的原生

表單,如果不設置 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來查看我們發送的請求體:

        img

      點擊一下上面紅框的內容,你就會看到,這次post請求發送數據的原始格式

        img

2 multipart/form-data

      這又是一個常見的 POST 數據提交的方式。我們使用表單上傳文件時,必須讓

表單的 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-- 標示結束。關於 multipart/form-data 的詳細定義,請前往 rfc1867 查看。

      這種方式一般用來上傳文件,各大服務端語言對它也有著良好的支援。

      上面提到的這兩種 POST 數據的方式,都是瀏覽器原生支援的,而且現階段標準中原生

表單也只支援這兩種方式(通過

元素的 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 方式提交。

       img

    如果在ajax裡面寫上這個contenttype類型,那麼data參數對應的數據,就不能是個object類型數據了,必須是json字元串,contenttype:’json’,簡寫一個json,它也能識別是application/json類型

        img

        服務端接受到數據之後,通過contenttype類型的值來使用不同的方法解析數據,其實就是服務端框架已經寫好了針對這幾個類型的不同的解析數據的方法,通過contenttype值來找對應方法解析,如果有一天你寫了一個contenttype類型,定義了一個消息格式,各大語言及框架都支援,那麼別人也會寫一個針對你的contenttype值來解析數據的方法,django裡面不能幫我們解析contenttype值為json的數據格式,你知道他能幫你解析application/x-www-form-urlencoded 和multipart/form-data(文件上傳會用到)就行了,如果我們傳json類型的話,需要我們自己來寫一個解析數據的方法,其實不管是什麼類型,我們都可以通過原始發送來的數據來進行加工處理,解析出自己想要的數據,這個事情我們在前面自己寫web框架的時候在獲取路徑那裡就玩過了,還記得嗎?

          img

  $.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表單的文件上傳

模板部分

<form action="" method="post" enctype="multipart/form-data"> #上面說的其他兩種contenttype都是鍵值的形式發送數據,這種form_data的格式一般是把大數據一段一段隔開的        用戶名 <input type="text" name="user">        頭像 <input type="file" name="avatar">  #如果不用form_data格式來發,那麼默認的是urlencoded的格式,這個標籤的數據會組成avatar:文件名字來進行發送      <input type="submit">  </form>  

視圖部分

def index(request):      print(request.body)   # 原始的請求體數據      print(request.GET)    # GET請求數據      print(request.POST)   # POST請求數據      print(request.FILES)  # 上傳的文件數據          return render(request,"index.html")  

  upload.py,內容如下:

def upload(request):        if request.method == 'GET':            return render(request,'upload.html')      else:          print(request.POST)          username = request.POST.get('user')          file_obj = request.FILES.get('file_obj') #獲得文件數據對象          print('>>>',file_obj,type(file_obj))          #>>> jaden部落格.txt <class 'django.core.files.uploadedfile.InMemoryUploadedFile'>,一個文件對象,可以理解為一個文件句柄          file_name = file_obj.name #jaden部落格.txt          print(file_name)          # 將數據寫到文件裡面,需要名字,需要數據          with open(file_name,'wb') as f: #直接把文件名字放這裡,那麼文件將直接生成在django的整個項目目錄下,因為django配置的系統搜索的根路徑就是咱們的項目文件夾路徑,那個BASE_DIR,一般我們需要自己建立一個文件夾專門存放上傳的文件      #所以需要我們自己來拼接一個路徑放到這裡,os.path.join(settings.BASE_DIR,'media','img',file_name)              # f.write()  #不能一下寫進去,佔用的內容太多,要一點一點寫              for data in file_obj: #讀數據                  f.write(data)  #每次讀取的data不是固定長度的,和讀取其他文件一樣,每次讀一行,識別符為r  n  rn,遇到這幾個符號就算是讀了一行         #for chunks in file_obj.chunks(): #chunks()默認一次返回大小為經測試為65536B,也就是64KB,最大為2.5M,是一個生成器         #  f.write(chunks)  

    通過js來找文件對象

      img

基於Ajax的文件上傳

模板

<form> #用不用form沒關係,這裡就是個盒子的作用,一般寫form標籤是為了提示別人,這個地方的內容是要提交的        {% csrf_token %}      用戶名 <input type="text" id="user">        頭像 <input type="file" id="avatar">       <input type="button" id="ajax-submit" value="ajax-submit">  </form>    <script>        $("#ajax-submit").click(function(){          var formdata=new FormData(); #ajax上傳文件的時候,需要這個類型,它會將添加給它的鍵值對加工成formdata的類型          formdata.append("user",$("#user").val());  #添加鍵值的方法是append,注意寫法,鍵和值之間是逗號       formData.append("csrfmiddlewaretoken", $("[name='csrfmiddlewaretoken']").val()); #別忘了csrf_token          formdata.append("avatar_img",$("#avatar")[0].files[0]);          $.ajax({                url:"",              type:"post",              data:formdata, #將添加好數據的formdata放到data這裡              processData: false ,    // 不處理數據              contentType: false,    // 不設置內容類型                success:function(data){                  console.log(data)              }          })        })    </script>  

      或者使用

var form = document.getElementById("form1");  var fd = new FormData(form);  

      這樣也可以直接通過ajax 的 send() 方法將 fd 發送到後台。

      注意:由於 FormData 是 XMLHttpRequest Level 2 新增的介面,現在 低於IE10 的IE瀏覽器不支援 FormData。

視圖

def index(request):        if request.is_ajax():            print(request.body)   # 原始的請求體數據          print(request.GET)    # GET請求數據          print(request.POST)   # POST請求數據          print(request.FILES)  # 上傳的文件數據            return HttpResponse("ok")          return render(request,"index.html")  

  檢查瀏覽器的請求頭:

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。  

練習(用戶名是否已被註冊)

功能介紹

      在註冊表單中,當用戶填寫了用戶名後,把游標移開後,會自動向伺服器發送非同步請求。伺服器返回這個用戶名是否已經被註冊過。

案例分析

  • 頁面中給出註冊表單;
  • 在username input標籤中綁定onblur事件處理函數。
  • 當input標籤失去焦點後獲取 username表單欄位的值,向服務端發送AJAX請求;
  • django的視圖函數中處理該請求,獲取username值,判斷該用戶在資料庫中是否被註冊,如果被註冊了就返回「該用戶已被註冊」,否則響應「該用戶名可以註冊」。
def index(request):        if request.method == 'POST':          print(request.POST)          print('files',request.FILES)          # book_objs = models.Book.objects.all()          # ret = serializers.serialize('json', book_objs,cls=JsonCustomEncoder) #同樣無法序列化時間          # ret = serializers.serialize('json', book_objs,cls=JsonCustomEncoder) #同樣無法序列化時間,無法和json一樣引入cls=JsonCustomEncoder這個類來解決日期時間格式數據的序列化          #得到的結果[{"model": "app01.book", "pk": 1, "fields": {"name": "python", "date": null, "img": ""}}, {"model": "app01.book", "pk": 2, "fields": {"name": "linux", "date": null, "img": ""}}]          #得到的結果的格式也是比較繁瑣,所以不推薦使用django自帶的serializers序列化器來序列化我們的queryset類型的數據          #推薦寫法:          ret = models.Book.objects.all().values()          print(ret)          # print(request.body)          import json          ret = json.dumps(list(ret),cls=JsonCustomEncoder)          print(ret)          print(type(ret))          # return HttpResponse(ret,content_type='applicationjson')          return JsonResponse(ret,safe=False)  

五 關於json

1.什麼是json

  • JSON 指的是 JavaScript 對象表示法(JavaScript Object Notation)
  • JSON 是輕量級的文本數據交換格式
  • JSON 獨立於語言 *
  • JSON 具有自我描述性,更易理解

  • JSON 使用 JavaScript 語法來描述數據對象,但是 JSON 仍然獨立於語言和平台。JSON 解析器和 JSON 庫支援許多不同的程式語言。

    啥都別多說了,上圖吧!

img

      json數據類型和python數據類型的對比:

      img

       object和python的dict類型是差不多的,但是要求裡面必須是雙引號,string和list、tuple等也是一樣的,都是雙引號。python中的datetime等時間日期類型是不能進行json序列化的,因為json沒有對應的格式,上面的這幾種數據類型雖然進行json.dumps序列化之後都是個字元串,但是也是有格式的

        img

        

    前端ajax拿到後端返回的一個python的json模組序列化之後的一個json字元串,那麼js通過自己的json介面,將接受到的json字元串來反序列化為js自己語言能夠識別的數據類型,然後再進行操作。      

    相當於我有一個json方法,你有一個json方法,你給我發數據必須是json字元串的格式,那麼你就需要將你的數據類型序列化為json的字元串,那麼序列化的時候,就把你的數據序列化為了符合json標準的字元串,然後我接收到這個字元串之後,我通過我的json方法,將數據轉換為我的語言支援的數據類型。在進行反序列化的時候,如果你的字元串不符合json的格式,那麼反序列化的時候就會報錯,所以只要你是通過json序列化成的字元串,都是能夠json反序列化的,因為json序列化的時候,就把你的數據改為了符合json標準的字元串形式,例如:裡面的單引號,序列化後變成了雙引號。

    合格的json對象:

["one", "two", "three"]  { "one": 1, "two": 2, "three": 3 } #這就是一個json的object類型,符合json的標準格式,就可以通過dumps來進行序列化  {"names": ["張三", "李四"] }  [ { "name": "張三"}, {"name": "李四"} ]   

    不合格的json對象:

{ name: "張三", 'age': 32 }  // 屬性名必須使用雙引號  [32, 64, 128, 0xFFF] // 不能使用十六進位值  { "name": "張三", "age": undefined }  // 不能使用undefined  { "name": "張三",    "birthday": new Date('Fri, 26 Aug 2011 07:13:10 GMT'),    "getName":  function() {return this.name;}  // 不能使用函數和日期對象  }  

    看一下普通字元串和json字元串,在進行序列化的時候的區別

import json  # s = "{'name':'chao','age':18}" #普通字元串,每加引號的沒問題,加了引號的,必須是雙引號才能使用json.loads()。  s = '{"name":"chao","age":18}'   #json字元串,裡面必須是雙引號  ret = json.loads(s)  print(ret)  print(ret['name'])  

2.js的stringify與parse方法

    JavaScript中關於JSON對象和字元串轉換的兩個方法:

    JSON.parse(): 用於將一個 JSON 字元串轉換為 JavaScript 對象 

JSON.parse('{"name":"chao"}');  JSON.parse('{name:"chao"}') ;   // 錯誤  JSON.parse('[18,undefined]') ;   // 錯誤  

    JSON.stringify(): 用於將 JavaScript 值轉換為 JSON 字元串。 

JSON.stringify({"name":"chao"})  

3.和XML的比較

    JSON 格式於2001年由 Douglas Crockford 提出,目的就是取代繁瑣笨重的 XML 格式。

    JSON 格式有兩個顯著的優點:書寫簡單,一目了然;符合 JavaScript 原生語法,可以由解釋引擎直接處理,不用另外添加解析程式碼。所以,JSON迅速被接受,已經成為各大網站交換數據的標準格式,並被寫入ECMAScript 5,成為標準的一部分。

    XML和JSON都使用結構化方法來標記數據,下面來做一個簡單的比較。

用XML表示中國部分省市數據如下:

<?xml version="1.0" encoding="utf-8"?>  <country>      <name>中國</name>      <province>          <name>黑龍江</name>          <cities>              <city>哈爾濱</city>              <city>大慶</city>          </cities>      </province>      <province>          <name>廣東</name>          <cities>              <city>廣州</city>              <city>深圳</city>              <city>珠海</city>          </cities>      </province>      <province>          <name>台灣</name>          <cities>              <city>台北</city>              <city>高雄</city>          </cities>      </province>      <province>          <name>新疆</name>          <cities>              <city>烏魯木齊</city>          </cities>      </province>  </country>  

    用JSON表示如下:

{      "name": "中國",      "province": [{          "name": "黑龍江",          "cities": {              "city": ["哈爾濱", "大慶"]          }      }, {          "name": "廣東",          "cities": {              "city": ["廣州", "深圳", "珠海"]          }      }, {          "name": "台灣",          "cities": {              "city": ["台北", "高雄"]          }      }, {          "name": "新疆",          "cities": {              "city": ["烏魯木齊"]          }      }]  }  

    

    由上面的兩端程式碼可以看出,JSON 簡單的語法格式和清晰的層次結構明顯要比 XML 容易閱讀,並且在數據交換方面,由於 JSON 所使用的字元要比 XML 少得多,可以大大得節約傳輸數據所佔用得頻寬。

4.ajax和服務端的數據交互時的序列化問題

  當我們給ajax回復的不是一個字元串,而是其他數據類型的時候,需要我們將數據轉換為json字元串進行發送,這樣好配合js進行json字元串的處理,不然發送或者接受的是普通字元串的話,沒辦法處理成原來的數據類型。

  這就用到了我們前面的視圖函數中那個JsonResponse了,看部落格,裡面response的部分

  還要注意ajax中的data參數:

    data參數中的鍵值對,如果值值不為字元串,需要將其轉換成字元串類型。

$("#b1").on("click", function () {      $.ajax({        url:"/ajax_add/",        type:"GET",        data:{"i1":$("#i1").val(),"i2":$("#i2").val(),"hehe": JSON.stringify([1, 2, 3])},        success:function (data) {          $("#i3").val(data);        }      })    })  

5.Django內置的serializers做序列化

    看程式碼:

def books_json(request):      book_list = models.Book.objects.all()[0:10]      from django.core import serializers      ret = serializers.serialize("json", book_list)      return HttpResponse(ret)  

  通過json學列化時間日期格式數據的時候需要注意,不能直接序列化,我寫了一個類,可以借用:

  

import json  from datetime import datetime  from datetime import date    #對含有日期格式數據的json數據進行轉換  class JsonCustomEncoder(json.JSONEncoder):      def default(self, field):          if isinstance(field,datetime):              return field.strftime('%Y-%m-%d %H:%M:%S')          elif isinstance(field,date):              return field.strftime('%Y-%m-%d')          else:              return json.JSONEncoder.default(self,field)      d1 = datetime.now()    dd = json.dumps(d1,cls=JsonCustomEncoder)  print(dd)  

六 補充一個SweetAlert插件示例

看效果:

    img

  點擊下載Bootstrap-sweetalert項目

$(".btn-danger").on("click", function () {    swal({      title: "你確定要刪除嗎?",      text: "刪除可就找不回來了哦!",      type: "warning",      showCancelButton: true,      confirmButtonClass: "btn-danger",      confirmButtonText: "刪除",      cancelButtonText: "取消",      closeOnConfirm: false      },      function () {        var deleteId = $(this).parent().parent().attr("data_id");        $.ajax({          url: "/delete_book/",          type: "post",          data: {"id": deleteId},          success: function (data) {            if (data.status === 1) {              swal("刪除成功!", "你可以準備跑路了!", "success");            } else {              swal("刪除失敗", "你可以再嘗試一下!", "error")            }          }        })      });  })  

  項目中簡單應用:

{% load static %}  <!DOCTYPE html>  <html lang="en">  <head>      <meta charset="UTF-8">      <title>Title</title>      <link rel="stylesheet" href="{% static 'bootstrap-3.3.0-dist/dist/css/bootstrap.min.css' %}">      <link rel="stylesheet" href="{% static 'bootstrap-sweetalert-master/dist/sweetalert.css' %}">  </head>  <body>    <div>      <button class="btn btn-danger">刪除</button>  </div>    </body>  <script src="{% static 'bootstrap-3.3.0-dist/dist/jQuery/jquery-3.1.1.js' %}"></script>  <script src="{% static 'bootstrap-3.3.0-dist/dist/js/bootstrap.min.js' %}"></script>  <script src="{% static 'bootstrap-sweetalert-master/dist/sweetalert.js' %}"></script>  <script>        $(".btn-danger").on("click", function () {          swal({                  title: "你確定要刪除嗎?",                  text: "刪除可就找不回來了哦!",                  type: "warning",                  showCancelButton: true,                  confirmButtonClass: "btn-danger",                  confirmButtonText: "我已經下定決心",                  cancelButtonText: "容我三思",                  closeOnConfirm: false              },              function () {                  var deleteId = $(this).parent().parent().attr("data_id");                  $.ajax({                      url: "/delete_book/",                      type: "post",                      data: {"id": deleteId},                      success: function (data) {                          console.log(data,typeof data);                          if (data === '1') {                              swal("刪除成功!", "你可以準備跑路了!", "success");                          } else {                              swal("刪除失敗", "你可以再嘗試一下!", "error")                          }                      }                  })              });      })  </script>  </html>  

複製程式碼

urls.py

url(r'^delete_book/', views.delete_book,name='delete_book'),  

views.py

def delete_book(request):        import random      ret = str(random.randint(1,2))      return HttpResponse(ret)  

七 同源策略與Jsonp

同源策略

    同源策略(Same origin policy)是一種約定,它是瀏覽器最核心也最基本的安全功能,如果缺少了同源策略,則瀏覽器的正常功能可能都會受到影響。可以說Web是構建在同源策略基礎之上的,瀏覽器只是針對同源策略的一種實現。

    同源策略,它是由Netscape提出的一個著名的安全策略。現在所有支援JavaScript 的瀏覽器都會使用這個策略。所謂同源是指,域名,協議,埠相同。當一個瀏覽器的兩個tab頁中分別打開來 百度和Google的頁面當瀏覽器的百度tab頁執行一個腳本的時候會檢查這個腳本是屬於哪個頁面的,即檢查是否同源,只有和百度同源的腳本才會被執行。如果非同源,那麼在請求數據時,瀏覽器會在控制台中報一個異常,提示拒絕訪問。

示例項目1:  

        

==================================http://127.0.0.1:8001項目的index  <!DOCTYPE html>  <html lang="en">  <head>      <meta charset="UTF-8">      <title>Title</title>      <script src="http://code.jquery.com/jquery-latest.js"></script>  </head>  <body>      <button>ajax</button>  {% csrf_token %}    <script>      $("button").click(function(){              $.ajax({              url:"http://127.0.0.1:7766/SendAjax/",              type:"POST",              data:{"username":"yuan","csrfmiddlewaretoken":$("[name='csrfmiddlewaretoken']").val()},              success:function(data){                  alert(123);                  alert(data)              }          })      })  </script>  </body>  </html>      ==================================http://127.0.0.1:8001項目的views    def index(request):          return render(request,"index.html")      def ajax(request):      import json      print(request.POST,"+++++++++++")      return HttpResponse(json.dumps("hello"))  

示例項目2:

==================================http://127.0.0.1:8002項目的index  <!DOCTYPE html>  <html lang="en">  <head>      <meta charset="UTF-8">      <title>Title</title>      <script src="http://code.jquery.com/jquery-latest.js"></script>  </head>  <body>      <button>sendAjax</button>    {% csrf_token %}    <script>      $("button").click(function(){              $.ajax({              url:"/SendAjax/",              type:"POST",              data:{"username":"yuan","csrfmiddlewaretoken":$("[name='csrfmiddlewaretoken']").val()},              success:function(data){                  alert(data)              }          })      })  </script>    </body>  </html>      ==================================http://127.0.0.1:8002項目的views    def index(request):        return render(request,"index.html")    from django.views.decorators.csrf import csrf_exempt    @csrf_exempt      def SendAjax(request):        import json        print("++++++++")        return HttpResponse(json.dumps("hello2"))  

      當點擊項目1的按鈕時,發送了請求,但是會發現報錯如下:

已攔截跨源請求:同源策略禁止讀取位於 http://127.0.0.1:7766/SendAjax/ 的遠程資源。(原因:CORS 頭缺少 'Access-Control-Allow-Origin')。  

      但是注意,項目2中的訪問已經發生了,說明是瀏覽器對非同源請求返回的結果做了攔截。

Jsonp

    jsonp是json用來跨域的一個東西。原理是通過script標籤的跨域特性來繞過同源策略。

    思考:這算怎麼回事?

<script src="http://code.jquery.com/jquery-latest.js"></script>  

    藉助script標籤,實現跨域請求,示例:

# =============================http://127.0.0.1:8001/index      <button>ajax</button>  {% csrf_token %}    <script>      function func(name){          alert(name)      }  </script>    <script src="http://127.0.0.1:7766/SendAjax/"></script>      # =============================http://127.0.0.1:8002/  from django.views.decorators.csrf import csrf_exempt    @csrf_exempt      def SendAjax(request):        import json        print("++++++++")      # dic={"k1":"v1"}      return HttpResponse("func('yuan')")  # return HttpResponse("func('%s')"%json.dumps(dic))  

    這其實就是JSONP的簡單實現模式,或者說是JSONP的原型:創建一個回調函數,然後在遠程服務上調用這個函數並且將JSON 數據形式作為參數傳遞,完成回調。

    將JSON數據填充進回調函數,這就是JSONP的JSON+Padding的含義。

​   一般情況下,我們希望這個script標籤能夠動態的調用,而不是像上面因為固定在html裡面所以沒等頁面顯示就執行了,很不靈活。我們可以通過javascript動態的創建script標籤,這樣我們就可以靈活調用遠程服務了。

<button onclick="f()">sendAjax</button>    <script>      function addScriptTag(src){           var script = document.createElement('script');           script.setAttribute("type","text/javascript");           script.src = src;           document.body.appendChild(script);           document.body.removeChild(script);      }          function func(name){          alert("hello"+name)      }        function f(){           addScriptTag("http://127.0.0.1:7766/SendAjax/")      }  </script>  

    為了更加靈活,現在將你自己在客戶端定義的回調函數的函數名傳送給服務端,服務端則會返回以你定義的回調函數名的方法,將獲取的json數據傳入這個方法完成回調:

    將8001的f()改寫為:

function f(){           addScriptTag("http://127.0.0.1:7766/SendAjax/?callbacks=func")      }  

    8002的views改為:

def SendAjax(request):        import json        dic={"k1":"v1"}        print("callbacks:",request.GET.get("callbacks"))      callbacks=request.GET.get("callbacks")        return HttpResponse("%s('%s')"%(callbacks,json.dumps(dic)))  

jQuery對JSONP的實現

getJSON

      jQuery框架也當然支援JSONP,可以使用$.getJSON(url,[data],[callback])方法

      8001的html改為:

<button onclick="f()">sendAjax</button>    <script>        function f(){            $.getJSON("http://127.0.0.1:7766/SendAjax/?callbacks=?",function(arg){              alert("hello"+arg)          });      }    </script>  

      8002的views不改動。

      結果是一樣的,要注意的是在url的後面必須添加一個callback參數,這樣getJSON方法才會知道是用JSONP方式去訪問服務,callback後面的那個問號是內部自動生成的一個回調函數名。

​     此外,如果說我們想指定自己的回調函數名,或者說服務上規定了固定回調函數名該怎麼辦呢?我們可以使用$.ajax方法來實現

$.ajax

      8001的html改為:

<script>        function f(){            $.ajax({                  url:"http://127.0.0.1:7766/SendAjax/",                  dataType:"jsonp",                  jsonp: 'callbacks',                  jsonpCallback:"SayHi"             });           }        function SayHi(arg){                  alert(arg);              }    </script>  

      8002的views不改動。

      當然,最簡單的形式還是通過回調函數來處理:

<script>        function f(){                $.ajax({                 url:"http://127.0.0.1:7766/SendAjax/",                 dataType:"jsonp",            //必須有,告訴server,這次訪問要的是一個jsonp的結果。                 jsonp: 'callbacks',          //jQuery幫助隨機生成的:callbacks="wner"                 success:function(data){                     alert("hi "+data)                }           });           }    </script>  

      jsonp: ‘callbacks’就是定義一個存放回調函數的鍵,jsonpCallback是前端定義好的回調函數方法名’SayHi’,server端接受callback鍵對應值後就可以在其中填充數據打包返回了;

      jsonpCallback參數可以不定義,jquery會自動定義一個隨機名發過去,那前端就得用回調函數來處理對應數據了。利用jQuery可以很方便的實現JSONP來進行跨域訪問。  

      注意 JSONP一定是GET請求

應用

<input type="button" onclick="AjaxRequest()" value="跨域Ajax" />      <div id="container"></div>          <script type="text/javascript">          function AjaxRequest() {              $.ajax({                  url: 'http://www.jxntv.cn/data/jmd-jxtv2.html?callback=list&_=1454376870403',                  type: 'GET',                  dataType: 'jsonp',                  jsonp: 'callback',                  jsonpCallback: 'list',                  success: function (data) {                        $.each(data.data,function(i){                          var item = data.data[i];                          var str = "<p>"+ item.week +"</p>";                          $('#container').append(str);                          $.each(item.list,function(j){                              var temp = "<a href='" + item.list[j].link +"'>" + item.list[j].name +" </a><br/>";                              $('#container').append(temp);                          });                          $('#container').append("<hr/>");                      })                    }              });          }  </script>  

八 CORS

  

一、簡介

    CORS需要瀏覽器和伺服器同時支援。目前,所有瀏覽器都支援該功能,IE瀏覽器不能低於IE10。

    整個CORS通訊過程,都是瀏覽器自動完成,不需要用戶參與。對於開發者來說,CORS通訊與同源的AJAX通訊沒有差別,程式碼完全一樣。瀏覽器一旦發現AJAX請求跨源,就會自動添加一些附加的頭資訊,有時還會多出一次附加的請求,但用戶不會有感覺。

    因此,實現CORS通訊的關鍵是伺服器。只要伺服器實現了CORS介面,就可以跨源通訊。

二、兩種請求

    瀏覽器將CORS請求分成兩類:簡單請求(simple request)和非簡單請求(not-so-simple request)。

    只要同時滿足以下兩大條件,就屬於簡單請求。

(1) 請求方法是以下三種方法之一:  HEAD  GET  POST  (2)HTTP的頭資訊不超出以下幾種欄位:  Accept  Accept-Language  Content-Language  Last-Event-ID  Content-Type:只限於三個值application/x-www-form-urlencoded、multipart/form-data、text/plain  

    凡是不同時滿足上面兩個條件,就屬於非簡單請求。

    瀏覽器對這兩種請求的處理,是不一樣的。

* 簡單請求和非簡單請求的區別?       簡單請求:一次請求     非簡單請求:兩次請求,在發送數據之前會先發一次請求用於做「預檢」,只有「預檢」通過後才再發送一次請求用於數據傳輸。  * 關於「預檢」    - 請求方式:OPTIONS  - 「預檢」其實做檢查,檢查如果通過則允許傳輸數據,檢查不通過則不再發送真正想要發送的消息  - 如何「預檢」       => 如果複雜請求是PUT等請求,則服務端需要設置允許某請求,否則「預檢」不通過          Access-Control-Request-Method       => 如果複雜請求設置了請求頭,則服務端需要設置允許某請求頭,否則「預檢」不通過          Access-Control-Request-Headers  

  支援跨域,簡單請求

    伺服器設置響應頭:Access-Control-Allow-Origin = ‘域名’ 或 ‘*’

  支援跨域,複雜請求

    由於複雜請求時,首先會發送「預檢」請求,如果「預檢」成功,則發送真實數據。

  • 「預檢」請求時,允許請求方式則需伺服器設置響應頭:Access-Control-Request-Method
  • 「預檢」請求時,允許請求頭則需伺服器設置響應頭:Access-Control-Request-Headers