[python] pprika:基于werkzeug编写的web框架(2) ——路由与请求响应

最简单的示例

from pprika import PPrika

app = PPrika()


@app.route('/')
def index():
    return 'Hello world!'


if __name__ == '__main__':
    app.run()

可以看到用法与flask几乎一样,但app生成不需要传递import_name

下方开始按请求处理顺序介绍PPrika类的方法

 

route

它是 add_url_rule 的装饰器版本

等同于 app.add_url_rule('/', view_func=index)

 

add_url_rule

def add_url_rule(self, path, endpoint=None, view_func=None, **options):
    if endpoint is None:
        assert view_func is not None, "无endpoint时view_func不可为空"
        endpoint = view_func.__name__

类似于flask的同名函数 `add_url_rule`,它将一个url注册到给定的endpoint上,并关联endpoint与视图函数view_func。其中url与endpoint多对一,endpoint与view_func一对一,通过endpoint作中介实现了url与func多对一映射。

参数中path即为要绑定的url路径,endpoint若省略则将视图函数名作为其值,因此二者不可同时为空。

    methods = options.pop('methods', '') or ("GET",)
    if isinstance(methods, str):
        # 即允许了类似methods="POST"的method指定方式
        methods = (methods,)
    methods = set(item.upper() for item in methods)

从options中尝试获取该视图函数所对应的methods,但即便没有显式 'HEAD' 方法,在路由时werkzeug会将HEAD请求作为GET匹配。

    rule = Rule(path, methods=methods, endpoint=endpoint, **options)
    self.url_map.add(rule)

利用之前处理好的参数构造Rule对象,并将其加入Map。

其中 self.url_map 是Map的实例,Map与Rule都是  werkzeug.routing 提供的类。

通过这一步就完成了一个路由关系的记录,是整个 add_url_rule 的核心步骤

    if view_func is not None:
        old_func = self.view_functions.get(endpoint)
        if old_func is not None and old_func != view_func:
            raise AssertionError("此endpoint已有对应函数: %s" % endpoint)
        self.view_functions[endpoint] = view_func

上一步是在url与endpoint之间建立联系,而这一步就是在endpoint与view_func之间产生关联,并将view_func记录于self.view_functions这一字典中。除此之外还会判断endpoint是否已有对应的视图函数,这部分来自于flask,说实在不是很明白它的意义。

推测:当执行add_url_rule('/', 'index', index)时endpoint='index',绑定了index函数,这时若再执行add_url_rule('/home', 'index', func)要求 func == index,否则会违背endpoint与view_func一对一的原则。

 

run

from werkzeug.serving import run_simple

def run(self, host='localhost', port=9000, **options):
    options.setdefault("threaded", True)  # 线程隔离
    run_simple(host, port, self, **options)

本质上是将参数传递给werkzeug提供的开发服务器

其中 self 指的是PPrika的实例,即上方的app。run_simple运行后会等待请求到来,当收到请求时会将所有的请求参数整理为一个变量传递给app,将其作为函数调用得到视图函数返回值,因此需要app实现 __call__ 方法。

ps: 一开始开放的是6000端口,测试了很久发现chrome居然拒绝发出请求,而Edge运行正常。

 

__call__

def __call__(self, environ, start_response):
    return self.wsgi_app(environ, start_response)

__call__仅负责转发请求给wsgi_app。这里的environ、start_response由run_simple提供,environ包含了所有请求信息,而start_response则是请求处理最后返回响应时调用的函数,但无需手动使用它。

 

wsgi_app

假设此时一个请求(http://localhost:9000/)到来,那么run_simple就会将请求传递给__call__,再进一步交由wsgi_app处理。赛这里会实现路由的匹配、处理的请求、响应结果返回以及异常的捕捉与处理。

def wsgi_app(self, environ, start_response):
    ctx = RequestContext(self, environ)  # 请求上下文对象
    try:
        try:
            ctx.bind()  # 绑定请求上下文并匹配路由
            rv = self.dispatch_request()
            response = make_response(rv)
        except Exception as e:
            response = self.handle_exception(e)
        return response(environ, start_response)
    finally:
        ctx.unbind()

ctx是请求上下文对象先不用在意,仅需知道通过ctx.bind全局变量request将变得可用。

通过ctx.bind匹配路由后wsgi_app会尝试调用dispatch_request去调用该路由相应的视图函数并获取函数返回值rv,并通过make_response生成响应对象,由于该对象是 werkzeug.wrappers.Response 的实例,以environ与start_response调用它可得到最终的响应,至此整个请求处理完成。

handle_exception作为外层处理函数负责处理没有绑定错误处理器的错误或其他错误。

ps:对比flask,由于pprika没有实现请求钩子之类的,因此也不需要额外的一层full_dispatch_request

 

dispatch_request

def dispatch_request(self):
    if request.routing_exception is not None:
        return self.handle_user_exception(request.routing_exception)
    # 'url_adapter.match' 时可能产生的路由错误

    try:
        endpoint, args = request.rule.endpoint, request.view_args
        rv = self.view_functions[endpoint](**args)
    except Exception as e:
        rv = self.handle_user_exception(e)
    return rv

request.routing_exception是上文中ctx.bind时可能发生的路由错误

dispatch_request会尝试从request中获取 endpoint, args,通过endpoint得到对应视图函数并将args作为参数传入得到函数返回值rv。

假如一切正常,会将rv返回给上文的wsgi_app,流程顺利结束。

若视图函数中抛出了异常会先以 self.handle_user_exception 尝试处理,若无法处理则会再次将错误抛出交给wsgi_app中提及的handle_exception。

 

结语

至此整个路由、请求接受与响应的过程都介绍完毕,这些是框架最核心的功能。

下一篇将讲述错误处理、请求上下文与帮助函数。

[python] pprika:基于werkzeug编写的web框架(3) ——错误处理

 

你可能感兴趣的:([python] pprika:基于werkzeug编写的web框架(2) ——路由与请求响应)