由上一篇 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''
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()
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() # 啓動服務端並一直運行
隨着業務愈來愈多, 功能愈來愈多, 將全部代碼放在同一個文件會帶來不少沒必要要的麻煩, 因而就須要咱們分文件放置相關的代碼html
def index_func(request): return 'index' def login_func(request): return 'login' def error(request): return '404 errors' def xxx(request): pass
from views import * urls = [ ('/index',index_func), ('/login',login_func) ]
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() # 啓動服務端並一直運行
靜態網頁 : 數據都是寫死的, 固定不變的python
解決了不一樣URL返回不一樣內容的問題, 可是我不想僅僅返回幾個字符串, 我想給瀏覽器返回完整的HTML內容, 對此咱們只須要經過 open 打開 HTML文件將內容讀出來再發送給瀏覽器就好了mysql
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'
動態頁面 : 數據來源於後端 (代碼或者數據庫)jquery
# 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'
上面咱們須要手動的 replace 更換 HTML 文件裏的代碼, 下面咱們使用 jinja2
來優化 replaceweb
jinja2 模塊的做用 : jiaja2的原理就是字符串替換,咱們只要在HTML頁面中遵循 jinja2 的語法規則寫上,其內部就會按照指定的語法進行相應的替換,從而達到動態的返回內容.ajax
jinja2 模板語法sql
// 定義變量, 雙花括號 {{ user_list }} // for 循環, 花括號 + 百分號 {% for user_dict in user_list %} {{ user_dict.id }} # 支持Python操做對象的方式取值 {% endfor %}
# 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也會自動下載該模塊數據庫
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'
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>
以上就實現了從數據庫獲取數據的動態頁面
// 瀏覽器客戶端 // 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文件 // 數據庫
手寫 Web 框架
wsgiref 模塊
1.封裝了socket代碼 2.處理了http數據格式
"urls.py" : 路由與視圖函數對應關係 "views.py" : 視圖函數 "templates" : 模板文件夾(存放HTML文件) 1.第一步添加路由與視圖函數的對應關係 2.去views中書寫功能代碼 3.若是須要使用到html則去模板文件夾中操做
// 定義變量, 雙花括號 {{ user_list }} // for 循環, 花括號 + 百分號 {% for user_dict in user_list %} {{ user_dict.id }} # 支持Python操做對象的方式取值 {% endfor %}
特色 : 大而全,自帶的功能組件很是多!相似於航空母艦 不足 : 有時候過於笨重
特色 : 小而精 自帶的功能特別特別特別的少, 相似於遊騎兵, 但第三方的模塊特別特別特別的多,若是將flask第三方的模塊加起來徹底能夠蓋過django 不足 : 比較依賴於第三方的開發者 ps : 三行代碼就能夠啓動一個 flask 後端服務
異步非阻塞 速度很是的快 快到能夠開發遊戲服務器
A : 用的是別人的 (wsgiref模塊) B : 用的是本身的 C : 用的是本身的 (沒有jinja2好用 可是也很方便)
A : 用的是別人的 (werkzeug(內部仍是wsgiref模塊)) B : 本身寫的 C : 用的別人的 (jinja2)
A,B,C都是本身寫的