Web框架本質javascript
1.本質css
對於全部的Web應用,本質上其實就是一個socket服務端,用戶的瀏覽器其實就是一個socket客戶端。html
2.原始版Webjava
import socket sk = socket.socket() sk.bind(("127.0.0.1", 80)) sk.listen() while True: conn, addr = sk.accept() data = conn.recv(8096) conn.send(b"OK") conn.close()
咱們在瀏覽器上輸入:http://127.0.0.1/ 就能夠看到服務端發來的"ok"了。能夠說Web服務本質上都是在這十幾行代碼基礎上擴展出來的。用戶的瀏覽器一輸入網址,會給服務端發送數據,那瀏覽器會發送什麼數據?怎麼發?這個誰來定? 這個規則就是HTTP協議,之後瀏覽器發送請求信息也好,服務器回覆響應信息也罷,都要按照這個規則來。HTTP協議主要規定了客戶端和服務器之間的通訊格式,那HTTP協議是怎麼規定消息格式的呢?讓咱們首先打印下咱們在服務端接收到的消息是什麼。python
import socket sk = socket.socket() sk.bind(("127.0.0.1", 80)) sk.listen(5) while True: conn, addr = sk.accept() data = conn.recv(8096) print(data) # 將瀏覽器發來的消息打印出來 conn.send(b"OK") conn.close()
下圖左邊就是服務端接收到瀏覽器的數據,右邊是瀏覽器收到的響應數據。mysql
3.HTTP工做原理jquery
a.客戶端鏈接到Web服務器
一個HTTP客戶端,一般是瀏覽器,與Web服務器的HTTP端口(默認爲80)創建一個TCP套接字鏈接。web
b. 發送HTTP請求
經過TCP套接字,客戶端向Web服務器發送一個文本的請求報文,一個請求報文由請求行、請求頭部、空行和請求數據4部分組成。sql
c. 服務器接受請求並返回HTTP響應
Web服務器解析請求,定位請求資源。服務器將資源複本寫到TCP套接字,由客戶端讀取。一個響應由狀態行、響應頭部、空行和響應數據4部分組成。數據庫
d. 釋放鏈接TCP鏈接
若connection 模式爲close,則服務器主動關閉TCP鏈接,客戶端被動關閉鏈接,釋放TCP鏈接;若connection 模式爲keepalive,則該鏈接會保持一段時間,在該時間內能夠繼續接收請求;
e. 客戶端瀏覽器解析HTML內容
客戶端瀏覽器首先解析狀態行,查看代表請求是否成功的狀態代碼。而後解析每個響應頭,響應頭告知如下爲若干字節的HTML文檔和文檔的字符集。客戶端瀏覽器讀取響應數據HTML,根據HTML的語法對其進行格式化,並在瀏覽器窗口中顯示。
4.HTTP狀態碼
全部HTTP響應的第一行都是狀態行,依次是當前HTTP版本號,3位數字組成的狀態代碼,以及描述狀態的短語,彼此由空格分隔。
狀態代碼的第一個數字表明當前響應的類型:
雖然 RFC 2616 中已經推薦了描述狀態的短語,例如"200 OK","404 Not Found",可是WEB開發者仍然可以自行決定採用何種短語,用以顯示本地化的狀態描述或者自定義信息。
5.URL
超文本傳輸協議(HTTP)的統一資源定位符將從因特網獲取信息的五個基本元素包括在一個簡單的地址中:
因爲超文本傳輸協議容許服務器將瀏覽器重定向到另外一個網頁地址,所以許多服務器容許用戶省略網頁地址中的部分,好比 www。從技術上來講這樣省略後的網頁地址其實是一個不一樣的網頁地址,瀏覽器自己沒法決定這個新地址是否通,服務器必須完成重定向的任務。
5.HTTP協議對收發消息的格式要求
(1)HTTP GET請求的格式:
(2)HTTP響應的格式:
6.自定義版Web框架
import socket sk = socket.socket() sk.bind(("127.0.0.1", 80)) sk.listen(5) # 寫一個死循環,一直等待客戶端來連我 while 1: # 獲取與客戶端的鏈接 conn, _ = sk.accept() # 接收客戶端發來消息 data = conn.recv(8096) print(data) # 給客戶端回覆消息 conn.send(b'http/1.1 200 OK\r\ncontent-type:text/html; charset=utf-8\r\n\r\n') # 想讓瀏覽器在頁面上顯示出來的內容都是響應正文 conn.send(b'OK') # 關閉 conn.close()
7.根據不一樣路徑返回不一樣的內容
""" 完善的web服務端示例 根據不一樣的路徑返回不一樣的內容 """ import socket # 生成socket實例對象 sk = socket.socket() # 綁定IP和端口 sk.bind(("127.0.0.1", 80)) # 監聽 sk.listen(5) # 寫一個死循環,一直等待客戶端來連我 while 1: # 獲取與客戶端的鏈接 conn, _ = sk.accept() # 接收客戶端發來消息 data = conn.recv(8096) # 把收到的數據轉成字符串類型 data_str = str(data, encoding="utf-8") # bytes("str", enconding="utf-8") # print(data_str) # 用\r\n去切割上面的字符串 l1 = data_str.split("\r\n") # print(l1[0]) # 按照空格切割上面的字符串 l2 = l1[0].split() url = l2[1] # 給客戶端回覆消息 conn.send(b'http/1.1 200 OK\r\ncontent-type:text/html; charset=utf-8\r\n\r\n') # 想讓瀏覽器在頁面上顯示出來的內容都是響應正文 # 根據不一樣的url返回不一樣的內容 if url == "/felix/": response = b'<h1>Welcome to felix home</h1>' elif url == "/lisa/": response = b'<h1>Welcome to lisa home</h1>' else: response = b'<h1>404! not found!</h1>' conn.send(response) # 關閉 conn.close()
8.函數版根據不一樣路徑返回不一樣的內容
""" 完善的web服務端示例 函數版根據不一樣的路徑返回不一樣的內容 進階函數版 不寫if判斷了,用url名字去找對應的函數名 """ import socket # 生成socket實例對象 sk = socket.socket() # 綁定IP和端口 sk.bind(("127.0.0.1", 80)) # 監聽 sk.listen(5) # 定義一個處理/felix/的函數 def felix(url): ret = 'hello {}'.format(url) return bytes(ret, encoding="utf-8") # 定義一個處理/xiaohei/的函數 def lisa(url): ret = 'hello {}'.format(url) return bytes(ret, encoding="utf-8") # 定義一個專門用來處理404的函數 def f404(url): ret = "你訪問的這個{} 找不到".format(url) return bytes(ret, encoding="utf-8") url_func = [ ("/felix/", felix), ("/lisa/", lisa), ] # 寫一個死循環,一直等待客戶端來連我 while 1: # 獲取與客戶端的鏈接 conn, _ = sk.accept() # 接收客戶端發來消息 data = conn.recv(8096) # 把收到的數據轉成字符串類型 data_str = str(data, encoding="utf-8") # bytes("str", enconding="utf-8") # print(data_str) # 用\r\n去切割上面的字符串 l1 = data_str.split("\r\n") # print(l1[0]) # 按照空格切割上面的字符串 l2 = l1[0].split() url = l2[1] # 給客戶端回覆消息 conn.send(b'http/1.1 200 OK\r\ncontent-type:text/html; charset=utf-8\r\n\r\n') # 想讓瀏覽器在頁面上顯示出來的內容都是響應正文 # 根據不一樣的url返回不一樣的內容 # 去url_func裏面找對應關係 for i in url_func: if i[0] == url: func = i[1] break # 找不到對應關係就默認執行f404函數 else: func = f404 # 拿到函數的執行結果 response = func(url) # 將函數返回的結果發送給瀏覽器 conn.send(response) # 關閉鏈接 conn.close()
9.函數版根據不一樣路徑返回不一樣的HTML內容
""" 完善的web服務端示例 函數版根據不一樣的路徑返回不一樣的內容 進階函數版 不寫if判斷了,用url名字去找對應的函數名 返回html頁面 """ import socket # 生成socket實例對象 sk = socket.socket() # 綁定IP和端口 sk.bind(("127.0.0.1", 80)) # 監聽 sk.listen(5) # 定義一個處理/felix/的函數 def felix(url): with open("felix.html", "rb") as f: ret = f.read() return ret # 定義一個處理/lisa/的函數 def lisa(url): with open("lisa.html", "rb") as f: ret = f.read() return ret # 定義一個專門用來處理404的函數 def f404(url): ret = "你訪問的這個{} 找不到".format(url) return bytes(ret, encoding="utf-8") url_func = [ ("/felix/", felix), ("/lisa/", lisa), ] # 寫一個死循環,一直等待客戶端來連我 while 1: # 獲取與客戶端的鏈接 conn, _ = sk.accept() # 接收客戶端發來消息 data = conn.recv(8096) # 把收到的數據轉成字符串類型 data_str = str(data, encoding="utf-8") # bytes("str", enconding="utf-8") # print(data_str) # 用\r\n去切割上面的字符串 l1 = data_str.split("\r\n") # print(l1[0]) # 按照空格切割上面的字符串 l2 = l1[0].split() url = l2[1] # 給客戶端回覆消息 conn.send(b'http/1.1 200 OK\r\ncontent-type:text/html; charset=utf-8\r\n\r\n') # 想讓瀏覽器在頁面上顯示出來的內容都是響應正文 # 根據不一樣的url返回不一樣的內容 # 去url_func裏面找對應關係 for i in url_func: if i[0] == url: func = i[1] break # 找不到對應關係就默認執行f404函數 else: func = f404 # 拿到函數的執行結果 response = func(url) # 將函數返回的結果發送給瀏覽器 conn.send(response) # 關閉鏈接 conn.close()
10.動態網頁
""" 完善的web服務端示例 函數版根據不一樣的路徑返回不一樣的內容 進階函數版 不寫if判斷了,用url名字去找對應的函數名 返回html頁面 返回動態的html頁面 """ import socket # 生成socket實例對象 sk = socket.socket() # 綁定IP和端口 sk.bind(("127.0.0.1", 80)) # 監聽 sk.listen(5) # 定義一個處理/yimi/的函數 def felix(url): with open("felix.html", "r", encoding="utf-8") as f: ret = f.read() import time # 獲得替換後的字符串 ret2 = ret.replace("@@xx@@", str(time.time())) return bytes(ret2, encoding="utf-8") # 定義一個處理/xiaohei/的函數 def lisa(url): with open("lisa.html", "rb") as f: ret = f.read() return ret # 定義一個專門用來處理404的函數 def f404(url): ret = "你訪問的這個{} 找不到".format(url) return bytes(ret, encoding="utf-8") url_func = [ ("/felix/", felix), ("/lisa/", lisa), ] # 寫一個死循環,一直等待客戶端來連我 while 1: # 獲取與客戶端的鏈接 conn, _ = sk.accept() # 接收客戶端發來消息 data = conn.recv(8096) # 把收到的數據轉成字符串類型 data_str = str(data, encoding="utf-8") # bytes("str", enconding="utf-8") # print(data_str) # 用\r\n去切割上面的字符串 l1 = data_str.split("\r\n") # print(l1[0]) # 按照空格切割上面的字符串 l2 = l1[0].split() url = l2[1] # 給客戶端回覆消息 conn.send(b'http/1.1 200 OK\r\ncontent-type:text/html; charset=utf-8\r\n\r\n') # 想讓瀏覽器在頁面上顯示出來的內容都是響應正文 # 根據不一樣的url返回不一樣的內容 # 去url_func裏面找對應關係 for i in url_func: if i[0] == url: func = i[1] break # 找不到對應關係就默認執行f404函數 else: func = f404 # 拿到函數的執行結果 response = func(url) # 將函數返回的結果發送給瀏覽器 conn.send(response) # 關閉鏈接 conn.close()
HTML網頁內容:
<!DOCTYPE html> <html lang="zh-CN"> <head> <meta charset="UTF-8"> <meta http-equiv="x-ua-compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1"> <title>felix</title> </head> <body> <h1>web框架的本質</h1> <h3>@@xx@@</h3> </body> </html>
2、服務器程序和應用程序
對於真實開發中的python web程序來講,通常會分爲兩部分:服務器程序和應用程序。
服務器程序負責對socket服務器進行封裝,並在請求到來時,對請求的各類數據進行整理。
應用程序則負責具體的邏輯處理。爲了方便應用程序的開發,就出現了衆多的Web框架,例如:Django、Flask、web.py 等。不一樣的框架有不一樣的開發方式,可是不管如何,開發出的應用程序都要和服務器程序配合,才能爲用戶提供服務。
WSGI(Web Server Gateway Interface)就是一種規範,它定義了使用Python編寫的web應用程序與web服務器程序之間的接口格式,實現web應用程序與web服務器程序間的解耦。
經常使用的WSGI服務器有uwsgi、Gunicorn。而Python標準庫提供的獨立WSGI服務器叫wsgiref,Django開發環境用的就是這個模塊來作服務器。
1.wsgiref封裝的socket版web框架
""" 根據URL中不一樣的路徑返回不一樣的內容--函數進階版 返回HTML頁面 讓網頁動態起來 wsgiref模塊版 """ import time from wsgiref.simple_server import make_server # 將返回不一樣的內容部分封裝成函數 def felix(url): with open("felix.html", "r", encoding="utf8") as f: s = f.read() now = str(time.time()) s = s.replace("@@xx@@", now) return bytes(s, encoding="utf8") def lisa(url): with open("lisa.html", "r", encoding="utf8") as f: s = f.read() return bytes(s, encoding="utf8") # 定義一個url和實際要執行的函數的對應關係 list1 = [ ("/felix/", felix), ("/lisa/", lisa), ] def run_server(environ, start_response): start_response('200 OK', [('Content-Type', 'text/html;charset=utf8'), ]) # 設置HTTP響應的狀態碼和頭信息 url = environ['PATH_INFO'] # 取到用戶輸入的url func = None for i in list1: if i[0] == url: func = i[1] break if func: response = func(url) else: response = b"404 not found!" return [response, ] if __name__ == '__main__': httpd = make_server('127.0.0.1', 8090, run_server) print("我在8090等你哦...") httpd.serve_forever()
2.jinja2模板渲染版web框架---完成HTML頁面內容的替換
from wsgiref.simple_server import make_server from jinja2 import Template def index(): with open("jinjia2.html", "r", encoding="utf-8") as f: data = f.read() template = Template(data) # 生成模板文件 # 從數據庫中取數據 import pymysql conn = pymysql.connect( host="127.0.0.1", port=3306, user="root", password="", database="userinfo", charset="utf8", ) cursor = conn.cursor(cursor=pymysql.cursors.DictCursor) cursor.execute("select * from user;") user_list = cursor.fetchall() print(user_list) # 實現字符串的替換 ret = template.render({"user_list": user_list}) # 把數據填充到模板裏面 return [bytes(ret, encoding="utf8"), ] def home(): with open("home.html", "rb") as f: data = f.read() return [data, ] # 定義一個url和函數的對應關係 URL_LIST = [ ("/", index), ("/home/", home), ] def run_server(environ, start_response): start_response('200 OK', [('Content-Type', 'text/html;charset=utf8'), ]) # 設置HTTP響應的狀態碼和頭信息 url = environ['PATH_INFO'] # 取到用戶輸入的url func = None # 將要執行的函數 for i in URL_LIST: if i[0] == url: func = i[1] # 去以前定義好的url列表裏找url應該執行的函數 break if func: # 若是能找到要執行的函數 return func() # 返回函數的執行結果 else: return [bytes("404沒有該頁面", encoding="utf8"), ] if __name__ == '__main__': httpd = make_server('', 80, run_server) print("Serving HTTP on port 8000...") httpd.serve_forever()
HTML網頁內容:
<!DOCTYPE html> <html lang="zh-CN"> <head> <meta charset="UTF-8"> <title>Title</title> <meta http-equiv="x-ua-compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1"> <link rel="stylesheet" href="./bootstrap/css/bootstrap.min.css"> </head> <body> <table border="1px" class="table table-bordered table-striped table-hover table-condensed"> <thead> <tr> <th>ID</th> <th>用戶名</th> <th>密碼</th> </tr> </thead> <tbody> {% for user in user_list %} <tr> <td>{{user.id}}</td> <td>{{user.username}}</td> <td>{{user.password}}</td> </tr> {% endfor %} </tbody> </table> <script src="./jquery-3.3.1.min.js"></script> <script src="./bootstrap/js/bootstrap.min.js"></script> </body> </html>
網頁內容:
3、總結
1.關於HTTP協議:
(1)瀏覽器往服務端發的叫 請求(request) 請求的消息格式: 請求方法 路徑 HTTP/1.1\r\n k1:v1\r\n k2:v2\r\n \r\n 請求數據 (2)服務端往瀏覽器發的叫 響應(response) 響應的消息格式: HTTP/1.1 狀態碼 狀態描述符\r\n k1:v1\r\n k2:v2\r\n \r\n 響應正文 <-- html的內容
2. web框架的本質:
socket服務端 與 瀏覽器的通訊
3. socket服務端功能劃分:
4. Python中 Web框架的分類: