web框架的原理以及web框架的實現(python)
阿新 • • 發佈:2019-02-09
在學習了動態伺服器的實現之後(wsgi),便引入了web框架。
何為web框架?其實就相當於人的骨架一樣,有了框架之後我們便可以往裡面新增肉,新增各種需要新增的,由此而組成了一個人。而web也是如此,我們希望使用者可以直接往框架裡新增功能,同時不用去管怎樣處理使用者的請求,即我們要實現一個方法,可以將使用者的請求(無論是靜態資源還是動態資源)進行路由分發,分配給具體的函式去執行(這也就是web框架的主體)。
在wsgi協議裡,我們通過函式application實現了該功能,但那樣的實現對使用者不很友好,所有的方法都要自己去實現編寫,且重用性差(僅僅是一個函式),不具備框架的功能,因此我們至少應該清楚我們編寫的web框架應該是一個類,具有相當好的重用性以及封裝性。
我們知道函式application裡的引數分別是env以及start_response,而start_response是一個伺服器內部的函式,具體作用是處理http相應的頭資訊,所以我們web框架的主要功能,便是對web框架內的urls(表示web框架內部的資源)進行遍歷查詢,將使用者請求的資源(env.PATH_INFO)與urls裡的資料進行對比,如果存在則實現相應功能。
因此框架的原始碼如下:
# coding:utf-8
import time
#from MyWebServer import HTTPServer
HTMLROOT = "."
class Application (object):
# web框架的主體
def __init__(self, urls):
self.urls = urls
def __call__(self, env, start_response):
path = env.get("PATH_INFO", "/")
if path.startswith("/static"):
# 如果請求的是靜態資源 提取出檔名 在根目錄下開啟
file_name = path[7:]
try:
file = open(HTMLROOT + file_name, "rb" )
except IOError as e:
# 找不到檔案
print(e)
status = "404 NOT FOUND"
headers = []
start_response(status, headers)
return "file not found"
else:
# 找到檔案並提取資訊
data = file.read()
file.close()
status = "200 OK"
headers = [("Content-Type", "html")]
start_response(status, headers)
# 返回html檔案的資料
return data.decode('utf-8')
for url, app in self.urls:
# 此處是路由的功能 對urls進行遍歷 如果請求的動態資源存在於env中 則執行相應函式
if url == env.get("PATH_INFO", "/"):
return app(env, start_response)
status = "404 NOT FOUND"
headers = []
start_response(status, headers)
return "NOT FOUND"
# 相應的請求實現的功能
def show_time(env, start_response):
status = "200 OK"
headers = [("Content-Type", "text/plain")]
start_response(status, headers)
return time.ctime()
def say_hello(env, start_response):
status = "200 OK"
headers = [("Content-Type", "text/plain")]
start_response(status, headers)
return "hello, world"
# urls表示的是動態資源,字典中的值對應函式,鍵是請求的檔名
urls = [
("/", show_time),
("/ctime", show_time),
("/sayHello", say_hello)
]
# 直接建立物件 便於伺服器呼叫
app = Application(urls)
# if __name__ == "__main__":
# urls = [
# ("/", show_time),
# ("/ctime", show_time),
# ("/sayHello", say_hello)
# ]
# app = Application(urls)
#
# webServer = HTTPServer(app)
# webServer.bind(7788)
# webServer.start()
伺服器作為執行程式,程式碼如下:
# coding:utf-8
import socket
import re
import sys
from multiprocessing import Process
#from MyWebFrameWork import app
class HTTPServer(object):
"""伺服器類 Application表示的是web框架裡的app 物件"""
def __init__(self, Application):
self.server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
self.server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
self.app = Application
def start(self):
self.server_socket.listen(128)
while True:
client_socket, client_address = self.server_socket.accept()
# print("[%s, %s]使用者連線上了" % (client_address[0],client_address[1]))
print("[%s, %s]使用者連線上了" % client_address)
handle_client_process = Process(target=self.handle_client, args=(client_socket,))
handle_client_process.start()
client_socket.close()
def start_response(self, status, headers):
"""
status = "200 OK"
headers = [
("Content-Type", "text/plain")
]
star
"""
response_headers = "HTTP/1.1 " + status + "\r\n"
for header in headers:
response_headers += "%s: %s\r\n" % header
self.response_headers = response_headers
def handle_client(self, client_socket):
"""處理客戶端請求"""
# 獲取客戶端請求資料
request_data = client_socket.recv(1024)
print("request data:", request_data)
request_lines = request_data.splitlines()
for line in request_lines:
print(line)
# 解析請求報文
# 'GET / HTTP/1.1'
request_start_line = request_lines[0]
# 提取使用者請求的檔名
print("*" * 10)
print(request_start_line.decode("utf-8"))
file_name = re.match(r"\w+ +(/[^ ]*) ", request_start_line.decode("utf-8")).group(1)
method = re.match(r"(\w+) +/[^ ]* ", request_start_line.decode("utf-8")).group(1)
# "/ctime.py"
# "/sayhello.py"
print("file_name:", file_name)
print("method:", method)
env = {
"PATH_INFO": file_name,
"METHOD": method
}
response_body = self.app(env, self.start_response)
response = self.response_headers + "\r\n" + response_body
print("response data:", response)
# 向客戶端返回響應資料
client_socket.send(bytes(response, "utf-8"))
# 關閉客戶端連線
client_socket.close()
def bind(self, port):
self.server_socket.bind(("", port))
def main():
# 這是一種固定框架的實現
# my_server = HTTPServer(app)
# my_server.bind(7788)
# my_server.start()
# 這是一種非固定的實現 具體用哪一個框架 由輸入的引數決定
if len(sys.argv) < 2:
sys.exit("python MyWebServer.py Module:Application")
module_name, app_name = sys.argv[1].split(':')
m = __import__(module_name)
app = getattr(m, app_name)
my_server = HTTPServer(app)
my_server.bind(7788)
my_server.start()
if __name__ == '__main__':
main()
執行環境為pycharm,在script parameter裡輸入MyWebFrameWork:app,表示命令列中引數的傳入。
在瀏覽器中輸入127.0.0.1:7788/static/index.html 會顯示如下頁面:(靜態頁面的請求)
輸入127.0.0.1:7788/ctime 顯示:(動態資源的請求)
輸入127.0.0.1:7788/sayHello 顯示:(動態資源的請求)