首頁>技術>

後端開發中,我們經常使用web框架來實現各種應用,比如python中的flask,django等,go語言中的gin等。web框架提供了很多現成的工具,大大加快了開發速度。這次,我們將動手實現自己的一個web框架。

當我們在瀏覽器開啟連結發起請求之後發生了什麼?

http請求會經過WSGI伺服器轉發給web框架比如flask,flask處理請求並返回響應。WSGI就相當於中間商,處理客戶端和框架之間的資訊交換。那麼WSGI到底是個啥?

WSGI

WSGI全稱伺服器閘道器介面,你想想,針對每一種伺服器都需要實現對應的處理介面是件很麻煩的事,WSGI規定了統一的應用介面實現,具體來說,WSGI規定了application應該實現一個可呼叫的物件(函式,類,方法或者帶__call__的例項),這個物件應該接受兩個位置引數:

環境變數(比如header資訊,狀態碼等)回撥函式(WSGI伺服器負責),用來發送http狀態和header等

同時,該物件需要返回可迭代的響應文字。

WSGI實現

為了充分理解WSGI,我們定義一個application,引數為environ和回撥函式。

def app(environ, start_response):    response_body = b"Hello, World!"    status = "200 OK"    # 將響應狀態和header交給WSGI伺服器比如gunicorn    start_response(status, headers=[])    return iter([response_body])

當利用諸如gunicorn之類的伺服器啟動該程式碼,gunicorn app:app,開啟瀏覽器就可以看到返回的“hello world”資訊。

可以看到,app函式中的回撥函式start_response將響應狀態和header交給了WSGI伺服器。

web框架實現

web框架例如flask的核心就是實現WSGI規範,路由分發,檢視渲染等功能,這樣我們就不用自己去寫相關的模組了。

以flask為例,使用方法如下:

from flask import Flaskapp = Flask(__name__)@app.route("/home")def hello():    return "hello world"if __name__ = '__main__':    app.run()

首先定義了一個全域性的app例項,然後在對應的函式上定義路由裝飾器,這樣不同的路由就分發給不同的函式處理。

WSGI實現

為了功能上的考量,我們將application定義為類的形式,新建一個api.py檔案,首先實現WSGI。

這裡為了方便使用了webob這個庫,它將WSGI處理封裝成了方便的介面,使用pip install webob 安裝。

from webob import Request, Responseclass API:    def __call__(self, environ, start_response):        request = Request(environ)        response = Response()        response.text = "Hello, World!"        return response(environ, start_response)

API類中定義了`__call__內建方法實現。很簡單,對吧。

對於路由 /home ,和路由/about, 像flask一樣,利用裝飾器將他們繫結到不同的函式上。

# app.pyfrom api.py import APIapp = API()@app.route("/home")def home(request, response):    response.text = "Hello from the HOME page"@app.route("/about")def about(request, response):    response.text = "Hello from the ABOUT page"

這個裝飾器是如何實現的?

不同的路由對應不同的handler,應該用字典來存放對吧。這樣當新的路由過來之後,直接route.get(path, None) 即可。

class API:    def __init__(self):        self.routes = {}    def route(self, path):        def wrapper(handler):            self.routes[path] = handler            return handler        return wrapper    ...

如上所示,定義了一個routes字典,然後一個路由裝飾器方法,這樣就可以使用@app.route("/home")。

路由繫結handler

有一個問題,路由有靜態的也有動態的,怎麼辦?

用parse這個庫解析請求的path和存在的path,獲取動態引數。比如:

>>> from parse import parse>>> result = parse("/people/{name}", "/people/shiniao")>>> print(result.named){'name': 'shiniao'}

除了動態路由,還要考慮到裝飾器是不是可以繫結在類上,比如django。另外如果請求不存在路由,需要返回404。

import inspectfrom parse import parsefrom webob import Request, Responseclass API(object):    def __init__(self):        # 存放所有路由        self.routes = {}    # WSGI要求實現的__call__    def __call__(self, environ, start_response):        request = Request(environ)        response = self.handle_request(request)        # 將響應狀態和header交給WSGI伺服器比如gunicorn        # 同時返回響應正文        return response(environ, start_response)    # 找到路由對應的處理物件和引數    def find_handler(self, request_path):        for path, handler in self.routes.items():            parse_result = parse(path, request_path)            if parse_result is not None:                return handler, parse_result.named        return None, None    # 匹配請求路由,分發到不同處理函式    def handle_request(self, request):        response = Response()        handler, kwargs = self.find_handler(request.path)        if handler is not None:            # 如果handler是類的話            if inspect.isclass(handler):                # 獲取類中定義的方法比如get/post                handler = getattr(handler(), request.method.low(), None)                # 如果不支援                if handler is None:                    raise AttributeError("method not allowed.", request.method)            handler(request, response, **kwargs)        else:            self.default_response(response)        return response    def route(self, path):        # if the path already exists        if path in self.routes:            raise AssertionError("route already exists.")        # bind the route path and handler function        def wrapper(handler):            self.routes[path] = handler            return handler        return wrapper    # 預設響應    def default_response(self, response):        response.status_code = 404        response.text = "not found."

新建一個app.py檔案,然後定義我們的路由處理函式:

from api import APIapp = API()@app.route("/home")def home(request, response):    response.text = "hello, ding."    @app.route("/people/{name}")def people(req, resp, name)    resp.text = "hello, {}".format(name) 

然後我們啟動gunicorn:

gunicorn app:app。

開啟瀏覽器訪問:

http://127.0.0.1:8000/people/shiniao

就能看到返回的資訊:hello, shiniao。

測試下

測試下訪問重複的路由會不會丟擲異常,新建test_ding.py 檔案。

使用pytest來測試。

import pytestfrom api import [email protected] api():    return API()def test_basic_route(api):    @api.route("/home")    def home(req, resp):        resp.text = "ding"    with pytest.raises(AssertionError):        @api.route("/home")        def home2(req, resp):            resp.text = "ding"

好了,以上就是實現了一個簡單的web框架,支援基本的路由轉發,動態路由等功能。我一直認為最好的學習方法就是模仿,自己動手去實現不同的輪子,寫個直譯器啊,web框架啊,小的資料庫啊,這些對自己的技術進步都會很有幫助。

另外,情人節快樂!

前期我也和很多小夥伴一樣,到處收集了很多資料,後面發現很多重複的!上面都是自己整理好的!現,我就把資料貢獻出來給有需要的人!順便求一波關注,

最新評論
  • BSA-TRITC(10mg/ml) TRITC-BSA 牛血清白蛋白改性標記羅丹明
  • 用 Nuxtjs + PixiJs 寫個單頁網站,並在netlify免費部署