1. 程式人生 > >Django-手擼簡易web框架-實現動態網頁-wsgiref初識-jinja2初識-python主流web框架對比-00

Django-手擼簡易web框架-實現動態網頁-wsgiref初識-jinja2初識-python主流web框架對比-00

目錄

  • 自己動手實現一個簡易版本的web框架
    • 手擼一個web服務端
    • 根據請求 url 做不同的響應處理
  • 基於wsgiref模組實現服務端
    • 用wsgiref 模組的做的兩件事
      • 拆分服務端程式碼
    • 支援新的請求地址(新增新頁面/新功能)
  • 動態靜態網頁--拆分模板檔案
    • 返回靜態頁面--案例
  • 實現返回時間--插值思路(動態頁面)
  • 利用 jinja2 模組實現動態頁面
    • 安裝 jinja2
    • 初步使用
      • 案例--展示字典資訊
      • 進階案例--渲染資料庫資料到頁面
  • 推導流程與小總結
    • 流程圖
    • 小擴充套件
  • python三大Web主流框架分析對比
    • Django
    • Flask
    • Tornado
    • 手擼三大部分在框架中的情況對比
      • Django
      • Flask
      • Tornado
  • Django的下載安裝基本使用

自己動手實現一個簡易版本的web框架

在瞭解python的三大web框架之前,我們先自己動手實現一個。

備註:

這部分重在掌握實現思路,程式碼不是重點

程式碼中也有許多細節並未考慮,重在實現思路

手擼一個web服務端

我們一般是使用瀏覽器當做客戶端,然後基於HTTP協議自己寫服務端程式碼作為服務端

先自行去回顧一下HTTP協議這一塊兒的知識

import socket
server = socket.socket()  # 基於socket通訊(TCP)
server.bind(('127.0.0.1', 8080))
server.listen(5)
while True:
    conn, addr = server.accept()
    data = conn.recv(2048)  # 接收請求
    print(str(data, encoding='utf-8'))
    conn.send(b'HTTP/1.1 200 OK\r\n\r\n')  # 依據HTTP協議,傳送響應給客戶端(瀏覽器),這裡是響應首行 + 響應頭 + 空行
    # response = bytes('<h3>這是響應內容</h3>', encoding='GBK')
    response = '<h3>這是響應內容</h3>'.encode('GBK')  # 我電腦上是GBK編碼,所以使用GBK編碼將字串轉成二進位制
    conn.send(response)  #  繼續傳送響應體
    conn.close()  # 斷開連線(無狀態、無連線)

# 瀏覽器發過來的資料如下
'''
GET / HTTP/1.1
Host: 127.0.0.1:8080
Connection: keep-alive
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3770.90 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3
Accept-Encoding: gzip, deflate, br
Accept-Language: zh-CN,zh;q=0.9
Cookie: _qddaz=QD.w3c3g1.j2bfa7.jvp70drt; csrftoken=kJHVGICQOglLxJNiui0o0UyxNtR3cXbJPXqaUFs5FoxeezuskRO7jlQE0JNwYXJs


GET /favicon.ico HTTP/1.1
Host: 127.0.0.1:8080
Connection: keep-alive
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3770.90 Safari/537.36
Accept: image/webp,image/apng,image/*,*/*;q=0.8
Referer: http://127.0.0.1:8080/
Accept-Encoding: gzip, deflate, br
Accept-Language: zh-CN,zh;q=0.9
Cookie: _qddaz=QD.w3c3g1.j2bfa7.jvp70drt; csrftoken=kJHVGICQOglLxJNiui0o0UyxNtR3cXbJPXqaUFs5FoxeezuskRO7jlQE0JNwYXJs
'''

然後右鍵執行,在瀏覽器訪問 127.0.0.1:8080 即可看到響應資料

關於啟動伺服器與頁面請求(在我處理的時候,頁面網路請求會經常處於 pending狀態,不是很清楚原因,一般這個情況下,直接重啟一下伺服器即可)

根據請求 url 做不同的響應處理

上面的程式碼已經實現了基本請求響應,那如何根據不同的請求作出不同的響應呢?

