🍖純手擼Web框架與主流框架

引入

由上一篇 HTTP 協議的介紹咱們知道, 想要瀏覽器能訪問到服務端的數據就必須按照 HTTP 協議來收發數據, 那麼接下來咱們就開始爲所要發送的消息加上相應狀態行, 實現一個合格的Web框架css

  • 先擺上請求數據格式好作對比
# 請求首行
b'GET / HTTP/1.1\r\n  

# 請求頭 (下面都是,一大堆的K:V鍵值對)
Host: 127.0.0.1:8080\r\n          
Connection: keep-alive\r\n
Cache-Control: max-age=0\r\n
Upgrade-Insecure-Requests: 1\r\n
User-Agent: Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.25 Safari/537.36 Core/1.70.3823.400 QQBrowser/10.7.4307.400\r\n
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8\r\n
Accept-Encoding: gzip, deflate, br\r\n
Accept-Language: zh-CN,zh;q=0.9\r\n
Cookie: csrftoken=WCzjKvmjOSdJbYKs0uIfPtiFfLl04FENb6p9CjypP7ZObcUpydaQPLZN0qPOVqwj\r\n

# 換行
\r\n'

# 請求體
b''

一.初代版本

1.根據 URL 中不一樣的路徑返回不一樣的內容

import socket

server = socket.socket()  # 默認就是TCP協議
server.bind(('127.0.0.1',8080))
server.listen(5)

while True:
    conn, addr = server.accept()  # 三次四次揮手
    data = conn.recv(1024)
    res = data.decode('utf8')
    conn.send(b'HTTP/1.1 200 OK\r\n\r\n')  # 請求首行,請求頭,空行
    path = res.split(' ')[1]   # 字符串切割獲取地址
    if path == '/index':       # 判斷地址
        # conn.send(b'index')  # 1.若是判斷成功則發送請求體
        with open(r'fh.html','rb') as f:  # 2.或者打開文件一內容做爲請求體發送
            data = f.read()
            conn.send(data)
    elif path == '/login':   # 1.若是判斷爲login
        conn.send(b'login')  # 2.就發送b'login'的請求體
    else:
        conn.send(b'404 error')  # 沒匹配到則返回404
    conn.close()

2.測試一下

image-20210313183439550

image-20210313183544244

image-20210313183759870

image-20210313184213730

3.存在的問題

  • 若是網址路徑不少, 服務端代碼就會由於 if...else... 變得很是重複
  • 而且手動的處理 HTTP 數據格式過於繁瑣

二.基於 wsgiref 模塊

1.wsgiref 模塊的做用

  • swgiref模塊幫助咱們封裝了socket 代碼
  • 幫咱們處理 http 格式的數據

2.便利之處

  • 請求來的時候幫助你自動拆分http格式數據並封裝成很是方便處理的數據格式(相似於字典)
  • 響應走的時候幫你將數據再打包成符合http格式的數據

3.實現代碼

from wsgiref.simple_server import make_server

# 以函數形式定義功能,擴展方便
def index_func(request):
    return 'index'

def login_func(request):
    return 'login'

def error(request):
    return '404 errors'

# 地址與功能的對應關係
urls = [
    ('/index',index_func),
    ('/login',login_func)
]
    
def run_server(request,response):
    """
    :param request:請求相關的全部數據,一個相似字典的形式,"PATH_INFO"正好就是咱們要找的地址
    :param response:響應相關的全部數據
    :return:
    """
    response('200 OK',[])  # 響應首行, 響應頭
    current_path = request.get("PATH_INFO")  # 找到路徑
    func = None  # 定義一個變量, 存儲匹配到的函數名
    for url in urls:
        if current_path == url[0]:
            func = url[1]  # 若是匹配到了則將函數名賦值給func
            break  # 匹配以後馬上結束循環
    if func:  # 而後判斷一下func是否被賦值了(也就是是否匹配到了)
        data = func(request)  # 執行函數拿到結果,request無關緊要,但放進去之後好擴展
    else:
        data = error(request)
    return [data.encode('utf-8')]

if __name__ == '__main__':
    server = make_server('127.0.0.1', 8080, run_server)  # 一旦被訪問將會交給run_server處理
    server.serve_forever()  # 啓動服務端並一直運行
  • 查看效果

image-20210313191605169

image-20210313191652421

image-20210313191716430

三.分文件盛放代碼

隨着業務愈來愈多, 功能愈來愈多, 將全部代碼放在同一個文件會帶來不少沒必要要的麻煩, 因而就須要咱們分文件放置相關的代碼html

1.views.py : 只放功能代碼

def index_func(request):
    return 'index'

def login_func(request):
    return 'login'

def error(request):
    return '404 errors'
    
def xxx(request):
    pass

