本篇主要介紹WSGI-web-mini框架的基本實現,以及什麼是WSGI協議、對於瀏覽器的資源請求服務端是如何處理的等相關知識;html
咱們知道瀏覽器和web服務器之間是基於HTTP協議進行文件的傳輸的,而根據請求消息的不一樣,服務器處理的流程也是不一樣的;以下圖:java
一般而言:python
對於瀏覽器請求的靜態資源,服務器在收到請求消息時(處理完後),會直接在服務器的本地磁盤中將信息(index.html頁面)讀取出來,直接發送給web瀏覽器;web
而對於動態資源的請求,服務器一般會調用web框架來處理,而web框架會根據用戶的請求不一樣,從數據庫中讀取不一樣的信息將其替換自HTML模板中,而且返回給HTTP服務器,最後由服務器將消息發送給web瀏覽器;數據庫
2.1 爲何要使用WSGI協議呢?瀏覽器
思考:在之前,選擇 Python-web架構 會受制於可用的 web服務器,反之亦然。爲了讓web架構和服務器能夠協同工做,但有可能面對(或者曾有過)下面的問題,當要把一個服務器和一個架構結合起來時,卻發現他們不是被設計成協同工做的;服務器
那麼,怎麼能夠不修改服務器和架構代碼而確保能夠在多個架構下運行web服務器呢?架構
答案就是 Python Web Server Gateway Interface (或簡稱 WSGI,讀做「wizgy」)。app
WSGI容許開發者將選擇web框架和web服務器分開。能夠混合匹配web服務器和web框架,選擇一個適合的配對。好比,能夠在Gunicorn 或者 Nginx/uWSGI 或者 Waitress上運行 Django, Flask, 或 Pyramid。真正的混合匹配,得益於WSGI同時支持服務器和架構;框架
2.2 WSGI接口
而一般web服務器必須具有WSGI接口,全部的現代Python Web框架都已具有WSGI接口,它讓你不對代碼做修改就能使服務器和特色的web框架協同工做。
WSGI由web服務器支持,而web框架容許你選擇適合本身的配對,但它一樣對於服務器和框架開發者提供便利使他們能夠專一於本身偏心的領域和專長而不至於相互牽制。其餘語言也有相似接口:java有Servlet API,Ruby 有 Rack。
咱們需在web框架內定義一個WSGI接口函數,而web服務器與框架進行協同工做時,只需經過該接口函數便可,其餘的實現均封裝在該框架的底部;該接口函數以下:
# WSGI接口函數 def application(environ, start_response): start_response('200 OK', [('Content-Type', 'text/html')]) return 'Hello World!'
參數:
environ :一個包含全部HTTP請求信息的dict對象;即告訴框架須要調取哪些動態資源頁面;
start_response :一個發送HTTP響應的函數。該函數至關於HTTP服務器給與框架的一個容器,而框架將頭部信息經過參數的方式存放在該函數內,最後服務器再從中取出;
返回值:
返回的是請求的動態資源的響應消息的body部分;一般web框架將動態資源替換HTML模板的消息返回給服務器;
其大體結構以下:
web服務器:
import socket import multiprocessing import re # import dynamic.mini_frame import sys class WSGIServer(object): def __init__(self,port,app,static_path): # 1.建立監聽套接字 self.server_socket = socket.socket(socket.AF_INET,socket.SOCK_STREAM) # 2.爲了服務端先斷開時,客戶端立刻能連接 self.server_socket.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1) # 3.綁定本地端口 self.server_socket.bind(("",port)) # 4.監聽 self.server_socket.listen(128) self.port = port self.application = app self.static_path = static_path def deal_with_static(self,new_socket): # 若是不是以.py結尾的文件,當成靜態頁面處理 try: f=open("static"+ self.file_name,"rb") except: header = "HTTP/1.1 404 NOT FOUND FILE\r\n" header += "\r\n" body = "<h1>not found file <\h1>" response = header+body new_socket.send(response.encode("utf-8")) else: header="HTTP/1.1 200 OK\r\n" header += "\r\n" body = f.read() response = header.encode("utf-8") + body new_socket.send(response) def deal_with_dynamic(self,new_socket): """處理動態頁面""" env = dict() env["PATH_INFO"] = self.file_name # web框架的接口函數 body = self.application(env,self.set_response_header) # headr部分的拼接成指定格式 header= "HTTP/1.1 %s"%self.status for temp in self.header: header += "%s:%s\r\n" % (temp[0], temp[1]) header+="\r\n" print(header) new_socket.send(header.encode("utf-8")) new_socket.send(body.encode("utf-8")) def set_response_header(self,status,header): """做爲接口函數的參數,至關於容器接收web框架返回的頭部信息""" self.status = status; self.header = header; def serve_client(self,new_socket): """接收請求消息和發送相應消息""" # 1.接受請求消息 GET / HTTP/1.1 request = new_socket.recv(1024).decode("utf-8") # 2.解析請求消息 request_lines = request.splitlines() ret = re.match(r"[^/]+(/[^ ]*)",request_lines[0]) if ret: self.file_name = ret.group(1) if self.file_name =="/": self.file_name = "/index.html" # print(file_name) # 3.嘗試打開文件,若文件存在則能打開,不然發送404 if not self.file_name.endswith(".py"): # 處理靜態網頁 self.deal_with_static(new_socket) # new_socket.close() else: # 處理動態網頁 self.deal_with_dynamic(new_socket) new_socket.close() def run_forever(self): """當成主函數""" while True: new_socket,client_addr = self.server_socket.accept() p = multiprocessing.Process(target=self.serve_client,args=(new_socket,)) p.start() p.join() new_socket.close() self.server_socket.close() def main(): # 運行服務器時,給出指定的 端口和運行的框架 if len(sys.argv)==3: try: port=int(sys.argv[1]) # 6968 frame_app_name = sys.argv[2] # mini_frame:application except Exception as e: print("輸入方式有誤,請按照如下方式運行:") print("python3 web_server.py 6968 mini_frame:application") return ret = re.match(r"([^:]+):(.*)",frame_app_name) # 獲取框架文件名 if ret: frame_name = ret.group(1) app_name = ret.group(2) else: print("您的輸入方式有誤,請按照上述方式運行") return with open("./web_server.conf","r") as f: conf_info = eval(f.read()) # 將字符串轉爲字典類型 # 將導入動態數據的模塊添加到python解釋器的環境變量中 sys.path.append(conf_info["dynamic_path"]) print(sys.path) frame =__import__(frame_name) # 導入指定的模塊 app = getattr(frame,app_name) # 反射出該模塊下的指定的函數app_name wsgi_server = WSGIServer(port,app,conf_info["static_path"]) wsgi_server.run_forever() if __name__ == '__main__': main()
解析:
一、在web服務器中,咱們首先根據資源的請求不一樣,給出的處理方式不一樣即靜態資源直接從本地磁盤讀取,而動態資源交給web框架處理。
二、在與web框架進行交互,調用了wsgi接口函數,傳入了字典env,用來告訴服務器需求頁面;以及函數set_response_header用來獲取web框架傳遞的頭部信息;而且將返回值做爲body;整個與web框架的交互均是圍繞着wsgi接口函數來實現的;
三、隨後即是讓咱們的服務器運行能夠指定端口和web框架來運行,則定義了main函數下面的一些代碼,經過sys.argv來獲取到指定的端口和web_frame:application等,再將這些做爲參數傳入類 WSGIServer中來執行;
mini-web框架
import re def index(): """顯示index.py動態數據""" with open("./templates/index.html","r") as f: content=f.read() # print(content) my_stock_info = "這是是數據庫的數據,未完待續。" content = re.sub(r"\{%content%\}", my_stock_info, content) return content def center(): """顯示center動態數據""" with open("./templates/center.html","r") as f: content = f.read() # print(content) mys_stock_info = "這是數據庫的數據,未完待續。" content = re.sub(r"\{%content%\} ",mys_stock_info,content) return content def application(environ,start_response): start_response("200 OK\r\n",[('Content-Type', 'text/html;charset=utf8')]) file_name=environ["PATH_INFO"] if file_name == "/index.py": return index() elif file_name == "/center.py": return center() else: start_response("400 NOT FOUND FILE\r\n",[('Content-Type', 'text/html;charset=utf8')]) return "Sorry,the file you want get have not found;"
在該框架內:定義了wsgi接口函數,以及獲取指定頁面的函數,在下篇中咱們將會介紹如何將數據庫的內容導入;
web_frame.conf 配置文件
{ "static_path":"./static", "dynamic_path":"./dynamic" }
配置文件主要用來指定:動態資源及靜態資源存儲位置,當請求動態資源時,將這些路徑添加至python解釋器的環境變量中去,方便web框架模塊的導入;
run.sh腳本運行文件
python3 web_server.py 6868 mini_frame:application
定義腳本運行文件,主要是方便運行,不至於每次寫一大坨東西去執行;
over~~~ 下篇中會實現路由的功能,而且替換成數據庫中的數據~~~