01.Django基礎一之web框架的本質

  • 2019 年 10 月 10 日
  • 筆記

一 web框架的本質及自定義web框架

我們可以這樣理解:所有的Web應用本質上就是一個socket服務端,而用戶的瀏覽器就是一個socket客戶端,基於請求做出響應,客戶都先請求,服務端做出對應的響應,按照http協議的請求協議發送請求,服務端按照http協議的響應協議來響應請求,這樣的網絡通信,我們就可以自己實現Web框架了。

通過對socket的學習,我們知道網絡通信,我們完全可以自己寫了,因為socket就是做網絡通信用的,下面我們就基於socket來自己實現一個web框架,寫一個web服務端,讓瀏覽器來請求,並通過自己的服務端把頁面返回給瀏覽器,瀏覽器渲染出我們想要的效果。在後面的學習中,大家提前準備一些文件:

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

<!DOCTYPE html>  <html lang="en">  <head>      <meta charset="UTF-8">      <title>Title</title>      <link rel="stylesheet" href="test.css">      <link rel="icon" href="wechat.ico">      <!--直接寫在html頁面裏面的css樣式是直接可以在瀏覽器上顯示的-->      <!--<style>-->          <!--h1{-->              <!--background-color: green;-->              <!--color: white;-->          <!--}-->      <!--</style>-->  </head>  <body>    <h1>姑娘,你好,我是Jaden,請問約嗎?嘻嘻~~</h1>  <!--直接寫在html頁面裏面的img標籤的src屬性值如果是別人網站的地址(網絡地址)是直接可以在瀏覽器上顯示的-->  <!--<img src="https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1550395461724&di=c2b971db12eef5d85aba410d1e2e8568&imgtype=0&src=http%3A%2F%2Fy0.ifengimg.com%2Fifengimcp%2Fpic%2F20140822%2Fd69e0188b714ee789e97_size87_w800_h1227.jpg" alt="">--> <!--如果都是網絡地址,那麼只要你的電腦有網,就可以看到,不需要自己在後端寫對應的讀取文件,返回圖片文件信息的代碼,因為別人的網站就做了這個事情了-->  <img src="meinv.png" alt="" width="100" height="100"> <!--如果你是本地的圖片想要返回給頁面,你需要對頁面上的關於這個圖片的請求要自己做出響應,這個src就是來你本地請求這個圖片,你只要將圖片信息讀取出來,返回給頁面,頁面拿到這個圖片的數據,就能夠渲染出來了,是不是很簡單-->    <!--直接寫在html頁面裏面的js操作是直接可以在瀏覽器上顯示的-->  <!--<script>-->      <!--alert('這是我們第一個網頁')-->  <!--</script>-->    <script src="test.js"></script>  </body>  </html>

    css文件內容如下,名稱為test.css: 

h1{      background-color: green;      color: white;  }

    js文件內容如下,名稱為test.js:

alert('這是我們第一個網頁');

再準備一個圖片,名稱為meinv.jpg,再準備一個ico文件,名稱為wechat.ico,其實就是個圖片文件,微信官網打開之後,在瀏覽器最上面能夠看到

,把它保存下來

  上面的文件都準備好之後,你用pycharm新建一個項目,把文件都放到一個文件夾裏面去,留着備用,像下面這個樣子:

  然後開始寫我們的web框架,我們分這麼幾步來寫:

一、簡單的web框架

創建一個python文件,內容如下,名稱為test.py:

import socket  sk = socket.socket()  sk.bind(('127.0.0.1',8001))  sk.listen()  conn,addr = sk.accept()  from_b_msg = conn.recv(1024)  str_msg = from_b_msg.decode('utf-8')  #socket是應用層和傳輸層之間的抽象層,每次都有協議,協議就是消息格式,那麼傳輸層的消息格式我們不用管,因為socket幫我們搞定了,但是應用層的協議還是需要咱們自己遵守的,所以再給瀏覽器發送消息的時候,如果沒有按照應用層的消息格式來寫,那麼你返回給瀏覽器的信息,瀏覽器是沒法識別的。而應用層的協議就是我們的HTTP協議,所以我們按照HTTP協議規定的消息格式來給瀏覽器返回消息就沒有問題了,關於HTTP我們會細說,首先看一下直接寫conn.send(b'hello')的效果,然後運行代碼,通過瀏覽器來訪問一下,然後再看這一句conn.send(b'HTTP/1.1 200 ok rnrnhello')的效果  #下面這句就是按照http協議來寫的  # conn.send(b'HTTP/1.1 200 ok rnrnhello')  #上面這句還可以分成下面兩句來寫  conn.send(b'HTTP/1.1 200 ok rnrn')  conn.send(b'hello')

    我們來瀏覽器上看一下瀏覽器發送的請求:

    目前我們還沒有寫如何返回一個html文件給瀏覽器,所以這裡暫時不用管它,那麼我們點開這個127.0.0.1看看:

    我們在python文件中打印一下瀏覽器發送過來的請求信息是啥:

    重啟我們的代碼,然後在網址中輸入這個:

    再重啟我們的代碼,然後在網址中輸入這個:

    瀏覽器發過來一堆的消息,我們給瀏覽器回復(響應)信息的時候,也要按照一個消息格式來寫,這些都是http協議規定的,那麼我們就來學習一下http協議,然後繼續完善我們的web框架:

     HTTP協議:https://www.cnblogs.com/changxin7/p/11570281.html

二、返回HTML文件的web框架

    首先寫一個html文件,內容如下,名稱為test.html:

<!DOCTYPE html>  <html lang="en">  <head>      <meta charset="UTF-8">      <title>Title</title><link rel="stylesheet" href="test.css">        <!--直接寫在html頁面裏面的css樣式是直接可以在瀏覽器上顯示的-->      <style>          h1{              background-color: green;              color: white;          }      </style>  </head>  <body>    <h1>姑娘,你好,我是Jaden,請問約嗎?嘻嘻~~</h1>  <!--直接寫在html頁面裏面的img標籤的src屬性值如果是別人網站的地址(網絡地址)是直接可以在瀏覽器上顯示的-->  <img src="https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1550395461724&di=c2b971db12eef5d85aba410d1e2e8568&imgtype=0&src=http%3A%2F%2Fy0.ifengimg.com%2Fifengimcp%2Fpic%2F20140822%2Fd69e0188b714ee789e97_size87_w800_h1227.jpg" alt="">  <!--如果都是網絡地址,那麼只要你的電腦有網,就可以看到,不需要自己在後端寫對應的讀取文件,返回圖片文件信息的代碼,因為別人的網站就做了這個事情了-->    <!--直接寫在html頁面裏面的js操作是直接可以在瀏覽器上顯示的-->  <script>      alert('這是我們第一個網頁')  </script>    </body>  </html>

    準備我們的python代碼,服務端程序,文件內容如下,文件名稱為test.py:       

