Python進階之(web框架基礎)
一、web框架本質
我們可以這樣理解:所有的Web應用本質上就是一個socket服務端,而用戶的瀏覽器就是一個socket客戶端。 這樣我們就可以自己實現Web框架了。
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‘hello world‘) conn.close()
可以說Web服務本質上都是在這十幾行代碼基礎上擴展出來的,用戶在瀏覽器一輸入網址,給服務端發送數據,那瀏覽器會發送什麽數據?怎麽發?這個誰來定? 所以,必須有一個統一的規則,讓大家發送消息、接收消息的時候有個格式依據,不能隨便寫。
這個規則就是HTTP協議,以後瀏覽器發送請求信息也好,服務器回復響應信息也罷,都要按照這個規則來。
HTTP協議主要規定了客戶端和服務器之間的通信格式,那HTTP協議是怎麽規定消息格式的呢?
讓我們首先打印下我們在服務端接收到的消息是什麽
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‘hello world‘) conn.close()
輸出結果:
b‘GET / HTTP/1.1\r\n Host: 127.0.0.1\r\n Connection: keep-alive\r\nCache-Control: max-age=0\r\n Upgrade-Insecure-Requests: 1\r\n User-Agent: Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Safari/537.36\r\n Accept:text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8\r\nAccept-Encoding: gzip, deflate, br\r\n Accept-Language: zh-CN,zh;q=0.9\r\n Cookie: bdshare_firstime=1529473540203; csrftoken=WqST6GYvUUK9qKpkZlMnsbQzue1kOm5LknTvd9GQlr1MOj8yZ9YouYFjKLwgE9lb\r\n\r\n‘
我們發現收發的消息是要按照一定的格式來,這裏就需要了解一下HTTP協議了
HTTP協議(HyperText Transfer Protocol,超文本傳輸協議)是因特網上應用最為廣泛的一種網絡傳輸協議,所有的WWW文件都必須遵守這個標準。
HTTP是一個基於TCP/IP通信協議來傳遞數據(HTML 文件, 圖片文件, 查詢結果等)
HTTP協議定義Web客戶端如何從Web服務器請求Web頁面,以及服務器如何把Web頁面傳送給客戶端。HTTP協議采用了請求/響應模型。客戶端向服務器發送一個請求報文,請求報文包含請求的方法、URL、協議版本、請求頭部和請求數據。服務器以一個狀態行作為響應,響應的內容包括協議的版本、成功或者錯誤代碼、服務器信息、響應頭部和響應數據。然後客戶端瀏覽器解析HTML內容
HTTP請求方法:
GET(向指定的資源發出“顯示”請求。使用GET方法應該只用在讀取數據,而不應當被用於產生“副作用”的操作中,例如在Web Application中。其中一個原因是GET可能會被網絡蜘蛛等隨意訪問)
HEAD(與GET方法一樣,都是向服務器發出指定資源的請求。只不過服務器將不傳回資源的本文部分。它的好處在於,使用這個方法可以在不必傳輸全部內容的情況下,就可以獲取其中“關於該資源的信息”(元信息或稱元數據))
POST(向指定資源提交數據,請求服務器進行處理(例如提交表單或者上傳文件)。數據被包含在請求本文中。這個請求可能會創建新的資源或修改現有資源,或二者皆有)
PUT(向指定資源位置上傳其最新內容)
DELETE(請求服務器刪除Request-URI所標識的資源)
TRACE(回顯服務器收到的請求,主要用於測試或診斷)
OPTIONS(這個方法可使服務器傳回該資源所支持的所有HTTP請求方法。用‘*‘來代替資源名稱,向Web服務器發送OPTIONS請求,可以測試服務器功能是否正常運作)
CONNECT(HTTP/1.1協議中預留給能夠將連接改為管道方式的代理服務器。通常用於SSL加密服務器的鏈接(經由非加密的HTTP代理服務器))
HTTP狀態碼:
- 1xx消息——請求已被服務器接收,繼續處理
- 2xx成功——請求已成功被服務器接收、理解、並接受
- 3xx重定向——需要後續操作才能完成這一請求
- 4xx請求錯誤——請求含有詞法錯誤或者無法被執行
- 5xx服務器錯誤——服務器在處理某個正確請求時發生錯誤
HTTP協議對收發消息的格式要求:
HTTP 請求格式:
HTTP響應格式:
二、完善自定義的web框架
import socket #index頁面 def f1(request): f = open(‘index.txt‘,‘rb‘) data = f.read() f.close() return data #home頁面 def f2(request): import pymysql # 使用pymsql連接數據庫 conn = pymysql.connect(host=‘127.0.0.1‘, port=3306, user=‘root‘, passwd=‘‘, db=‘userbase‘) cursor = conn.cursor(cursor=pymysql.cursors.DictCursor) cursor.execute("select id,username,password from userinfo") user_list = cursor.fetchall() #拿出數據 cursor.close() conn.close() content_list = [] for row in user_list: tp = "<tr><td>%s</td><td>%s</td><td>%s</td></tr>" %(row[‘id‘],row[‘username‘],row[‘password‘]) content_list.append(tp) content = "".join(content_list)
f = open(‘home.html‘,‘r‘,encoding=‘utf-8‘) template = f.read() f.close() # 模板渲染 data = template.replace(‘@@userlist@@‘,content) #替換掉原有文件裏面的特殊符號內容 return bytes(data,encoding=‘utf-8‘) routers = [ (‘/index‘, f1), (‘/home.html‘, f2), ] def runserver(): sock = socket.socket() sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) sock.bind((‘127.0.0.1‘,80)) sock.listen(5) while True: conn,addr = sock.accept() data = conn.recv(8096) data = str(data,encoding=‘utf-8‘) headers,bodys = data.split(‘\r\n\r\n‘) temp_list = headers.split(‘\r\n‘) method,url,protocal = temp_list[0].split(‘ ‘) func_name = None for item in routers: if item[0] == url: func_name = item[1] break if func_name: response = func_name(data) else: response = b"404" conn.send(response) conn.close() if __name__ == ‘__main__‘: runserver()
三、wsgiref
對於真實開發中的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開發環境用的就是這個模塊來做服務器
from wsgiref.simple_server import make_server def run_server(environ, start_response): start_response(‘200 OK‘, [(‘Content-Type‘, ‘text/html;charset=utf8‘), ]) # 設置HTTP響應的狀態碼和頭信息 url = environ[‘PATH_INFO‘] # 取到用戶輸入的url,可進行判斷取舍 if __name__ == ‘__main__‘: httpd = make_server(‘127.0.0.1‘, 8090, run_server) print("開始服務...") httpd.serve_forever()
四、jinja2
上面的自定義web框架中,我們使用字符串拼接後用特殊符號替換來完成html的渲染,其實模板渲染有個現成的工具: jinja2
下載安裝:pip install jinja2
from jinja2 import Template def index(): userlist=[{"id":1,"username":‘jump‘,"age":12}] with open("index.html", "r") as f: data = f.read() template = Template(data) # 生成模板文件 ret = template.render(index=userlist) # 把數據填充到模板裏面 return [bytes(ret, encoding="utf8"), ] #html頁面部分 {% for row in index %} <tr> <td>{{row.id}}</td> <td>{{row.username}}</td> <td>{{row.age}}</td> </tr> {% endfor %}
補充:
自定義web框架:
- socket服務端
- 根據URL不同返回不同的內容(路由系統:URL -> 函數)
- 結果返回給用戶(模板引擎渲染:自己創造任意數據替換,jinja2)
Web框架:
根據上述3點web框架分類:
- 1,2,3 --> Tornado
- [第三方1],2,3 --> wsgiref -> Django
- [第三方1],2,[第三方3] --> flask
Python進階之(web框架基礎)