python之MiniWeb框架


本次學習MiniWeb框架的目的並不是能夠完成框架的編寫,而是需要學習框架編寫中的思想,主要是理解路由表的定義和自動維護、視圖函數的作用、前后端分離的開發思想,以及WSGI協議中的application函數的實現與調用的思想。

以往,我們多完成的是靜態web服務器,主要處理的都是一些已經‘寫死’的數據,那么今天,我們來學習一下動態數據的處理。

說到動態數據,我們就需要了解一個東西,那就是web框架。

所謂web框架簡單地說就是用來處理數據或模板的一個py程序。

那么接下,我就簡單的給大家簡述一下一個瀏覽器訪問動態數據的整體流程。

WSGI服務器網管接口(Web Server Gateway Interface)是為python語言定義的一種服務器與框架進行通信的簡單接口,其接口原理就是實現application函數。
application函數格式如下:

def application(environ, func): func('200 OK', [('Content-Type', 'text/html;charset=utf-8')]) return 'Hello MiniWeb'

函數的第一個參數environ類型是一個字典,用來接收封裝到字典內的請求資源路徑。

函數的第二個參數func類型是一個函數,用來接收調用時傳遞的函數引用。

application函數在是在框架中實現,框架就是處理數據的py文件;在服務器端調用。

實現application函數並且完成數據處理的py文件我們就可以稱它是WSGI接口。

首先,我們先看一張圖片

 

這張圖片完美的闡述了從瀏覽器到服務器到web框架的應用。

接下來我將給大家進行簡單的闡述:

首先,大家需要明確一點本次開發中以.html后綴的文件為動態資源,其余均為靜態資源

1.瀏覽器發送HTTP請求報文給服務器,

2.服務器根據HTTP請求報文的請求路徑進行判斷,

如果請求的是靜態資源,那么服務器直接將靜態資源的數據讀出來然后拼接成HTTP響應報文返回給瀏覽器;

如果請求的是動態資源,那么服務器會將動態資源請求發送給web框架。

3.web框架接收到服務器轉發的請求后,先對請求進行判斷,

如果是請求模版那么web框架將會去模版中查找請求的模板,如果查詢到請求的模板那就把模板數據返回給web服務器,再有服務器返回給瀏覽器,

如果請求的是數據那么web框架將會去數據庫中進行操作,將查詢等操作的數據或返回值,返回給web服務器,再有服務器返回給瀏覽器。

至此一個整體流程就闡述完畢了。

1.web服務器代碼實現:

# 1.導入socket模塊
import socket import threading import FreamWork # 創建服務器類
class HttpServerSocket(object): # 給服務器類的對象設置屬性
    def __init__(self): # 2.創建Socket對象
        self.server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) # 3.設置端口復用
 self.server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, True) # 4.綁定端口
        self.server_socket.bind(('', 8000)) # 5.設置監聽
        self.server_socket.listen(128) def start(self): while True: # 6.等待客戶端連接
            client_socket, ip_port = self.server_socket.accept() # gevent.spawn(self.task, client_socket, ip_port)
            print("上線了", ip_port) threading.Thread(target=self.task, args=(client_socket, ip_port), daemon=True).start() def task(self, client_socket, ip_port): # 7.接收數據
        recv_data = client_socket.recv(1024).decode('utf-8') print(recv_data) if not recv_data: print("客戶端:%s下線了,端口號為:%s" % ip_port) return

        # 8.發送數據
        # 判斷請求資源是否包含參數
        # 請求行格式:GET /index.html HTTP/1.1
        recv_path = recv_data.split()[1] # print("第一次分割",recv_path)
        # 如果有參數則以?分割
        if '?' in recv_path: real_recv_path = recv_path.split('?')[0] # print("?分割",real_recv_path)
        else: # 如果沒有參數,則保持請求路徑不變
            real_recv_path = recv_path # print("無?分割",real_recv_path)

        # 設置沒指定資源路徑,默認返回index.html
        if real_recv_path == '/': real_recv_path = '/index.html'

        # 判斷請求資源是靜態資源還是動態資源
        if real_recv_path.endswith('.html'): env = {'PATH_INFO': real_recv_path} # 調用框架中的application函數 response_body = FreamWork.application(env, self.start_response) response_line = 'HTTP/1.1 %s\r\n' % self.status response_header = 'Server: PWS/1.0\r\n' # self.response_header 接收的是列表中保存的元組需要進行解包處理 response_header += '%s :%s\r\n' % self.response_header[0] send_data = (response_line + response_header + '\r\n' + response_body).encode('utf8') client_socket.send(send_data) client_socket.close() else: # 判斷請求的資源路徑是否存在
            try: with open(f"static{real_recv_path}", "rb") as file: response_body = file.read() except Exception as e: # 如果不存在則返回404
                response_line = 'HTTP/1.1 404 NOT FOUND\r\n' response_header = 'Server: PWS/1.0\r\n' response_body = 'sorry nor found page!\r\n'.capitalize() send_data = (response_line + response_header + '\r\n' + response_body).encode('utf-8') client_socket.send(send_data) else: # 如果存在則換回請求的頁面信息
                response_line = 'HTTP/1.1 200 OK\r\n' response_header = 'Server: PWS/1.0\r\n' send_data = (response_line + response_header + '\r\n').encode('utf-8') + response_body client_socket.send(send_data) finally: # 斷開與客戶端連接
 client_socket.close() def start_response(self, status, response_header): self.status = status self.response_header = response_header def __del__(self): # 當服務端程序結束時停止服務器服務
 self.server_socket.close() def main(): http_socket = HttpServerSocket() http_socket.start() if __name__ == '__main__': main()