我們輸入不同的url,看看伺服器端會返回什麼

分析請求

瀏覽器訪問 http://127.0.0.1:8080/index
GET /index HTTP/1.1
Host: 127.0.0.1:8080
Connection: keep-alive
Cache-Control: max-age=0
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3770.90 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3
Accept-Encoding: gzip, deflate, br
Accept-Language: zh-CN,zh;q=0.9
Cookie: _qddaz=QD.w3c3g1.j2bfa7.jvp70drt; csrftoken=kJHVGICQOglLxJNiui0o0UyxNtR3cXbJPXqaUFs5FoxeezuskRO7jlQE0JNwYXJs
    

瀏覽器訪問 http://127.0.0.1:8080/home   
GET /home HTTP/1.1
Host: 127.0.0.1:8080
Connection: keep-alive
Cache-Control: max-age=0
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3770.90 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3
Accept-Encoding: gzip, deflate, br
Accept-Language: zh-CN,zh;q=0.9
Cookie: _qddaz=QD.w3c3g1.j2bfa7.jvp70drt; csrftoken=kJHVGICQOglLxJNiui0o0UyxNtR3cXbJPXqaUFs5FoxeezuskRO7jlQE0JNwYXJs

原來請求首行的 GET 後面跟的就是請求我們想要資訊(/index 首頁、/home 家)

這些資訊也是我們接收到的(data = conn.recv(2048) print(str(data, encoding='utf-8'))),那可不可以取出來,根據值的不同作不同處理呢?

處理請求,獲取 url

data = '''GET /home HTTP/1.1
Host: 127.0.0.1:8080
Connection: keep-alive
Cache-Control: max-age=0
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3770.90 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3
Accept-Encoding: gzip, deflate, br
Accept-Language: zh-CN,zh;q=0.9
Cookie: _qddaz=QD.w3c3g1.j2bfa7.jvp70drt; csrftoken=kJHVGICQOglLxJNiui0o0UyxNtR3cXbJPXqaUFs5FoxeezuskRO7jlQE0JNwYXJs'''
print(data.split('\n')[0].split(' ')[1])  # ... ---> GET /home HTTP/1.1 --> ['GET', '/home', 'HTTP/1.1']  --> /home
# /home

依據上述切割規則,我們來對不同的請求作出不同的響應

import socket
server = socket.socket()
server.bind(('127.0.0.1', 8080))
server.listen(5)
while True:
    conn, addr = server.accept()
    data = conn.recv(2048).decode('utf-8')
    data = data.split('\n')[0].split(' ')[1]
    print(data)
    conn.send(b'HTTP/1.1 200 OK\r\n\r\n')
    if data == '/index':
        response = '<h3>這裡是 index...</h3>'.encode('GBK')
    elif data == '/home':
        response = '<h3>這裡是 home...</h3>'.encode('GBK')
    else:
        response = '<h3>404 NOT FOUND...\n找不到您要找的資源...</h3>'.encode('GBK')
    conn.send(response)
    conn.close()

# --- 瀏覽器請求 http://127.0.0.1:8080/index 的列印資訊
# /index
# /favicon.ico
# --- 瀏覽器請求 http://127.0.0.1:8080/home 的列印資訊
# /home
# /favicon.ico
# --- 瀏覽器請求 http://127.0.0.1:8080/de2332f 的列印資訊
# /de2332f
# /favicon.ico

頁面成功顯示不同的資訊

http://127.0.0.1:8080/index

http://127.0.0.1:8080/home

http://127.0.0.1:8080/de2332f

404頁面也應該算作設計網站的一部分,可以給人不一樣的感覺

基於wsgiref模組實現服務端

前面處理 scoket 和 http 的那堆程式碼通常是不變的,且與業務邏輯沒什麼關係,如果每個專案都要寫一遍,那豈不是很麻煩?那封裝成模組嘛~

不過這個操作已經有人幫我們做了,並且封裝的更加強大,就是 wsgiref 模組

