本次學習MiniWeb框架的目的並非可以完成框架的編寫,而是須要學習框架編寫中的思想,主要是理解路由表的定義和自動維護、視圖函數的做用、先後端分離的開發思想,以及WSGI協議中的application函數的實現與調用的思想。html
以往,咱們多完成的是靜態web服務器,主要處理的都是一些已經‘寫死’的數據,那麼今天,咱們來學習一下動態數據的處理。python
說到動態數據,咱們就須要瞭解一個東西,那就是web框架。mysql
所謂web框架簡單地說就是用來處理數據或模板的一個py程序。web
那麼接下,我就簡單的給你們簡述一下一個瀏覽器訪問動態數據的總體流程。sql
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類型是一個字典,用來接收封裝到字典內的請求資源路徑。json
函數的第二個參數func類型是一個函數,用來接收調用時傳遞的函數引用。後端
application函數在是在框架中實現,框架就是處理數據的py文件;在服務器端調用。api
實現application函數而且完成數據處理的py文件咱們就能夠稱它是WSGI接口。數組
首先,咱們先看一張圖片
這張圖片完美的闡述了從瀏覽器到服務器到web框架的應用。
首先,你們須要明確一點本次開發中以.html後綴的文件爲動態資源,其他均爲靜態資源
1.瀏覽器發送HTTP請求報文給服務器,
2.服務器根據HTTP請求報文的請求路徑進行判斷,
若是請求的是靜態資源,那麼服務器直接將靜態資源的數據讀出來而後拼接成HTTP響應報文返回給瀏覽器;
若是請求的是動態資源,那麼服務器會將動態資源請求發送給web框架。
3.web框架接收到服務器轉發的請求後,先對請求進行判斷,
若是是請求模版那麼web框架將會去模版中查找請求的模板,若是查詢到請求的模板那就把模板數據返回給web服務器,再有服務器返回給瀏覽器,
若是請求的是數據那麼web框架將會去數據庫中進行操做,將查詢等操做的數據或返回值,返回給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響應報文發送給瀏覽器,最後關閉與瀏覽器鏈接。
採用先後端分離思想進行數據傳輸,在數據傳輸過程當中,須要將數據轉換成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>'
[{"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,其主要功能是處理數據。
在編寫數據庫操做的代碼時,須要注意的時每一個函數返回值的類型,
除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
1.在原有的TCP服務端的基礎上加上一個請求資源的判斷,即判斷是不是動態資源;
2.若是是動態資源,就將動態資源的請求路徑發送給框架,
3.框架在進行判斷,若是動態資源是模版那就讀出模版的數據並返回給服務器,最後再由服務器拼接響應報文給客戶端,
4.若是動態資源請求的是數據,那麼框架就回到數據庫中將數據查詢出來,而且拼接成JSON數據格式的字符串,再將JSON數據格式的字符串轉換成JSON數據,最後在返回給服務器,最後再由服務器拼接響應報文併發送給客戶端。
原文出處:https://www.cnblogs.com/chao666/p/12068063.html