import socket  sk = socket.socket()  sk.bind(('127.0.0.1',8001))  sk.listen()  conn,addr = sk.accept()  from_b_msg = conn.recv(1024)  str_msg = from_b_msg.decode('utf-8')  print('瀏覽器請求信息:',str_msg)    # conn.send(b'HTTP/1.1 200 ok rncontent-type:text/html;charset=utf-8;rn')  conn.send(b'HTTP/1.1 200 ok rnrn')    with open('test1.html','rb') as f:      f_data = f.read()  conn.send(f_data)

    頁面上輸入網址看效果,css和js代碼的效果也有,very good:

  但是我們知道,我們的css和js基本都是寫在本地的文件裏面的啊,而且我們的圖片基本也是我們自己本地的啊,怎麼辦,我們將上面我們提前準備好的js和css還有那個.ico結尾的圖片文件都準備好,來我們在來一個升級版的web框架,其實css、js、圖片等文件都叫做網站的靜態文件。

  首先我們先看一個效果,如果我們直接將我們寫好的css和js還有.ico和圖片文件插入到我們的html頁面裏面,就是下面這個html文件

    名稱為test.html,內容如下:    

<!DOCTYPE html>  <html lang="en">  <head>      <meta charset="UTF-8">      <title>Title</title>      <link rel="stylesheet" href="test.css">      <!--加上下面這句,那麼我們看瀏覽器調試窗口中的那個network裏面就沒有那個favicon.ico的請求了,其實這就是頁面title標籤文字左邊的那個頁面圖標,但是這個文件是我們自己本地的,所以我們需要在後端代碼裏面將這個文件數據讀取出來返回給前端-->      <link rel="icon" href="wechat.ico">      <!--直接寫在html頁面裏面的css樣式是直接可以在瀏覽器上顯示的-->      <!--<style>-->          <!--h1{-->              <!--background-color: green;-->              <!--color: white;-->          <!--}-->      <!--</style>-->  </head>  <body>    <h1>姑娘,你好,我是Jaden,請問約嗎?嘻嘻~~</h1>  <!--直接寫在html頁面裏面的img標籤的src屬性值如果是別人網站的地址(網絡地址)是直接可以在瀏覽器上顯示的-->  <!--<img src="https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1550395461724&di=c2b971db12eef5d85aba410d1e2e8568&imgtype=0&src=http%3A%2F%2Fy0.ifengimg.com%2Fifengimcp%2Fpic%2F20140822%2Fd69e0188b714ee789e97_size87_w800_h1227.jpg" alt="">--> <!--如果都是網絡地址,那麼只要你的電腦有網,就可以看到,不需要自己在後端寫對應的讀取文件,返回圖片文件信息的代碼,因為別人的網站就做了這個事情了-->  <img src="meinv.png" alt="" width="100" height="100"> <!--如果你是本地的圖片想要返回給頁面,你需要對頁面上的關於這個圖片的請求要自己做出響應,這個src就是來你本地請求這個圖片,你只要將圖片信息讀取出來,返回給頁面,頁面拿到這個圖片的數據,就能夠渲染出來了,是不是很簡單-->    <!--直接寫在html頁面裏面的js操作是直接可以在瀏覽器上顯示的-->  <!--<script>-->      <!--alert('這是我們第一個網頁')-->  <!--</script>-->    <script src="test.js"></script>  </body>  </html>

    同樣使用我們之前的python程序,來看效果:

    發現js和css的效果都沒有出來,並且我們看一下瀏覽器調試窗口的那個network

    在下來我們在network裏面點擊那個test.css文件,看看請求是什麼:

    還有就是當我們直接在瀏覽器上保存某個頁面的時候,隨便一個頁面,我們到頁面上點擊右鍵另存為,然後存到本地的一個目錄下,你會發現這個頁面的html、css、js、圖片等文件都跟着保存下來了,我保存了一下博客園首頁的頁面,看,是一個文件夾和一個html文件:

      我們點開博客園那個文件夾看看裏面都有什麼:

      發現js、css還有圖片什麼的都被保存了下來,說明什麼,說明這些文件本身就存在瀏覽器上了,哦,原來就是將html頁面需要的css、js、圖片等文件也發送給瀏覽器就可以了,並且這些靜態文件都是瀏覽器單獨過來請求的,其實和標籤的屬性有有關係,css文件是link標籤的href屬性:,js文件是script標籤的src屬性:,圖片文件是img標籤的src屬性:,那個.ico文件是link標籤的屬性:,其實這些屬性都會在頁面加載的時候,單獨到自己對應的屬性值裏面取請求對應的文件數據,而且我們如果在值裏面寫的都是自己本地的路徑,那麼都會來自己的本地路徑來找,如果我們寫的是相對路徑,就會到我們自己的網址+文件名稱,這個路徑來找它需要的文件,所以我們只需要將這些請求做一些響應,將對應的文件數據相應給瀏覽器就可以了!並且我們通過前面的查看,能夠發現,瀏覽器url的請求路徑我們知道是什麼,靜態文件不是也這樣請求的嗎,好,我們針對不同的路徑給它返回不同的文件,

非常好!我們來嘗試一下!

三、返回靜態文件的高級web框架

     還是用第二個web框架裏面的那個html文件,我們只需要寫一些我們的服務端程序就可以了,同樣是test.py文件,內容如下:   