2.urls.py : 存放路徑與功能的對應關係

from views import *

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

3.run.py : 只放請求與相應處理代碼

import wsgiref.simple_server import make_server
import urls import urls
import views import *

def run_server(request,response):
    """
    :param request:請求相關的全部數據,一個相似字典的形式,"PATH_INFO"正好就是咱們要找的地址
    :param response:響應相關的全部數據
    :return:
    """
    response('200 OK',[])  # 響應首行, 響應頭
    current_path = request.get("PATH_INFO")  # 找到路徑
    func = None  # 定義一個變量, 存儲匹配到的函數名
    for url in urls:
        if current_path == url[0]:
            func = url[1]  # 若是匹配到了則將函數名賦值給func
            break  # 匹配以後馬上結束循環
    if func:  # 而後判斷一下func是否被賦值了(也就是是否匹配到了)
        data = func(request)  # 執行函數拿到結果,request無關緊要,但放進去之後好擴展
    else:
        data = error(request)
    return [data.encode('utf-8')]

if __name__ == '__main__':
    server = make_server('127.0.0.1', 8080, run_server)  # 一旦被訪問將會交給run_server處理
    server.serve_forever()  # 啓動服務端並一直運行

四.返回HTML靜態網頁

靜態網頁 : 數據都是寫死的, 固定不變的python

解決了不一樣URL返回不一樣內容的問題, 可是我不想僅僅返回幾個字符串, 我想給瀏覽器返回完整的HTML內容, 對此咱們只須要經過 open 打開 HTML文件將內容讀出來再發送給瀏覽器就好了mysql

  • 修改 view.py 文件
def index_func(request):
    return 'index'

def login_func(request):
    with open(r"./login.html", "r", encoding="utf-8")as f:
        res = f.read()  #打開文件讀出內容,再返回文件內容
    return res

def error(request):
    return '404 errors'

image-20210313212151474

五.返回動態頁面

動態頁面 : 數據來源於後端 (代碼或者數據庫)jquery

1.示例1 : 訪問網址展現當前時間

  • 由後端生成時間不能改展現到HTML頁面中
# view.py 文件
def index_func(request):
    return 'index'

def login_func(request):
    from datetime import datetime
    now_time = datetime.now().strftime("%Y-%m-%d %X")
    with open(r"./login.html", "r", encoding="utf-8")as f:
        res = f.read().replace("datetime1",now_time)
    return res

def error(request):
    return '404 errors'

image-20210313215355990

2.示例二 : 從數據庫中拿到數據

上面咱們須要手動的 replace 更換 HTML 文件裏的代碼, 下面咱們使用 jinja2 來優化 replaceweb

1.jinja模塊介紹

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

  • jinja2 模板語法sql

// 定義變量, 雙花括號
{{ user_list }}

// for 循環, 花括號 + 百分號
{% for user_dict in user_list %}
{{ user_dict.id }}  # 支持Python操做對象的方式取值
{% endfor %}
  • 下載 jinja2 模塊
# pip3 install jinja2
豆瓣源 : http://pypi.douban.com/simple/
清華源: https://pypi.tuna.tsinghua.edu.cn/simple
使用方法 : pip install -i https://pypi.tuna.tsinghua.edu.cn/simple jinja2

ps : 該模塊是flask框架必備的模塊 因此下載flask也會自動下載該模塊數據庫

2.在數據庫中建立一張表來作準備

image-20210313221221285

  • view.py 文件
def index_func(request):
    return 'index'

def login_func(request):
    from datetime import datetime
    now_time = datetime.now().strftime("%Y-%m-%d %X")
    with open(r"./login.html", "r", encoding="utf-8")as f:
        res = f.read().replace("datetime1",now_time)
    return res

# 從數據庫獲取數據
def get_db_func(request):
    from jinja2 import Template
    import pymysql
    conn = pymysql.connect(host='127.0.0.1',
                           port=3306,
                           user='root',
                           password='123',
                           db='db1111',
                           charset='utf8',
                           autocommit=True)
    cursor = conn.cursor(cursor=pymysql.cursors.DictCursor)
    sql = 'select * from user_info'
    rows = cursor.execute(sql)
    user_list = cursor.fetchall()  # [{},{},{}] 格式
    with open(r'get_db.html','r',encoding='utf-8')as f:
        data = f.read()  # 字符串
    temp = Template(data)
    # 將user_list傳給HTML頁面, 在頁面中使用data_list調用
    res = temp.render(data_list=user_list)  
    return res

def error(request):
    return '404 errors'
  • urls.py 文件
from views import *