核心代碼(代碼段中顏色為紅色並加粗的代碼)解讀:

1.通過分解后的請求資源路徑的后綴判斷請求的是否是html頁面,如果是則認為請求的是動態資源;

2.將動態資源路徑封裝到一個字典中,並將字典和函數的引用傳遞給application函數,

3.web框架(application函數)根據傳遞的資源路徑去模板中查找是否含有請求的模板,如果有則讀取模版數據並返回;

4.接收到web框架(application函數)返回的數據,並拼接HTTP響應報文,然后通過瀏覽器套接字將HTTP響應報文發送給瀏覽器,最后關閉與瀏覽器連接。

2.MiniWeb框架代碼實現:

采用前后端分離思想進行數據傳輸,在數據傳輸過程中,需要將數據轉換成JSON數據,

這時候需要注意數據庫中的Decimal類型Datetime類型為數據庫獨有類型的數據都需要轉換成字符串才能夠進行數據類型轉換;

否則數據轉換不成功,則無法為Ajax提供數據支持,那么前段頁面會一直提示,Ajax請求失敗。

import json
import db

# 定義路由表
router_table = {}


# 定義接口函數
def application(environ, start_response):
    start_response('200 OK', [('Content-Type', 'text/html;charset=utf-8')])
    path = environ['PATH_INFO']
    func = error
    if path in router_table:
        func = router_table[path]
    response_body = func()
    return response_body


# 定義裝飾器自動維護路由表
def router(url):
    def decorator(func):
        def inner(*args, **kwargs):
            body = func(*args, **kwargs)
            return body

        router_table[url] = inner
        return inner

    return decorator


# 定義視圖函數
@router('/index.html')
def index():
    with open('templates/index.html', 'r', encoding='utf-8') as file:
        body = file.read()
    return body


@router('/center.html')
def center():
    with open('templates/center.html', 'r', encoding='utf-8') as file:
        body = file.read()
    return body


# 定義發送JSON數據的函數
@router('/data.html')
def josn_data():
    sql = '''select * from info;'''
    result = db.operation_data(sql)
    # JSON數據格式:[{},{}]
    data = []
    for data_tuple in result:
        data_dict = {}
        data_dict['id'] = data_tuple[0]
        data_dict['code'] = data_tuple[1]
        data_dict['short'] = data_tuple[2]
        data_dict['chg'] = data_tuple[3]
        data_dict['turnover'] = data_tuple[4]
        # decimal類型只存在於數據庫中,所以需要轉換成字符串進行顯示
        data_dict['price'] = str(data_tuple[5])
        data_dict['highs'] = str(data_tuple[6])
        # datetime類型只存在於數據庫中,所以需要類型轉換
        data_dict['time'] = str(data_tuple[7])
        data.append(data_dict)

    # 將列表轉化成JSON數據
    json_data = json.dumps(data, ensure_ascii=False)
    return json_data