用wsgiref 模組的做的兩件事

  1. 在請求來的時候,自動解析 HTTP 資料,並打包成一個字典,便於對請求發過來的資料進行操作
  2. 發響應之前,自動幫忙把資料打包成符合 HTTP 協議的格式(響應資料格式,不需要再手動寫 conn.send(b'HTTP/1.1 200 OK\r\n\r\n') 了),返回給服務端
from wsgiref.simple_server import make_server  # 導模組


def run(env, response):
    """
    先不管這裡的 env 和 response 什麼個情況

    env:是請求相關的資料,wsgiref幫我們把請求包裝成了一個大字典,方便取值
    response:是響應相關的資料
    """
    response('200 OK', [])
    print(env)
    current_path = env.get('PATH_INFO')
    print(current_path)

    if current_path == '/index':
        return ['hello, there is index...'.encode('utf-8')]
    elif current_path == '/login':
        return ['hello, there is login...'.encode('utf-8')]
    else:
        return ['sorry... that pages you want is not found...'.encode('utf-8')]


if __name__ == '__main__':
    # 實時監測 127.0.0.1:8080 地址,一旦有客戶端連線,會自動加括號呼叫 run 方法
    server = make_server('127.0.0.1', 8080, run)
    server.serve_forever()  # 啟動伺服器


# /index
# ---> env 的資料(手動刪減了一些),可以看到其中有個 PATH_INFO 是我們要的東西(還有瀏覽器版本啊,USER-AGENT啊,客戶端系統環境變數啊之類的資訊)
'''{'ALLUSERSPROFILE': 'C:\\ProgramData', ...省略部分... , 'COMSPEC': 'C:\\Windows\\system32\\cmd.exe', 'PROCESSOR_IDENTIFIER': 'Intel64 Family 6 Model 60 Stepping 3, GenuineIntel', 'PROCESSOR_LEVEL': '6', 'PROCESSOR_REVISION': '3c03', 'PYTHONIOENCODING': 'UTF-8',  'SESSIONNAME': 'Console', 'SYSTEMDRIVE': 'C:', 'SERVER_PORT': '8080', 'REMOTE_HOST': '', 'CONTENT_LENGTH': '', 'SCRIPT_NAME': '', 'SERVER_PROTOCOL': 'HTTP/1.1', 'SERVER_SOFTWARE': 'WSGIServer/0.2', 'REQUEST_METHOD': 'GET', 'PATH_INFO': '/index', 'QUERY_STRING': '', 'REMOTE_ADDR': '127.0.0.1', 'CONTENT_TYPE': 'text/plain', 'HTTP_HOST': '127.0.0.1:8080', 'HTTP_CONNECTION': 'keep-alive', 'HTTP_UPGRADE_INSECURE_REQUESTS': '1', 'HTTP_USER_AGENT': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3770.90 Safari/537.36', 'HTTP_ACCEPT': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3', csrftoken=kJHVGICQOglLxJNiui0o0UyxNtR3cXbJPXqaUFs5FoxeezuskRO7jlQE0JNwYXJs', mode='w' encoding='UTF-8'>, 'wsgi.version': (1, 0), }'''

伏筆

拆分服務端程式碼

服務端程式碼、路由配置、檢視函式,照目前的寫法全都冗在一塊兒,後期功能擴充套件時,這個檔案會變得很長,不方便維護,所以選擇把他拆分開來

就是將服務端程式碼拆分成如下三部分:

  • server.py 放服務端程式碼

  • urls.py 放路由與檢視函式對應關係

  • views.py 放檢視函式/類(處理業務邏輯)

views.py

def index(env):
    return 'index'


def login(env):
    return 'login'

urls.py

from views import *

urls = [
    ('/index', index),
    ('/login', login),
]

server.py

from wsgiref.simple_server import make_server  # 導模組
from urls import urls  # 引入 urls.py 裡的 urls列表(命名的不是很規範)


def run(env, response):
    response('200 OK', [])
    current_path = env.get('PATH_INFO')

    func = None
    for url in urls:
        if current_path == url[0]:
            func = url[1]
            break

    if func:
        res = func(env)
    else:
        res = '404 Not Found.'
    return [res.encode('utf-8')]  # 注意這裡返回的是一個列表(可迭代物件才行),wsgiref 模組規定的,可能還有其他的用途吧