import socket    sk = socket.socket()  sk.bind(('127.0.0.1',8001))  sk.listen()    #首先瀏覽器相當於給我們發送了多個請求,一個是請求我們的html文件,而我們的html文件裏面的引入文件的標籤又給我們這個網站發送了請求靜態文件的請求,所以我們要將建立連接的過程循環起來,才能接受多個請求,沒毛病  while 1:      conn,addr = sk.accept()      # while 1:      from_b_msg = conn.recv(1024)      str_msg = from_b_msg.decode('utf-8')      #通過http協議我們知道,瀏覽器請求的時候,有一個請求內容的路徑,通過對請求信息的分析,這個路徑我們在請求的所有請求信息中可以提煉出來,下面的path就是我們提煉出來的路徑      path = str_msg.split('rn')[0].split(' ')[1]      print('path>>>',path)      conn.send(b'HTTP/1.1 200 ok rnrn')      #由於整個頁面需要html、css、js、圖片等一系列的文件,所以我們都需要給人家瀏覽器發送過去,瀏覽器才能有這些文件,才能很好的渲染你的頁面      #根據不同的路徑來返迴響應的內容      if path == '/': #返回html文件          print(from_b_msg)          with open('test.html','rb') as f:          # with open('Python開發.html','rb') as f:              data = f.read()          conn.send(data)          conn.close()      elif path == '/meinv.png': #返回圖片          with open('meinv.png','rb') as f:              pic_data = f.read()          # conn.send(b'HTTP/1.1 200 ok rnrn')          conn.send(pic_data)          conn.close()      elif path == '/test.css': #返回css文件          with open('test.css','rb') as f:              css_data = f.read()          conn.send(css_data)          conn.close()        elif path == '/wechat.ico':#返回頁面的ico圖標          with open('wechat.ico','rb') as f:              ico_data = f.read()          conn.send(ico_data)          conn.close()        elif path == '/test.js': #返回js文件          with open('test.js','rb') as f:              js_data = f.read()          conn.send(js_data)          conn.close()        #注意:上面每一個請求處理完之後,都有一個conn.close()是因為,HTTP協議是短鏈接的,一次請求對應一次響應,這個請求就結束了,所以我們需要寫上close,不然瀏覽器自己斷了,你自己寫的服務端沒有斷,就會出問題。

    運行起來我們的py文件,然後在瀏覽器訪問一下我們的服務端,看效果:

    666666,完全搞定了,自己通過socket已經完全搞定了web項目,激動不,哈哈,我們再來完善一下

四:函數版高級web框架

    html文件和其他的靜態文件還是我們上面使用的。

    python代碼如下: 

#!/usr/bin/env python  # -*- coding:utf-8 -*-  # @Time    : 2019/2/17 14:06  # @Author  : wuchao  # @Site    :  # @File    : test.py  # @Software: PyCharm  import socket    sk = socket.socket()  sk.bind(('127.0.0.1',8001))  sk.listen()    #處理頁面請求的函數  def func1(conn):      with open('test.html', 'rb') as f:          # with open('Python開發.html','rb') as f:          data = f.read()      conn.send(data)      conn.close()    #處理頁面img標籤src屬性值是本地路徑的時候的請求  def func2(conn):      with open('meinv.png', 'rb') as f:          pic_data = f.read()      # conn.send(b'HTTP/1.1 200 ok rnrn')      conn.send(pic_data)      conn.close()  #處理頁面link( <link rel="stylesheet" href="test.css">)標籤href屬性值是本地路徑的時候的請求  def func3(conn):      with open('test.css', 'rb') as f:          css_data = f.read()      conn.send(css_data)      conn.close()    #處理頁面link(<link rel="icon" href="wechat.ico">)標籤href屬性值是本地路徑的時候的請求  def func4(conn):      with open('wechat.ico', 'rb') as f:          ico_data = f.read()      conn.send(ico_data)      conn.close()    #處理頁面script(<script src="test.js"></script>)標籤src屬性值是本地路徑的時候的請求  def func5(conn):      with open('test.js', 'rb') as f:          js_data = f.read()      conn.send(js_data)      conn.close()    while 1:      conn,addr = sk.accept()      # while 1:      from_b_msg = conn.recv(1024)      str_msg = from_b_msg.decode('utf-8')      path = str_msg.split('rn')[0].split(' ')[1]      print('path>>>',path)      conn.send(b'HTTP/1.1 200 ok rnrn')      print(from_b_msg)      if path == '/':          func1(conn)      elif path == '/meinv.png':          func2(conn)      elif path == '/test.css':          func3(conn)      elif path == '/wechat.ico':          func4(conn)        elif path == '/test.js':          func5(conn)

五 更高級版(多線程版)web框架

    應用上我們並發編程的內容,反正html文件和靜態文件都直接給瀏覽器,那大家就一塊並發處理,html文件和靜態文件還是上面的。

    python代碼如下:

#!/usr/bin/env python  # -*- coding:utf-8 -*-  # @Time    : 2019/2/17 14:06  # @Author  : wuchao  # @Site    :  # @File    : test.py  # @Software: PyCharm  import socket  from threading import Thread  #注意一點,不開多線程完全是可以搞定的,在這裡只是教大家要有並發編程的思想,所以我使用了多線程    sk = socket.socket()  sk.bind(('127.0.0.1',8001))  sk.listen()    def func1(conn):      with open('test.html', 'rb') as f:          # with open('Python開發.html','rb') as f:          data = f.read()      conn.send(data)      conn.close()    def func2(conn):      with open('meinv.png', 'rb') as f:          pic_data = f.read()      # conn.send(b'HTTP/1.1 200 ok rnrn')      conn.send(pic_data)      conn.close()    def func3(conn):      with open('test.css', 'rb') as f:          css_data = f.read()      conn.send(css_data)      conn.close()    def func4(conn):      with open('wechat.ico', 'rb') as f:          ico_data = f.read()      conn.send(ico_data)      conn.close()    def func5(conn):      with open('test.js', 'rb') as f:          js_data = f.read()      conn.send(js_data)      conn.close()    while 1:      conn,addr = sk.accept()      # while 1:      from_b_msg = conn.recv(1024)      str_msg = from_b_msg.decode('utf-8')      path = str_msg.split('rn')[0].split(' ')[1]      print('path>>>',path)      conn.send(b'HTTP/1.1 200 ok rnrn')      print(from_b_msg)      if path == '/':          # func1(conn)          t = Thread(target=func1,args=(conn,))          t.start()      elif path == '/meinv.png':          # func2(conn)          t = Thread(target=func2, args=(conn,))          t.start()      elif path == '/test.css':          # func3(conn)          t = Thread(target=func3, args=(conn,))          t.start()      elif path == '/wechat.ico':          # func4(conn)          t = Thread(target=func4, args=(conn,))          t.start()      elif path == '/test.js':          # func5(conn)          t = Thread(target=func5, args=(conn,))          t.start()