urls = [
    ('/index', index_func),
    ('/login', login_func),
    ('/get_db',get_db_func)  # 添加一個新功能
]
  • run.py 文件不須要變更django

  • get_db.html 文件

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <script src="https://cdn.bootcdn.net/ajax/libs/jquery/3.4.1/jquery.min.js"></script>
    <link href="https://cdn.bootcdn.net/ajax/libs/twitter-bootstrap/3.4.1/css/bootstrap.min.css" rel="stylesheet">
    <script src="https://cdn.bootcdn.net/ajax/libs/twitter-bootstrap/3.4.1/js/bootstrap.min.js"></script>
</head>
<body>
    <div class="container">
        <div class="row">
            <h2 class="text-center">用戶信息表</h2>
            <table class="table table-hover table-striped table-bordered">
                <thead>
                <tr>
                    <th>ID</th>
                    <th>name</th>
                    <th>age</th>
                </tr>
                </thead>
                <tbody>
                {% for user_dict in data_list %}     {# 🔰從列表中循環取出字典 #}
                    <tr>
                        <td>{{ user_dict.id }}</td>  {# 🔰以相似Python中字典的方式取值 #}
                        <td>{{ user_dict.name }}</td>
                        <td>{{ user_dict.age }}</td>
                    </tr>
                {% endfor %}
                </tbody>
            </table>
        </div>
    </div>
</body>
</html>
  • 效果

image-20210313223626633

  • 數據庫添加一條記錄, 頁面刷新

image-20210313223955238

image-20210313224029363

以上就實現了從數據庫獲取數據的動態頁面

六.總結

1.自定義版本的Web框架流程圖

d7179dd031e50bf9d32bdc279a80a8e

// 瀏覽器客戶端

// wsgiref模塊
    請求來: 處理瀏覽器請求, 解析瀏覽器http格式的數據, 封裝成大字典(PATH_INFO中存放的用訪問資源的路徑)
    響應去: 將數據打包成符合http格式 再返回給瀏覽器
        
// 後端 
    "urls.py": 找用處輸入的路徑有沒有與視圖函數的對應關係. 若是有則取到views.py找對應的視圖函數. 
    "views.py": 
        功能1(靜態): 視圖函數找templates中的html文件, 返回給wsgiref作HTTP格式的封包處理, 再返回給瀏覽器.
        功能2(動態): 視圖函數經過pymysql連接數據庫, 經過jinja2模板語法將數據庫中取出的數據在tmpelates文件夾下的html文件作一個數據的動態渲染, 最後返回給wsgiref作HTTP格式的封包處理, 再返回給瀏覽器.
        功能3(動態): 也能夠經過jinja2模板語法對tmpelates文件夾下的html文件進行數據的動態渲染, 渲染完畢, 再通過wsgiref作HTTP格式的封包處理, 再返回給瀏覽器.
        
    "templates": html文件
    
// 數據庫

2.步驟總結

  • 手寫 Web 框架

  • wsgiref 模塊

1.封裝了socket代碼
2.處理了http數據格式
  • 根據不一樣功能拆分不一樣文件
"urls.py" : 路由與視圖函數對應關係
"views.py" : 視圖函數
"templates" : 模板文件夾(存放HTML文件)
    
1.第一步添加路由與視圖函數的對應關係
2.去views中書寫功能代碼
3.若是須要使用到html則去模板文件夾中操做
  • jinja2 模板語法
// 定義變量, 雙花括號
{{ user_list }}

// for 循環, 花括號 + 百分號
{% for user_dict in user_list %}
{{ user_dict.id }}  # 支持Python操做對象的方式取值
{% endfor %}
  • 流程圖

七.主流 web 框架

1.三大主流 web 框架

  • django 框架
特色 : 大而全,自帶的功能組件很是多!相似於航空母艦
不足 : 有時候過於笨重
  • flask 框架
特色 : 小而精  自帶的功能特別特別特別的少, 相似於遊騎兵, 但第三方的模塊特別特別特別的多,若是將flask第三方的模塊加起來徹底能夠蓋過django
不足 : 比較依賴於第三方的開發者
ps : 三行代碼就能夠啓動一個 flask 後端服務
  • tornado 框架
異步非阻塞  速度很是的快 快到能夠開發遊戲服務器
  • Sanic 框架
  • FastAPI 框架
  • .....

2.Web框架三部分

  • socket 部分
  • 路由與視圖匹配部分
  • 模板語法部分

3.三種主流框架三部分的使用狀況

  • Django
A : 用的是別人的  (wsgiref模塊)
B : 用的是本身的
C : 用的是本身的  (沒有jinja2好用 可是也很方便)
  • flask
A : 用的是別人的  (werkzeug(內部仍是wsgiref模塊))
B : 本身寫的
C : 用的別人的    (jinja2)
  • tornado
A,B,C都是本身寫的
相關文章
相關標籤/搜索