if __name__ == '__main__':
    server = make_server('127.0.0.1', 8080, run)
    server.serve_forever()

支援新的請求地址(新增新頁面/新功能)

經過上面的拆分後,後續想要支援其他 url,只需要在 urls.py 中新增一條對應關係,在 views.py 中把該函式實現,重啟伺服器即可訪問

以支援 http://127.0.0.1:8080/new_url 訪問為例

urls.py

from views import *

urls = [
    ('/index', index),
    ('/login', login),
    ('/new_url', new_url),
]

views.py

def index(env):
    return 'index'


def login(env):
    return 'login'


def new_url(env):
    # 這裡可以寫一堆邏輯程式碼
    return 'new_url'

重啟伺服器,開啟瀏覽器即可訪問 http://127.0.0.1:8080/new_url

擴充套件性高了很多,且邏輯更清晰了,更不容易弄錯(框架的好處提現,也是為什麼脫離了框架不會寫的原因,這塊程式碼寫的太少,不常用到,沒了框架又寫不出來)

動態靜態網頁--拆分模板檔案

前面寫了那麼多,都只是一直在返回純文字資訊,而我們一般請求頁面返回的都是瀏覽器渲染好的華麗的頁面,那要怎麼放回華麗的頁面呢?

頁面嘛,就是 HTML + CSS + JS 渲染出來的,所以我們也可以把 HTML檔案當成資料放在響應體裡直接返回回去

新建一個功能的步驟還是複習一下

  • 在 urls.py 裡面加一條路由與檢視函式的對應關係
  • 在 views.py 裡面加上那個檢視函式,並寫好內部邏輯程式碼
  • 重啟伺服器,瀏覽器開啟頁面訪問

返回靜態頁面--案例

這裡咱們就接著上面的 new_url 寫,用他來返回 一個網頁

新建一個 templates 資料夾,專門用來放 HTML 檔案

templates/new_url.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
    <h1>New URL</h1>
    <h5>Wellcome!</h5>
</body>
</html>

views.py

def index(env):
    return 'index'


def login(env):
    return 'login'


def new_url(env):
    # 讀取並把 new_url 檔案返回給客戶端(瀏覽器)
    with open(r'templates/new_url.html', 'rb') as f:
        html_data = f.read()
    return html_data.decode('utf-8')  # 因為 run 函式那裡做了 encode, 而二進位制資料沒有 encode這個方法,所以這裡先解碼一下,然後那邊再編碼一下

重啟伺服器,使用瀏覽器訪問

上面提到了靜態頁面,那什麼是靜態頁面?什麼又是動態頁面呢?

  • 靜態網頁:純html網頁,資料是寫死的,所有同url的請求拿到的資料都是一樣的

  • 動態網頁:後端資料拼接,資料不是寫死的,是動態拼接的,比如:

    ​ 後端實時獲取當前時間“傳遞”(塞)給前端頁面展示

    ​ 後端從資料庫獲取資料“傳遞”給前端頁面展示

實現返回時間--插值思路(動態頁面)

要怎麼在 html 裡插入時間呢?

往 html 裡的插入?那替換好像也可以達到效果啊?

html_data = f.read() ? 好像 html 被讀出出來了,而且還是二進位制的,二進位制可以 decode 變成字串,字串有 replace方法可以替換字串,那我隨便在網頁裡寫點內容,然後替換成時間?

先把基礎歩鄹做好

templates/get_time.html 編寫展示頁面

put_times_here 用來做佔位符,一會兒給他替換成時間

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
    <h1>北京時間:</h1>
    <h1>put_time_here</h1>
</body>
</html>

urls.py 路由與檢視函式對應關係

from views import *

urls = [
    ('/index', index),
    ('/login', login),
    ('/new_url', new_url),
    ('/get_time', get_time),
]

views.py 實現檢視函式

def index(env):
    return 'index'


def login(env):
    return 'login'


def new_url(env):
    # 讀取並把 new_url 檔案返回給客戶端(瀏覽器)
    with open(r'templates/new_url.html', 'rb') as f:
        html_data = f.read()
    return html_data