六 更更高級版web框架

    if判斷太多了,開線程的方式也比較噁心,有多少個if判斷,就寫多少次創建線程,簡化一下:

import socket  from threading import Thread    sk = socket.socket()  sk.bind(('127.0.0.1',8001))  sk.listen()    def func1(conn):        conn.send(b'HTTP/1.1 200 okrncontent-type:text/htmlrncharset:utf-8rnrn')      with open('test.html', 'rb') as f:          # with open('Python開發.html','rb') as f:          data = f.read()      conn.send(data)      conn.close()    def func2(conn):      conn.send(b'HTTP/1.1 200 okrnrn')      with open('meinv.png', 'rb') as f:          pic_data = f.read()      # conn.send(b'HTTP/1.1 200 ok rnrn')      conn.send(pic_data)      conn.close()    def func3(conn):      conn.send(b'HTTP/1.1 200 okrnrn')      with open('test.css', 'rb') as f:          css_data = f.read()      conn.send(css_data)      conn.close()    def func4(conn):      conn.send(b'HTTP/1.1 200 okrnrn')      with open('wechat.ico', 'rb') as f:          ico_data = f.read()      conn.send(ico_data)      conn.close()    def func5(conn):      conn.send(b'HTTP/1.1 200 okrnrn')      with open('test.js', 'rb') as f:          js_data = f.read()      conn.send(js_data)      conn.close()    #定義一個路徑和執行函數的對應關係,不再寫一堆的if判斷了  l1 = [      ('/',func1),      ('/meinv.png',func2),      ('/test.css',func3),      ('/wechat.ico',func4),      ('/test.js',func5),  ]    #遍歷路徑和函數的對應關係列表,並開多線程高效的去執行路徑對應的函數,  def fun(path,conn):      for i in l1:          if i[0] == path:              t = Thread(target=i[1],args=(conn,))              t.start()      # else:      #     conn.send(b'sorry')    while 1:      conn,addr = sk.accept()      #看完這裏面的代碼之後,你就可以思考一個問題了,很多人要同時訪問你的網站,你在請求這裡是不是可以開起並發編程的思想了,多進程+多線程+協程,妥妥的支持高並發,再配合服務器集群,這個網頁就支持大量的高並發了,有沒有很激動,哈哈,但是咱們寫的太low了,而且功能很差,容錯能力也很差,當然了,如果你有能力,你現在完全可以自己寫web框架了,寫一個nb的,如果現在沒有這個能力,那麼我們就來好好學學別人寫好的框架把,首先第一個就是咱們的django框架了,其實就是將這些功能封裝起來,並且容錯能力強,抗壓能力強,總之一個字:吊。      # while 1:      from_b_msg = conn.recv(1024)      str_msg = from_b_msg.decode('utf-8')      path = str_msg.split('rn')[0].split(' ')[1]      print('path>>>',path)      # 注意:因為開啟的線程很快,可能導致你的文件還沒有發送過去,其他文件的請求已經來了,導致你文件信息沒有被瀏覽器正確的認識,所以需要將發送請求行和請求頭的部分寫道前面的每一個函數裏面去,並且防止出現瀏覽器可能不能識別你的html文件的情況,需要在發送html文件的那個函數裏面的發送請求行和請求頭的部分加上兩個請求頭content-type:text/htmlrncharset:utf-8rn      # conn.send(b'HTTP/1.1 200 okrnrn')  不這樣寫了      # conn.send(b'HTTP/1.1 200 okrncontent-type:text/htmlrncharset:utf-8rnrn')  不這樣寫了      print(from_b_msg)      #執行這個fun函數並將路徑和conn管道都作為參數傳給他      fun(path,conn)

七 根據不同路徑返回不同頁面的web框架

    既然知道了我們可以根據不同的請求路徑來返回不同的內容,那麼我們可不可以根據用戶訪問的不同路徑,返回不同的頁面啊,嗯,應該是可以的

    自己創建兩個html文件,寫幾個標籤在裏面,名為index.html和home.html,然後根據不同的路徑返回不同的頁面,我就給大家寫上python代碼吧:

"""  根據URL中不同的路徑返回不同的內容  返回獨立的HTML頁面  """    import socket  sk = socket.socket()  sk.bind(("127.0.0.1", 8080))  # 綁定IP和端口  sk.listen()  # 監聽      # 將返回不同的內容部分封裝成函數  def index(url):      # 讀取index.html頁面的內容      with open("index.html", "r", encoding="utf8") as f:          s = f.read()      # 返回位元組數據      return bytes(s, encoding="utf8")      def home(url):      with open("home.html", "r", encoding="utf8") as f:          s = f.read()      return bytes(s, encoding="utf8")      # 定義一個url和實際要執行的函數的對應關係  list1 = [      ("/index/", index),      ("/home/", home),  ]    while 1:      # 等待連接      conn, add = sk.accept()      data = conn.recv(8096)  # 接收客戶端發來的消息      # 從data中取到路徑      data = str(data, encoding="utf8")  # 把收到的位元組類型的數據轉換成字符串      # 按rn分割      data1 = data.split("rn")[0]      url = data1.split()[1]  # url是我們從瀏覽器發過來的消息中分離出的訪問路徑      conn.send(b'HTTP/1.1 200 OKrnrn')  # 因為要遵循HTTP協議,所以回復的消息也要加狀態行      # 根據不同的路徑返回不同內容      func = None  # 定義一個保存將要執行的函數名的變量      for i in list1:          if i[0] == url:              func = i[1]              break      if func:          response = func(url)      else:          response = b"404 not found!"        # 返回具體的響應消息      conn.send(response)      conn.close()

八、返回動態頁面的web框架

    這網頁能夠顯示出來了,但是都是靜態的啊。頁面的內容都不會變化的,我想要的是動態網站,動態網站的意思是裏面有動態變化的數據,而不是頁面裏面有動態效果,這個大家要注意啊。

    沒問題,我也有辦法解決。我選擇使用字符串替換來實現這個需求。(這裡使用時間戳來模擬動態的數據,還是只給大家python代碼吧)

