咱們能夠這樣理解:全部的Web應用本質上就是一個socket服務端,而用戶的瀏覽器就是一個socket客戶端。 這樣咱們就能夠本身實現Web框架了。html
mport 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()
能夠說Web服務本質上都是在這十幾行代碼基礎上擴展出來的。這段代碼就是它們的祖宗。python
用戶的瀏覽器一輸入網址,會給服務端發送數據,那瀏覽器會發送什麼數據?怎麼發?這個誰來定? 你這個網站是這個規定,他那個網站按照他那個規定,這互聯網還能玩麼?web
因此,必須有一個統一的規則,讓你們發送消息、接收消息的時候有個格式依據,不能隨便寫。數據庫
這個規則就是HTTP協議,之後瀏覽器發送請求信息也好,服務器回覆響應信息也罷,都要按照這個規則來。瀏覽器
HTTP協議主要規定了客戶端和服務器之間的通訊格式,那HTTP協議是怎麼規定消息格式的呢?服務器
讓咱們首先打印下咱們在服務端接收到的消息是什麼。app
import socket sk = socket.socket() sk.bind(("127.0.0.1", 80)) sk.listen() while True: conn, addr = sk.accept() data = conn.recv(8096) print(data) # 將瀏覽器發來的消息打印出來 conn.send(b"OK") conn.close()
輸出:框架
b'GET / HTTP/1.1\r\nHost: 127.0.0.1:8080\r\nConnection: keep-alive\r\nUpgrade-Insecure-Requests: 1\r\nUser-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/64.0.3282.186 Safari/537.36\r\nAccept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8\r\nDNT: 1\r\nAccept-Encoding: gzip, deflate, br\r\nAccept-Language: zh-CN,zh;q=0.9\r\nCookie: csrftoken=RKBXh1d3M97iz03Rpbojx1bR6mhHudhyX5PszUxxG3bOEwh1lxFpGOgWN93ZH3zv\r\n\r\n'
而後咱們再看一下咱們訪問博客園官網時瀏覽器收到的響應信息是什麼。socket
響應相關信息能夠在瀏覽器調試窗口的network標籤頁中看到。函數
HTTP協議對收發消息的格式要求
每一個HTTP請求和響應都遵循相同的格式,一個HTTP包含Header和Body兩部分,其中Body是可選的。 HTTP響應的Header中有一個 Content-Type
代表響應的內容格式。如 text/html
表示HTML網頁。
通過上面的補充學習,咱們知道了要想讓咱們本身寫的web server端正經起來,必需要讓咱們的Web server在給客戶端回覆消息的時候按照HTTP協議的規則加上響應狀態行,這樣咱們就實現了一個正經的Web框架了
import socket sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) sock.bind(('127.0.0.1', 8000)) sock.listen() while True: conn, addr = sock.accept() data = conn.recv(8096) # 給回覆的消息加上響應狀態行 conn.send(b"HTTP/1.1 200 OK\r\n\r\n") conn.send(b"OK") conn.close()
咱們經過十幾行代碼簡單地演示了web 框架的本質。
接下來就讓咱們繼續完善咱們的自定義web框架吧!
能夠從請求相關數據裏面拿到請求URL的路徑,而後拿路徑作一個判斷。
""" 根據URL中不一樣的路徑返回不一樣的內容 """ import socket sk = socket.socket() sk.bind(("127.0.0.1", 8080)) # 綁定IP和端口 sk.listen() # 監聽 while 1: # 等待鏈接 conn, add = sk.accept() data = conn.recv(8096) # 接收客戶端發來的消息 # 從data中取到路徑 data = str(data, encoding="utf8") # 把收到的字節類型的數據轉換成字符串 # 按\r\n分割 data1 = data.split("\r\n")[0] url = data1.split()[1] # url是咱們從瀏覽器發過來的消息中分離出的訪問路徑 conn.send(b'HTTP/1.1 200 OK\r\n\r\n') # 由於要遵循HTTP協議,因此回覆的消息也要加狀態行 # 根據不一樣的路徑返回不一樣內容 if url == "/index/": response = b"index" elif url == "/home/": response = b"home" else: response = b"404 not found!" conn.send(response) conn.close()
""" 根據URL中不一樣的路徑返回不一樣的內容--函數版 """ import socket sk = socket.socket() sk.bind(("127.0.0.1", 8080)) # 綁定IP和端口 sk.listen() # 監聽 # 將返回不一樣的內容部分封裝成函數 def index(url): s = "這是{}頁面!".format(url) return bytes(s, encoding="utf8") def home(url): s = "這是{}頁面!".format(url) return bytes(s, encoding="utf8") while 1: # 等待鏈接 conn, add = sk.accept() data = conn.recv(8096) # 接收客戶端發來的消息 # 從data中取到路徑 data = str(data, encoding="utf8") # 把收到的字節類型的數據轉換成字符串 # 按\r\n分割 data1 = data.split("\r\n")[0] url = data1.split()[1] # url是咱們從瀏覽器發過來的消息中分離出的訪問路徑 conn.send(b'HTTP/1.1 200 OK\r\n\r\n') # 由於要遵循HTTP協議,因此回覆的消息也要加狀態行 # 根據不一樣的路徑返回不一樣內容,response是具體的響應體 if url == "/index/": response = index(url) elif url == "/home/": response = home(url) else: response = b"404 not found!" conn.send(response) conn.close()
""" 根據URL中不一樣的路徑返回不一樣的內容--函數進階版 """ import socket sk = socket.socket() sk.bind(("127.0.0.1", 8080)) # 綁定IP和端口 sk.listen() # 監聽 # 將返回不一樣的內容部分封裝成函數 def index(url): s = "這是{}頁面!".format(url) return bytes(s, encoding="utf8") def home(url): s = "這是{}頁面!".format(url) return bytes(s, encoding="utf8") # 定義一個url和實際要執行的函數的對應關係 list1 = [ ("/index/", index), ("/home/", home), ] while 1: # 等待鏈接 conn, add = sk.accept() data = conn.recv(8096) # 接收客戶端發來的消息 # 從data中取到路徑 data = str(data, encoding="utf8") # 把收到的字節類型的數據轉換成字符串 # 按\r\n分割 data1 = data.split("\r\n")[0] url = data1.split()[1] # url是咱們從瀏覽器發過來的消息中分離出的訪問路徑 conn.send(b'HTTP/1.1 200 OK\r\n\r\n') # 由於要遵循HTTP協議,因此回覆的消息也要加狀態行 # 根據不一樣的路徑返回不一樣內容 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!" # 返回具體的響應消息 conn.send(response) conn.close()
""" 根據URL中不一樣的路徑返回不一樣的內容--函數進階版 返回獨立的HTML頁面 """ import socket sk = socket.socket() sk.bind(("127.0.0.1", 8080)) # 綁定IP和端口 sk.listen() # 監聽 # 將返回不一樣的內容部分封裝成函數 def index(url): # 讀取index.html頁面的內容 with open("index.html", "r", encoding="utf8") as f: s = f.read() # 返回字節數據 return bytes(s, encoding="utf8") def home(url): with open("home.html", "r", encoding="utf8") as f: s = f.read() return bytes(s, encoding="utf8") # 定義一個url和實際要執行的函數的對應關係 list1 = [ ("/index/", index), ("/home/", home), ] while 1: # 等待鏈接 conn, add = sk.accept() data = conn.recv(8096) # 接收客戶端發來的消息 # 從data中取到路徑 data = str(data, encoding="utf8") # 把收到的字節類型的數據轉換成字符串 # 按\r\n分割 data1 = data.split("\r\n")[0] url = data1.split()[1] # url是咱們從瀏覽器發過來的消息中分離出的訪問路徑 conn.send(b'HTTP/1.1 200 OK\r\n\r\n') # 由於要遵循HTTP協議,因此回覆的消息也要加狀態行 # 根據不一樣的路徑返回不一樣內容 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!" # 返回具體的響應消息 conn.send(response) conn.close()
""" 根據URL中不一樣的路徑返回不一樣的內容--函數進階版 返回HTML頁面 讓網頁動態起來 """ import socket import time sk = socket.socket() sk.bind(("127.0.0.1", 8080)) # 綁定IP和端口 sk.listen() # 監聽 # 將返回不一樣的內容部分封裝成函數 def index(url): with open("index.html", "r", encoding="utf8") as f: s = f.read() now = str(time.time()) s = s.replace("@@oo@@", now) # 在網頁中定義好特殊符號,用動態的數據去替換提早定義好的特殊符號 return bytes(s, encoding="utf8") def home(url): with open("home.html", "r", encoding="utf8") as f: s = f.read() return bytes(s, encoding="utf8") # 定義一個url和實際要執行的函數的對應關係 list1 = [ ("/index/", index), ("/home/", home), ] while 1: # 等待鏈接 conn, add = sk.accept() data = conn.recv(8096) # 接收客戶端發來的消息 # 從data中取到路徑 data = str(data, encoding="utf8") # 把收到的字節類型的數據轉換成字符串 # 按\r\n分割 data1 = data.split("\r\n")[0] url = data1.split()[1] # url是咱們從瀏覽器發過來的消息中分離出的訪問路徑 conn.send(b'HTTP/1.1 200 OK\r\n\r\n') # 由於要遵循HTTP協議,因此回覆的消息也要加狀態行 # 根據不一樣的路徑返回不一樣內容 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!" # 返回具體的響應消息 conn.send(response) conn.close()
對於真實開發中的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開發環境用的就是這個模塊來作服務器。
咱們利用wsgiref模塊來替換咱們本身寫的web框架的socket server部分:
""" 根據URL中不一樣的路徑返回不一樣的內容--函數進階版 返回HTML頁面 讓網頁動態起來 wsgiref模塊版 """ import time from wsgiref.simple_server import make_server # 將返回不一樣的內容部分封裝成函數 def index(url): with open("index.html", "r", encoding="utf8") as f: s = f.read() now = str(time.time()) s = s.replace("@@oo@@", now) return bytes(s, encoding="utf8") def home(url): with open("home.html", "r", encoding="utf8") as f: s = f.read() return bytes(s, encoding="utf8") # 定義一個url和實際要執行的函數的對應關係 list1 = [ ("/index/", 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 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()
上面的代碼實現了一個簡單的動態,我徹底能夠從數據庫中查詢數據,而後去替換我html中的對應內容,而後再發送給瀏覽器完成渲染。 這個過程就至關於HTML模板渲染數據。 本質上就是HTML內容中利用一些特殊的符號來替換要展現的數據。 我這裏用的特殊符號是我定義的,其實模板渲染有個現成的工具: jinja2