def get_time(env):
    # 讀取並把 get_time 檔案返回給客戶端(瀏覽器)
    with open(r'templates/get_time.html', 'rb') as f:
        html_data = f.read().decode('utf-8')
    import time
    html_data = html_data.replace('put_time_here', time.strftime("%Y-%m-%d %X"))
    return html_data

重啟伺服器並開啟瀏覽器訪問 http://127.0.0.1:8080/get_time

關鍵思路:相當於佔位符,字串替換,後期把前端要替換的字元的格式統一規定下,方便閱讀與統一處理,這其實也就是目前的模版語法的雛形

我們只需要把處理好的字串(HTML格式的)返回給瀏覽器,待瀏覽器渲染即可有頁面效果

利用 jinja2 模組實現動態頁面

jinja2模組有著一套 模板語法,可以幫我更方便地在 html 寫程式碼(就想寫後臺程式碼一樣),讓前端也能夠使用後端的一些語法操作後端傳入的資料

安裝 jinja2

jinja2 並不是 python 直譯器自帶的,所以需要我們自己安裝

​ 由於 flask 框架是依賴於 jinja2 的,所下載 flask 框架也會自帶把 jinja2 模組裝上

命令列執行,pip3 install jinja2 或圖形化操作安裝(參考 Django 的安裝方法)

初步使用

這裡只是知道有模板語法這麼一個東西可以讓我們很方便的往 html 寫一些變數一樣的東西,並不會講 jinja2 的語法,後續會有的

案例--展示字典資訊

urls.py

from views import *

urls = [
    ('/index', index),
    ('/login', login),
    ('/new_url', new_url),
    ('/get_time', get_time),
    ('/show_dic', show_dic),
]

views.py

def index(env):
    return 'index'


def login(env):
    return 'login'


def new_url(env):
    # 讀取並把 new_url 檔案返回給客戶端(瀏覽器)
    with open(r'templates/new_url.html', 'rb') as f:
        html_data = f.read()
    return html_data


def get_time(env):
    # 讀取並把 get_time 檔案返回給客戶端(瀏覽器)
    with open(r'templates/get_time.html', 'rb') as f:
        html_data = f.read().decode('utf-8')
    import time
    html_data = html_data.replace('put_time_here', time.strftime("%Y-%m-%d %X"))
    return html_data


def show_dic(env):
    user = {
        "username": "jason",
        "age": 18,
    }
    with open(r'templates/show_dic.html', 'rb') as f:
        html_data = f.read()

    # 使用 jinja2 的模板語法來將資料渲染到頁面上(替換佔位符)
    from jinja2 import Template
    tmp = Template(html_data)
    res = tmp.render(dic=user)  # 將字典 user 傳遞給前端頁面,前端頁面通過變數名 dic 就能夠獲取到該字典
    return res

templates/show_dic.html 寫頁面