"""  根據URL中不同的路徑返回不同的內容  返回HTML頁面  讓網頁動態起來  """    import socket  import time    sk = socket.socket()  sk.bind(("127.0.0.1", 8080))  # 綁定IP和端口  sk.listen()  # 監聽      # 將返回不同的內容部分封裝成函數  def index(url):      with open("index.html", "r", encoding="utf8") as f:          s = f.read()          now = str(time.time())          s = s.replace("@@oo@@", now)  # 在網頁中定義好特殊符號,用動態的數據去替換提前定義好的特殊符號      return bytes(s, encoding="utf8")      def home(url):      with open("home.html", "r", encoding="utf8") as f:          s = f.read()      return bytes(s, encoding="utf8")      # 定義一個url和實際要執行的函數的對應關係  list1 = [      ("/index/", index),      ("/home/", home),  ]    while 1:      # 等待連接      conn, add = sk.accept()      data = conn.recv(8096)  # 接收客戶端發來的消息      # 從data中取到路徑      data = str(data, encoding="utf8")  # 把收到的位元組類型的數據轉換成字符串      # 按rn分割      data1 = data.split("rn")[0]      url = data1.split()[1]  # url是我們從瀏覽器發過來的消息中分離出的訪問路徑      conn.send(b'HTTP/1.1 200 OKrnrn')  # 因為要遵循HTTP協議,所以回復的消息也要加狀態行      # 根據不同的路徑返回不同內容      func = None  # 定義一個保存將要執行的函數名的變量      for i in list1:          if i[0] == url:              func = i[1]              break      if func:          response = func(url)      else:          response = b"404 not found!"        # 返回具體的響應消息      conn.send(response)      conn.close()

  這八個框架讓大家滿意了吧,這下子明白整個web框架的原理了吧,哈哈,但是我們寫的框架還是太low了,不夠強壯,那別人已經開發好了很多nb的框架了,如:Django、Flask、Tornado等等,我們學學怎麼用就可以啦,但是注意一個問題,我們在裏面獲取路徑的時候,我們是按照rn來分割然後再通過空格來分割獲取到的路徑,但是如果不是http協議的話,你自己要注意消息格式了。

  接下來我們看一個別人寫好的模塊來搞的web框架,這個模塊叫做wsgiref

九、wsgiref模塊版web框架

    wsgiref模塊其實就是將整個請求信息給封裝了起來,就不需要你自己處理了,假如它將所有請求信息封裝成了一個叫做request的對象,那麼你直接request.path就能獲取到用戶這次請求的路徑,request.method就能獲取到本次用戶請求的請求方式(get還是post)等,那這個模塊用起來,我們再寫web框架是不是就簡單了好多啊。

    對於真實開發中的python web程序來說,一般會分為兩部分:服務器程序和應用程序。

    服務器程序負責對socket服務器進行封裝,並在請求到來時,對請求的各種數據進行整理。

    應用程序則負責具體的邏輯處理。為了方便應用程序的開發,就出現了眾多的Web框架,例如:Django、Flask、web.py 等。不同的框架有不同的開發方式,但是無論如何,開發出的應用程序都要和服務器程序配合,才能為用戶提供服務。

    這樣,服務器程序就需要為不同的框架提供不同的支持。這樣混亂的局面無論對於服務器還是框架,都是不好的。對服務器來說,需要支持各種不同框架,對框架來說,只有支持它的服務器才能被開發出的應用使用。最簡單的Web應用就是先把HTML用文件保存好,用一個現成的HTTP服務器軟件,接收用戶請求,從文件中讀取HTML,返回。如果要動態生成HTML,就需要把上述步驟自己來實現。不過,接受HTTP請求、解析HTTP請求、發送HTTP響應都是苦力活,如果我們自己來寫這些底層代碼,還沒開始寫動態HTML呢,就得花個把月去讀HTTP規範。

正確的做法是底層代碼由專門的服務器軟件實現,我們用Python專註於生成HTML文檔。因為我們不希望接觸到TCP連接、HTTP原始請求和響應格式,所以,需要一個統一的接口協議來實現這樣的服務器軟件,讓我們專心用Python編寫Web業務。

    這時候,標準化就變得尤為重要。我們可以設立一個標準,只要服務器程序支持這個標準,框架也支持這個標準,那麼他們就可以配合使用。一旦標準確定,雙方各自實現。這樣,服務器可以支持更多支持標準的框架,框架也可以使用更多支持標準的服務器。

    WSGI(Web Server Gateway Interface)就是一種規範,它定義了使用Python編寫的web應用程序與web服務器程序之間的接口格式,實現web應用程序與web服務器程序間的解耦。

    常用的WSGI服務器有uwsgi、Gunicorn。而Python標準庫提供的獨立WSGI服務器叫wsgiref,Django開發環境用的就是這個模塊來做服務器。

    好,接下來我們就看一下(能理解就行,了解就可以了):先看看wsfiref怎麼使用

from wsgiref.simple_server import make_server  # wsgiref本身就是個web框架,提供了一些固定的功能(請求和響應信息的封裝,不需要我們自己寫原生的socket了也不需要咱們自己來完成請求信息的提取了,提取起來很方便)  #函數名字隨便起  def application(environ, start_response):      '''      :param environ: 是全部加工好的請求信息,加工成了一個字典,通過字典取值的方式就能拿到很多你想要拿到的信息      :param start_response: 幫你封裝響應信息的(響應行和響應頭),注意下面的參數      :return:      '''      start_response('200 OK', [('Content-Type', 'text/html'),('k1','v1')])      print(environ)      print(environ['PATH_INFO'])  #輸入地址127.0.0.1:8000,這個打印的是'/',輸入的是127.0.0.1:8000/index,打印結果是'/index'      return [b'<h1>Hello, web!</h1>']    #和咱們學的socketserver那個模塊很像啊  httpd = make_server('127.0.0.1', 8080, application)    print('Serving HTTP on port 8080...')  # 開始監聽HTTP請求:  httpd.serve_forever()

    來一個完整的web項目,用戶登錄認證的項目,我們需要連接數據庫了,所以先到mysql數據庫裏面準備一些表和數據