@router('/center_data.html')
def center_data():
    sql = ''' select i.code, i.short, i.chg, i.turnover,i.price, i.highs, f.note_info from info i inner join focus f on i.id = f.info_id '''
    result = db.operation_data(sql)
    # 定義一個列表,用來保存所有的記錄 (列表對應的是 json 中的數組)
    data = []
    # 遍歷查詢結果
    for t in result:
        # 定義一個字典用來保存每一條記錄(字典對應的是 json 中的對象)
        dd = {}
        # 將元組中的所有數據保存到字典中
        dd['code'] = t[0]
        dd['short'] = t[1]
        dd['chg'] = t[2]
        dd['turnover'] = t[3]
        dd['price'] = str(t[4])  # 坑一: 由於下面兩個字段的值在數據庫是是 Decimal 類型,在轉換 json 時不能直接轉換,所以轉成字符串
        dd['highs'] = str(t[5])
        dd['info'] = t[6]
        data.append(dd)

    # 將轉換后的列表轉換成 json 字符串
    json_str = json.dumps(data, ensure_ascii=False)  # 坑二: 在這轉換時,要加參數二,不以 Ascii 碼進行解析數據
    print(json_str)

    return json_str


def error():
    return '<h1>頁面加載失敗!</h1>'

JSON的數據格式:

[{"key":"value"},{"key":"value"},{"key":"value"}]

在JSON數據轉換時需要注意,json.dumps()會默認采用ASCII碼進項解碼轉換,想要不使用ASCII進行轉碼就需要設置ensure_ascii參數,將它設置成False即可解決問題。

這里給大家簡單闡述一下路由表的概念:

路由表就是一個用請求地址作為Key,裝飾器內層函數的引用作為Value而形成的鍵值對組成的字典。

其格式為:

{ '/index.html':inner, '/error.html':inner }

對於路由表的自動維護,我們可以通過編寫帶參數的裝飾器來實現:

router_table = {} def router(url): def decorator(func): def inner(*args, **kwargs): func(*args, **kwargs) router_table[url] = inner return inner return decorator

通過@router('/index.html')語法糖來給路由表中添加數據。

其主要流程:

1.先執行router('/index.html'),然后將裝飾器decorator函數的引用返回;

2.在通過@decorator的引用,將視圖函數傳遞給func,將inner的引用及url組成鍵值對保存到路由表中;

這里再給大家擴展一個概念,視圖函數是MVT開發模式中的V也就是View,其主要功能是處理數據。

3.數據庫操作代碼實現:

在編寫數據庫操作的代碼時,需要注意的時每個函數返回值的類型,

除db_connect()返回的是數據庫的連接對象外,

其余函數返回的均為查詢后的數據,在調用函數時要注意。

import pymysql


def connect_db():
    db = pymysql.connect(host='180.76.105.161', port=3306, user='root', password='root', database='stock_db',
                         charset='utf8')
    # 此處返回的是數據庫連接對象
    return db


def operation_data(sql):
    db = connect_db()
    cur = db.cursor()
    cur.execute(sql)
    data = cur.fetchall()
    cur.close()
    db.close()
    # 此處返回的是數據庫查詢后返回的數據,而不是對象
    return data

最后,總結一下MiniWeb開發的大概流程:

1.在原有的TCP服務端的基礎上加上一個請求資源的判斷,即判斷是否是動態資源;

2.如果是動態資源,就將動態資源的請求路徑發送給框架,

3.框架在進行判斷,如果動態資源是模版那就讀出模版的數據並返回給服務器,最后再由服務器拼接響應報文給客戶端,

4.如果動態資源請求的是數據,那么框架就回到數據庫中將數據查詢出來,並且拼接成JSON數據格式的字符串,再將JSON數據格式的字符串轉換成JSON數據,最后在返回給服務器,最后再由服務器拼接響應報文並發送給客戶端。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM