手擼web框架
手擼web框架
web框架是前端與資料庫的中介。
在我們所常見的web中,一般情況下,一個應用不會只有一個首頁介面,當頂級域名後有其他字尾時,會有其他的web介面,也就是將我們的網頁應用拆分開來,通過不同的字尾可以訪問不同的介面。如圖:
部落格園首頁https://www.cnblogs.com/
隨意點選網頁中的一篇部落格檢視:
socket服務端
我們可以socket寫一個服務端,然後通過http協議請求,通過瀏覽器來訪問到服務端的內容。
然後可以通過辨認請求體的請求首行,來得知網址的字尾,通過不同字尾返回給我們的網頁不同的內容。
請求體對比:
# 首頁 http://127.0.0.1:8080 GET / HTTP/1.1 Host: 127.0.0.1:8080 。。。(一系列鍵值對) Accept-Language: zh-CN,zh;q=0.9 /r/n # 字尾一個/index http://127.0.0.1:8080/index GET /index HTTP/1.1 。。。(一系列鍵值對) Accept-Language: zh-CN,zh;q=0.9 /r/n
只要是同一個域名就可以訪問到我的服務端,
而請求首行則會提供給我們請求方式,網址字尾(路由)協議型別
,基於路由做判斷,即可分發不同的html內容,達到應用介面劃分的效果。
import socket server = socket.socket() # TCP UDP server.bind(('127.0.0.1', 8080)) # IP PORT server.listen(5) # 半連線池 while True: sock, address = server.accept() # 等待連線 data = sock.recv(1024) # 位元組(bytes) # print(data.decode('utf8')) # 解碼列印 sock.send(b'HTTP/1.1 200 OK\r\n\r\n') # 簡易響應體,有響應首行和空頭、換行 data_str = data.decode('utf8') # 先轉換成字串 target_url = data_str.split(' ')[1] # 按照空格切割字串並取索引1對應的資料 # print(target_url) # /index /login /reg if target_url == '/index': # sock.send(b'index page') with open(r'myhtml01.html','rb') as f: # 可以傳送html檔案 sock.send(f.read()) elif target_url == '/login': sock.send(b'login page') # 可以直接傳送二進位制 else: sock.send(b'home page!')
以上,有許多問題叩待解決:
- socket程式碼過於重複
- 針對請求資料處理繁瑣
- 字尾匹配邏輯過於LowB
基於wsgiref模組封裝優化
wsgiref是一個內建模組,它幫我們:
- 封裝了socket程式碼
- 處理了請求資料
所有的請求首行、請求頭所代表的資訊被封裝為一個字典,可以通過鍵值拿資訊。
如我們的路由(網址字尾)就可以通過request.get('PATH_INFO')
來獲取
關於wsgiref模組需要注意以下功能:
關鍵字 | 功能 |
---|---|
request | 封裝好的請求體資料字典 |
response | 響應相關資料 |
make_server(ip,port,app) | 監聽該埠,一旦有請求,會將request、response傳給app函式執行 |
.serve_forever() | 將上一個功能返回的服務端物件開啟,進行實時監聽 |
用wsgiref搭建web
from wsgiref.simple_server import make_server
def run(request, response):
"""
:param request: 請求相關資料
:param response: 響應相關資料
:return: 返回給客戶端的真實資料
"""
response('200 OK', []) # 固定格式 不用管它
# print(request) 是一個處理之後的大字典
path_info = request.get('PATH_INFO')
if path_info == '/index':
return [b'index']
elif path_info == '/login':
return [b'login']
return [b'hello wsgiref module']
if __name__ == '__main__':
server = make_server('127.0.0.1', 8080, run)
# 實時監聽127.0.0.1:8080 一旦有請求過來自動給第三個引數加括號並傳引數呼叫
server.serve_forever() # 啟動服務端
仍然沒有解決分頁邏輯很low的問題。
說到處理if分支過多的問題,我們自然可以想到將路由和要返回的html頁面的對應關係存起來,然後為了結構更加清晰,又可以將對應關係存到一個檔案中urls.py
,html檔案群放到資料夾templates
中。而原本啟動服務端的核心功能就可以命名為view.py
檢視檔案。
分模組程式碼
# view.py
from wsgiref.simple_server import make_server
from urls import urls
import templates
import os
BASE_PATH = os.path.dirname(__file__)
TEMP_PATH = os.path.join(BASE_PATH, "templates")
def run(request, response):
response("200 OK", [])
path_info = request.get("PATH_INFO")
html_file = os.path.join(TEMP_PATH, "default.html")
for ur in urls:
if ur[0] == path_info:
html_file = ur[1]
html_file = os.path.join(TEMP_PATH, html_file)
print(html_file)
break
with open(html_file, 'rb') as f:
return [f.read()]
if __name__ == '__main__':
server = make_server("127.0.0.1", 8080, run)
server.serve_forever()
# urls.py
urls = [
("/index", "index.html"),
("/login", "login.html"),
]
# templates
default.html
index.html
login.html
# default.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>default</title>
</head>
<body>
<h1>404 not found</h1>
</body>
</html>
動靜態網頁
在我們平常接觸的web中,一般都是動態網頁,而我們上述所寫的是靜態網頁
- 網頁的html檔案資料被寫死了,無法通過程式修改即靜態網頁
- 網頁的html檔案中的資料來源於後端,每次請求都會讓後端的資料組織到html中返回給使用者的網頁叫做動態網頁。
為了做動態網頁,我們應該想到,後端的資料來源於資料庫,我們可以通過pymysql對接sql和python程式碼,我們還需要將python資料放到頁面中。
jinja2模組處理
那麼如何通過python程式碼向html檔案中放入資料呢,這顯然是一個複雜且重複性高的動作,我們可以通過第三方模組jinja2來相對方便的將python資料動態的嵌入到我們的html檔案中。
接下來的程式中要用到jinja2的一些方法:
關鍵字 | 功能 |
---|---|
Template() | 獲取字串,可以是html檔案內容 |
render | 將python資料以字典形式提交給html |
html中{} | render會識別這些符號進行一些操作。 |
jinja2匯入資料到html檔案
# view.py
from jinja2 import Template
def get_dict(request, data):
user_dict = {'name': 'jason', 'pwd': 123, 'hobby': 'read'}
new_list = [11, 22, 33, 44, 55, 66]
temp_obj = Template(data)
res = temp_obj.render({'user': user_dict, 'new_list': new_list}) # 將python資料提交給html檔案
return res # 將處理好的文字檔案返回
def run(request, response):
response("200 OK", [])
path_info = request.get("PATH_INFO")
html_file = os.path.join(TEMP_PATH, "default.html")
for ur in urls:
if ur[0] == path_info:
html_file = ur[1]
html_file = os.path.join(TEMP_PATH, html_file)
print(html_file)
break
with open(html_file, 'r', encoding='utf8') as f:
return [get_dict(request, f.read()).encode('utf8')] # 呼叫get_dict函式
if __name__ == '__main__':
server = make_server("127.0.0.1", 8080, run)
server.serve_forever()
# get_dict.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>get_dict</title>
</head>
<body>
<h1>字典資料展示</h1>
<p>{{ user }}</p> <!--對提交進來的資料進行應用 -->
<p>{{ user.name }}</p>
<p>{{ user['pwd'] }}</p>
<p>{{ user.get('hobby') }}</p>
<h1>列表資料展示</h1>
<p>
{% for i in new_list%}
<span>元素:{{ i }}</span>
{% endfor %}
</p>
</body>
</html>