mysql> create database db1;  Query OK, 1 row affected (0.00 sec)    mysql> use db1;  Database changed  mysql> create table userinfo(id int primary key auto_increment,username char(20) not null unique,password char(20) not null);  Query OK, 0 rows affected (0.23 sec)    mysql> insert into userinfo(username,password) values('chao','666'),('sb1','222');  Query OK, 2 rows affected (0.03 sec)  Records: 2  Duplicates: 0  Warnings: 0    mysql> select * from userinfo;  +----+----------+----------+  | id | username | password |  +----+----------+----------+  |  1 | chao     | 666      |  |  2 | sb1      | 222      |  +----+----------+----------+  2 rows in set (0.00 sec)

    然後再創建這麼幾個文件:

    python文件名稱webmodel.py,內容如下:

#創建表,插入數據  def createtable():      import pymysql      conn = pymysql.connect(          host='127.0.0.1',          port=3306,          user='root',          password='666',          database='db1',          charset='utf8'      )      cursor = conn.cursor(pymysql.cursors.DictCursor)      sql = '''          -- 創建表          create table userinfo(id int primary key auto_increment,username char(20) not null unique,password char(20) not null);          -- 插入數據          insert into userinfo(username,password) values('chao','666'),('sb1','222');      '''      cursor.execute(sql)      conn.commit()      cursor.close()      conn.close()

    python的名為webauth文件,內容如下:

#對用戶名和密碼進行驗證  def auth(username,password):      import pymysql      conn = pymysql.connect(          host='127.0.0.1',          port=3306,          user='root',          password='123',          database='db1',          charset='utf8'      )      print('userinfo',username,password)      cursor = conn.cursor(pymysql.cursors.DictCursor)      sql = 'select * from userinfo where username=%s and password=%s;'      res = cursor.execute(sql, [username, password])      if res:          return True      else:          return False

    用戶輸入用戶名和密碼的文件,名為web.html,內容如下:

<!DOCTYPE html>  <html lang="en">  <head>      <meta charset="UTF-8">      <title>Title</title>  </head>  <body>  <!--如果form表單裏面的action什麼值也沒給,默認是往當前頁面的url上提交你的數據,所以我們可以自己指定數據的提交路徑-->  <!--<form action="http://127.0.0.1:8080/auth/" method="post">-->  <form action="http://127.0.0.1:8080/auth/" method="get">      用戶名<input type="text" name="username">      密碼 <input type="password" name="password">      <input type="submit">  </form>    <script>    </script>  </body>  </html>

    用戶驗證成功後跳轉的頁面,顯示成功,名為websuccess.html,內容如下:

<!DOCTYPE html>  <html lang="en">  <head>      <meta charset="UTF-8">      <title>Title</title>      <style>          h1{              color:red;          }      </style>  </head>  <body>  <h1>寶貝兒,恭喜你登陸成功啦</h1>      </body>  </html>

    python服務端代碼(主邏輯代碼),名為web_python.py:

from urllib.parse import parse_qs  from wsgiref.simple_server import make_server  import webauth  def application(environ, start_response):        # start_response('200 OK', [('Content-Type', 'text/html'),('k1','v1')])      # start_response('200 OK', [('Content-Type', 'text/html'),('charset','utf-8')])      start_response('200 OK', [('Content-Type', 'text/html')])      print(environ)      print(environ['PATH_INFO'])      path = environ['PATH_INFO']      #用戶獲取login頁面的請求路徑      if path == '/login':          with open('web.html','rb') as f:              data = f.read()      #針對form表單提交的auth路徑,進行對應的邏輯處理      elif path == '/auth/':          #登陸認證          #1.獲取用戶輸入的用戶名和密碼            #2.去數據庫做數據的校驗,查看用戶提交的是否合法          # user_information = environ['']          if environ.get("REQUEST_METHOD") == "POST":              #獲取請求體數據的長度,因為提交過來的數據需要用它來提取,注意POST請求和GET請求的獲取數據的方式不同              try:                  request_body_size = int(environ.get('CONTENT_LENGTH', 0))              except (ValueError):                  request_body_size = 0              #POST請求獲取數據的方式              request_data = environ['wsgi.input'].read(request_body_size)              print('>>>>>',request_data) # >>>>> b'username=chao&password=123',是個bytes類型數據              print('?????',environ['QUERY_STRING']) #????? 空的,因為post請求只能按照上面這種方式取數據              #parse_qs可以幫我們解析數據              re_data = parse_qs(request_data)              print('拆解後的數據',re_data) #拆解後的數據 {b'password': [b'123'], b'username': [b'chao']}            #post請求的返回數據我就不寫啦        pass          if environ.get("REQUEST_METHOD") == "GET":              #GET請求獲取數據的方式,只能按照這種方式取              print('?????',environ['QUERY_STRING']) #????? username=chao&password=123,是個字符串類型數據              request_data = environ['QUERY_STRING']                # parse_qs可以幫我們解析數據              re_data = parse_qs(request_data)              print('拆解後的數據', re_data) #拆解後的數據 {'password': ['123'], 'username': ['chao']}              username = re_data['username'][0]              password = re_data['password'][0]              print(username,password)              #進行驗證:              status = webauth.auth(username,password)              if status:              # 3.將相應內容返回                  with open('websuccess.html','rb') as f:                      data = f.read()              else:                  data = b'auth error'          # 但是不管是post還是get請求都不能直接拿到數據,拿到的數據還需要我們來進行分解提取,所以我們引入urllib模塊來幫我們分解                #注意昂,我們如果直接返回中文,沒有給瀏覽器指定編碼格式,默認是gbk,所以我們需要gbk來編碼一下,瀏覽器才能識別          # data='登陸成功!'.encode('gbk')      else:          data = b'sorry 404!,not found the page'      return [data]        #和咱們學的socketserver那個模塊很像啊  httpd = make_server('127.0.0.1', 8080, application)    print('Serving HTTP on port 8080...')  # 開始監聽HTTP請求:  httpd.serve_forever()

    把代碼拷走,創建文件,放到同一個目錄下,運行一下we_python.py文件的代碼就能看到效果,注意先輸入的網址是127.0.0.1:8080/login ,還要注意你的mysql數據庫沒有問題。