jinja2 給字典擴充套件了點語法支援({{ dic.username }}

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
    <h1>Nice to meet you~ i'm {{ dic.username }} , and i'm {{ dic.age }} years old.</h1>
    <p>username: {{ dic['username']}}</p>
    <p>age: {{ dic.get('age')}}</p>
</body>
</html>

重啟伺服器並開啟瀏覽器訪問 http://127.0.0.1:8080/show_dic

為什麼說動態?

如果你改變了字典裡的值,那麼請求這個頁面,顯示的資料也會跟著改變(注意這個字典一般都是其他地方獲取過來的)


模板語法(貼近python語法): 前端也能夠使用後端的一些語法操作後端傳入的資料
    {{data.password}}  # jinja2 多給字典做了 點語法支援
    ... 其他的語法,寫法
    
    for 迴圈
    {%for user_dict in user_list%}
        <tr>
            <td>{{user_dict.id}}</td>
            <td>{{user_dict.name}}</td>
            <td>{{user_dict.password}}</td>
        </tr>
    {%endfor%}

進階案例--渲染資料庫資料到頁面

思路

pymsql 從資料庫取資料(指定成 列表套字典 的格式(DictCursor))
後臺 python 程式碼處理資料
交由 jinja2 模組語法渲染到 html 頁面上

資料條數不定怎麼辦?
    有多少條記錄就顯示多少條唄...迴圈?
    表格格式先寫好,然後迴圈渲染資料到標籤上(特定語法表示迴圈)

資料準備

建立資料庫 django_test_db,然後執行如下 SQL 命令

/*
 Navicat MySQL Data Transfer

 Source Server         : localhost-E
 Source Server Type    : MySQL
 Source Server Version : 50645
 Source Host           : localhost:3306
 Source Schema         : django_test_db

 Target Server Type    : MySQL
 Target Server Version : 50645
 File Encoding         : 65001

 Date: 15/09/2019 00:41:09
*/

SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;

-- ----------------------------
-- Table structure for user_info
-- ----------------------------
DROP TABLE IF EXISTS `user_info`;
CREATE TABLE `user_info`  (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `username` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL,
  `password` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
  PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 5 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Compact;

-- ----------------------------
-- Records of user_info
-- ----------------------------
INSERT INTO `user_info` VALUES (1, 'jason', '123');
INSERT INTO `user_info` VALUES (2, 'tank', '123');
INSERT INTO `user_info` VALUES (3, 'jerry', '123');
INSERT INTO `user_info` VALUES (4, 'egon', '456');

SET FOREIGN_KEY_CHECKS = 1;

配路由與檢視函式

urls.py

from views import *

urls = [
    ('/index', index),
    ('/login', login),
    ('/new_url', new_url),
    ('/get_time', get_time),
    ('/show_dic', show_dic),
    ('/get_users', get_users),
]

views.py

def index(env):
    return 'index'


def login(env):
    return 'login'


def new_url(env):
    # 讀取並把 new_url 檔案返回給客戶端(瀏覽器)
    with open(r'templates/new_url.html', 'rb') as f:
        html_data = f.read()
    return html_data


def get_time(env):
    # 讀取並把 get_time 檔案返回給客戶端(瀏覽器)
    with open(r'templates/get_time.html', 'rb') as f:
        html_data = f.read().decode('utf-8')
    import time
    html_data = html_data.replace('put_time_here', time.strftime("%Y-%m-%d %X"))
    return html_data


def show_dic(env):
    user = {
        "username": "jason",
        "age": 18,
    }
    with open(r'templates/show_dic.html', 'rb') as f:
        html_data = f.read()

    # 使用 jinja2 的模板語法來將資料渲染到頁面上(替換佔位符)
    from jinja2 import Template
    tmp = Template(html_data)
    res = tmp.render(dic=user)  # 將字典 user 傳遞給前端頁面,前端頁面通過變數名 dic 就能夠獲取到該字典
    return res


# 先寫個空函式在這裡佔位置,去把 pymysql 查資料的寫了再過來完善
def get_users(env):
    # 從資料庫取到資料
    import op_mysql
    user_list = op_mysql.get_users()

    with open(r'templates/get_users.html', 'r', encoding='utf-8') as f:
        html_data = f.read()

    from jinja2 import Template  # 其實這個引入應該放在頁面最上方去的,但為了漸進式演示程式碼推進過程,就放在這裡了
    tmp = Template(html_data)
    res = tmp.render(user_list=user_list)
    return res

op_mysql.py 如果你的配置不一樣要自己改過來

import pymysql


def get_cursor():
    server = pymysql.connect(
        # 根據自己電腦上 mysql 的情況配置這一塊的內容
        host='127.0.0.1',
        port=3306,
        user='root',
        password='000000',
        charset='utf8',  # 千萬注意這裡是 utf8 !
        database='django_test_db',
        autocommit=True
    )
    cursor = server.cursor(pymysql.cursors.DictCursor)
    return cursor


def get_users():
    cursor = get_cursor()  # 連線資料庫

    sql = "select * from user_info"  # 把使用者的所有資訊查出來(一般不會把密碼放回給前端的,這裡只是為了做演示)
    affect_rows = cursor.execute(sql)
    user_list = cursor.fetchall()
    return user_list

templates/get_users.html 使用者資訊展示頁面

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <!--  引入jquery bootstrap 檔案的 CDN  -->
    <script src="https://cdn.bootcss.com/jquery/3.4.1/jquery.min.js"></script>
    <link href="https://cdn.bootcss.com/twitter-bootstrap/3.4.1/css/bootstrap.min.css" rel="stylesheet">
    <script src="https://cdn.bootcss.com/twitter-bootstrap/3.4.1/js/bootstrap.min.js"></script>
</head>
<body>
    <div class="container">
        <div class="row">

            <div class="col-md-8 col-md-offset-2">
                <h2 class="text-center">使用者資料展示</h2>
                <table class="table table-hover table-bordered table-striped">
                    <thead>
                    <tr>
                        <th>id</th>
                        <th>username</th>
                        <th>password</th>
                    </tr>
                    </thead>
                    <tbody>
                    <!-- jinja2 的模版語法(for迴圈) -->
                    {%for user_dict in user_list%}
                    <tr>
                        <td>{{user_dict.id}}</td>
                        <td>{{user_dict.username}}</td>
                        <td>{{user_dict.password}}</td>
                    </tr>
                    {%endfor%}
                    </tbody>
                </table>
            </div>

        </div>
    </div>
</body>
</html>

用瀏覽器訪問 http://127.0.0.1:8080/get_users,重啟伺服器,在切回瀏覽器即可看到頁面效果

推導流程與小總結

1.純手擼web框架
    1.手動書寫socket程式碼
    2.手動處理http資料
    
2.基於wsgiref模組幫助我們處理scoket以及http資料(頂掉上面的歩鄹)
    wsgiref模組
        1.請求來的時候 解析http資料幫你打包成一個字典傳輸給你 便於你操作各項資料
        2.響應走的時候 自動幫你把資料再打包成符合http協議格式的樣子 再返回給前端
    
3.封裝路由與檢視函式對應關係 以及檢視函式檔案 網站用到的所有的html檔案全部放在了templates資料夾下
    1.urls.py 路由與檢視函式對應關係
    2.views.py 檢視函式 (檢視函式不單單指函式 也可以是類)
    3.templates 模板資料夾
    
4.基於jinja2實現模板的渲染
    模板的渲染
        後端生成好資料 通過某種方式傳遞給前端頁面使用(前端頁面可以基於模板語法更加快捷簡便使用後端傳過來的資料)

流程圖

小擴充套件

在不知道是要 encode 還是 decode 的時候,可以用一下方法

二進位制資料對應的肯定是 decode 解碼 成字串呀

字串對應的肯定是 encode 編碼成二進位制資料呀

資料型別轉換技巧(處理編碼)(資料 + encoding)

# 轉成 bytes 型別
bytes(data, encoding='utf-8')

# 轉成 str 型別
str(data, encoding='utf-8')

python三大Web主流框架分析對比

Django

大而全,自帶的功能特別特別多,就類似於航空母艦

缺點:有時過於笨重(小專案很多用不到)

Flask

短小精悍,自帶的功能特別少,全都是依賴於第三方元件(模組)

第三方元件特別多 --> 如果把所有的第三方元件加起來,完全可以蓋過django

缺點:比較受限於第三方的開發者(可能有bug等)

Tornado

天生的非同步非阻塞框架,速度特別快,能夠抗住高併發

​ 可以開發遊戲伺服器(但開發遊戲,還是 C 和C++用的多,執行效率更快)

手擼三大部分在框架中的情況對比

前面的手擼推導過程,整個框架過程大致可以分為以下三部分

A:socket處理請求的接收與響應的傳送

B:路由與檢視函式

C:模板語法給動態頁面渲染資料

Django

A:用的別人的 wsgiref 模組
B:自帶路由與檢視函式檔案
C:自帶一套模板語法

Flask

A:用的別人的werkzeug 模組(基於 wsgiref 封裝的)
B:自帶路由與檢視函式檔案
C:用的別人的jinja2

Tornado

A,B,C全都有自己的實現

Django的下載安裝基本使用

參見我的另一篇部落格:Django-下載安裝-配置-建立django專案-三板斧簡單使用