1. 程式人生 > >web框架的原理以及web框架的實現(python)

web框架的原理以及web框架的實現(python)

在學習了動態伺服器的實現之後(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 顯示:(動態資源的請求)

這裡寫圖片描述