十、起飛版web框架

    我們上一個web框架把所有的代碼都寫在了一個py文件中,我們拆到其他文件裏面去,並且針對不用的路徑來進行分發請求的時候都用的if判斷,很多值得優化的地方,好,結合我們前面幾個版本的優勢我們來優化一下,分幾個文件和文件夾

    代碼就不在博客上都列出來了,我打包放到百度雲上了,大家去下載看看把:https://pan.baidu.com/s/1Ns5QHFpZGusGHuHzrCto3A

  將來要說的MVC框架是什麼呢:

    M:model.py 就是和數據庫打交道用的,創建表等操作

    V:View 視圖(視圖函數,html文件)

    C:controller 控制器(其實就是我百度雲代碼裏面那個urls文件裏面的內容,url(路徑)分發與視圖函數的邏輯處理)

  Django叫做MTV框架

    M:model.py 就是和數據庫打交道用的,創建表等操作(和上面一樣)

    T:templates 存放HTML文件的

    V:View 視圖函數(邏輯處理)

    其實你會發現MTV比MVC少一個url分發的部分

    所以我們學的django還要學一個叫做url控制器(路徑分發)的東西,MTV+url控制器就是我們django要學的內容。     

  捋一下框架的整個流程吧~~~

二 模板渲染JinJa2

  上面的代碼實現了一個簡單的動態頁面(字符串替換),我完全可以從數據庫中查詢數據,然後去替換我html中的對應內容(專業名詞叫做模板渲染,你先渲染一下,再給瀏覽器進行渲染),然後再發送給瀏覽器完成渲染。 這個過程就相當於HTML模板渲染數據。 本質上就是HTML內容中利用一些特殊的符號來替換要展示的數據。 我這裡用的特殊符號是我定義的,其實模板渲染有個現成的工具: jinja2

  下載:

pip install jinja2

  來一個html文件,index2,html,內容如下:

<!DOCTYPE html>  <html lang="zh-CN">  <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>Title</title>  </head>  <body>      <h1>姓名:{{name}}</h1>      <h1>愛好:</h1>      <ul>          {% for hobby in hobby_list %}          <li>{{hobby}}</li>          {% endfor %}      </ul>  </body>  </html>

  使用jinja2渲染index2.html文件,創建一個python文件,代碼如下:

from wsgiref.simple_server import make_server  from jinja2 import Template      def index():      with open("index2.html", "r",encoding='utf-8') as f:          data = f.read()      template = Template(data)  # 生成模板文件      ret = template.render({"name": "于謙", "hobby_list": ["燙頭", "泡吧"]})  # 把數據填充到模板裏面      return [bytes(ret, encoding="utf8"), ]      # 定義一個url和函數的對應關係  URL_LIST = [      ("/index/", index),  ]    def run_server(environ, start_response):      start_response('200 OK', [('Content-Type', 'text/html;charset=utf8'), ])  # 設置HTTP響應的狀態碼和頭信息      url = environ['PATH_INFO']  # 取到用戶輸入的url      func = None  # 將要執行的函數      for i in URL_LIST:          if i[0] == url:              func = i[1]  # 去之前定義好的url列表裡找url應該執行的函數              break      if func:  # 如果能找到要執行的函數          return func()  # 返回函數的執行結果      else:          return [bytes("404沒有該頁面", encoding="utf8"), ]      if __name__ == '__main__':      httpd = make_server('', 8000, run_server)      print("Serving HTTP on port 8000...")      httpd.serve_forever()

  現在的數據是我們自己手寫的,那可不可以從數據庫中查詢數據,來填充頁面呢?

  使用pymysql連接數據庫:

conn = pymysql.connect(host="127.0.0.1", port=3306, user="root", passwd="xxx", db="xxx", charset="utf8")  cursor = conn.cursor(cursor=pymysql.cursors.DictCursor)  cursor.execute("select name, age, department_id from userinfo")  user_list = cursor.fetchall()  cursor.close()  conn.close()

  創建一個測試的user表:

CREATE TABLE user(    id int auto_increment PRIMARY KEY,    name CHAR(10) NOT NULL,    hobby CHAR(20) NOT NULL  )engine=innodb DEFAULT charset=UTF8;

  模板的原理就是字符串替換,我們只要在HTML頁面中遵循jinja2的語法規則寫上,其內部就會按照指定的語法進行相應的替換,從而達到動態的返回內容。

三 MVC和MTV框架

MVC

  Web服務器開發領域裏著名的MVC模式,所謂MVC就是把Web應用分為模型(M),控制器(C)和視圖(V)三層,他們之間以一種插件式的、松耦合的方式連接在一起,模型負責業務對象與數據庫的映射(ORM),視圖負責與用戶的交互(頁面),控制器接受用戶的輸入調用模型和視圖完成用戶的請求,其示意圖如下所示:

MTV

  Django的MTV模式本質上和MVC是一樣的,也是為了各組件間保持松耦合關係,只是定義上有些許不同,Django的MTV分別是值:

  • M 代表模型(Model): 負責業務對象和數據庫的關係映射(ORM)。
  • T 代表模板 (Template):負責如何把頁面展示給用戶(html)。
  • V 代表視圖(View): 負責業務邏輯,並在適當時候調用Model和Template。

  除了以上三層之外,還需要一個URL分發器,它的作用是將一個個URL的頁面請求分發給不同的View處理,View再調用相應的Model和Template,MTV的響應模式如下所示:

  一般是用戶通過瀏覽器向我們的服務器發起一個請求(request),這個請求回去訪問視圖函數,(如果不涉及到數據調用,那麼這個時候視圖函數返回一個模板也就是一個網頁給用戶),視圖函數調用模型,模型去數據庫查找數據,然後逐級返回,視圖函數把返回的數據填充到模板中空格中,最後返回網頁給用戶。

四 Django下載安裝

Django官網下載頁面

1、下載Django:

pip3 install django==1.11.9

2、創建一個django project

django-admin startproject mysite   創建了一個名為"mysite"的Django 項目:  

  當前目錄下會生成mysite的工程,目錄結構如下:(大家注意昂,pip下載下來的django你就理解成一個模塊,而不是django項目,這個模塊可以幫我們創建django項目)

​   

  • manage.py —– Django項目裏面的工具,通過它可以調用django shell和數據庫,啟動關閉項目與項目交互等,不管你將框架分了幾個文件,必然有一個啟動文件,其實他們本身就是一個文件。
  • settings.py —- 包含了項目的默認設置,包括數據庫信息,調試標誌以及其他一些工作的變量。
  • urls.py —– 負責把URL模式映射到應用程序。
  • wsgi.py —- runserver命令就使用wsgiref模塊做簡單的web server,後面會看到renserver命令,所有與socket相關的內容都在這個文件裏面了,目前不需要關注它。
 python manage.py runserver 127.0.0.1:8080  #此時已經可以啟動django項目了,只不過什麼邏輯也沒有呢

  你會發現,上面沒有什麼view視圖函數的文件啊,這裡我們說一個應用與項目的關係,上面我們只是創建了一個項目,並沒有創建應用,以微信來舉例,微信是不是一個大的項目,但是微信裏面是不是有很多個應用,支付應用、聊天應用、朋友圈、小程序等這些在一定程度上都是相互獨立的應用,也就是說一個大的項目裏面可以有多個應用,也就是說項目是包含應用的,它沒有將view放到這個項目目錄裏面是因為它覺得,一個項目裏面可以有多個應用,而每個應用都有自己這個應用的邏輯內容,所以他覺得這個view應該放到應用裏面,比如說我們的微信,剛才說了幾個應用,這幾個應用的邏輯能放到一起嗎,放到一起是不是就亂套啦,也不好管理和維護,所以這些應用的邏輯都分開來放,它就幫我們提煉出來了,提煉出來一個叫做應用的東西,所以我們需要來創建這個應用。

3、在mysite目錄下創建應用

python manage.py startapp blog   #通過執行manage.py文件來創建應用,執行這句話一定要注意,你應該在這個manage.py的文件所在目錄下執行這句話,因為其他目錄裏面沒有這個文件  python manage.py startapp blog2  #每個應用都有自己的目錄,每個應用的目錄下都有自己的views.py視圖函數和models.py數據庫操作相關的文件

​   

  我們現在只需要看其中兩個文件

    models.py :之前我們寫的那個名為model的文件就是創建表用的,這個文件就是存放與該app(應用)相關的表結構的

    views.py :存放與該app相關的視圖函數的

4、啟動django項目

python manage.py runserver 8080   # python manage.py runserver 127.0.0.1:8080,本機就不用寫ip地址了 如果連端口都沒寫,默認是本機的8000端口

​ 這樣我們的django就啟動起來了!當我們訪問:http://127.0.0.1:8080/時就可以看到:

學習Django,我們就學上面的這些文件,怎麼在MTV+url分發的功能下來使用。

最後我們說一下,其實我們將來創建django項目,很少用命令行了,就用pycharm來創建,怎麼創建呢?看圖:

    看項目目錄:

五 基於Django實現一個簡單的示例

現在實現一個用戶輸入一個timer路徑,返回一個含有當前時間的頁面,想想怎麼做?用戶輸入網址–>路徑–>函數–>返回數據(文件)

url控制器(第一步就找它)

django 1.11.9版本的url寫法:from django.conf.urls import urlfrom django.contrib import adminfrom crm import viewsurlpatterns = [    url(r'^admin/', admin.site.urls),    url(r'^index/', views.index),]  下面是django2.x版本的url寫法,不太一樣了,但是兼容1.x的,不過我們現在還是主要說1.xx版本的,所以寫url的時候按照上的方式寫。from django.contrib import admin  from django.urls import path    #找對應的函數,是哪個app裏面的函數  from app01 import views    urlpatterns = [      path('admin/', admin.site.urls), #這個先不用管,後面會學      path('index/',views.index),  ]

  視圖

from django.shortcuts import render,HttpResponse    # Create your views here.  #邏輯和返回數據  def index(request):        import datetime      now=datetime.datetime.now()      ctime=now.strftime("%Y-%m-%d %X")     #return HttpResponse('哈哈,好玩嗎?')      return render(request,"index.html",{"ctime":ctime}) #render,渲染html頁面文件並返回給瀏覽器

  模板

<!DOCTYPE html>  <html lang="en">  <head>      <meta charset="UTF-8">      <title>Title</title>  </head>  <body>    <h4>當前時間:{{ ctime }}</h4>    </body>  </html>

  通過pycharm來運行項目:

    看控制台:

  執行效果如下:

有同學說:我想自己配置啟動的端口怎麼搞啊:

  還有一點說一下昂,在settings配置文件裏面有關於templates(放html文件的配置):

TEMPLATES = [      {          'BACKEND': 'django.template.backends.django.DjangoTemplates',          'DIRS': [os.path.join(BASE_DIR, 'templates')] #有些版本的django沒有寫這個,自己寫一下,就是配置一個django找html文件的路徑,render方法就來這裡找html文件          ,          'APP_DIRS': True,          'OPTIONS': {              'context_processors': [                  'django.template.context_processors.debug',                  'django.template.context_processors.request',                  'django.contrib.auth.context_processors.auth',                  'django.contrib.messages.context_processors.messages',              ],          },      },  ]

  關於請求和響應的請求信息和相應信息的設置還需要你自己寫嗎?之前我們用wsgiref是不是還寫來着,現在都不需要寫了,簡單不。

  還有一點:post請求的時候你會發現一個 Forbidden的錯誤:

  現在只需要做一步,在settings配置文件裏面將這一行注釋掉,這是django給你加的一個csrf的認證,現在不需要,我們會講的

MIDDLEWARE = [      'django.middleware.security.SecurityMiddleware',      'django.contrib.sessions.middleware.SessionMiddleware',      'django.middleware.common.CommonMiddleware',      # 'django.middleware.csrf.CsrfViewMiddleware',      'django.contrib.auth.middleware.AuthenticationMiddleware',      'django.contrib.messages.middleware.MessageMiddleware',      'django.middleware.clickjacking.XFrameOptionsMiddleware',  ]

  還記得django寫視圖函數的時候,有一個參數是必須要給的嗎,叫做request,如果你是post請求,那麼就用request.POST,就能拿到post請求提交過來的所有數據(一個字典,然後再通過字典取值request.POST.get('username'),取出來的就是個字符串,你在那個字典裏面看到的是{'username':['chao']},雖然看着是列表,但是request.POST.get('username')取出來的就是個字符串),通過request.GET就能拿到提交過來的所有數據,而且記着,每一個視圖函數都要給人家返回一些內容,用render或者HttpResponse等,其實render裏面也是通過HttpResponse來返回內容,不然會報錯,錯誤是告訴你沒有返回任何內容:

  django認識了,以後我們就按照下面的步驟來學:

    1.django的url控制器

    2.django的視圖

    3.django的模板(template)

    4.ORM(花的時間比較久)

  作業好了,我們之前寫的那個登陸認證示例,加到django裏面來吧!