zoukankan      html  css  js  c++  java
  • [python] pprika:基于werkzeug编写的web框架(4) ——请求上下文与helpers

    请求上下文对象

    之前在wsgi_app中提到了ctx,它需要在请求的开始绑定,结束阶段解绑↓ (去掉了不相关部分)

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

     上述标橙的RequestContext即是实现该功能重要的类 ↓

    RequestContext

    class RequestContext(object):
        def __init__(self, app, environ):
            self.url_adapter = app.url_map.bind_to_environ(environ)
            self.request = Request(environ)  # 即全局变量request
    
        def bind(self):
    
        def unbind(self):
    
        def match_request(self):

    其中app.url_map是之前提及的Map的实例,调用它的bind_to_environ方法可以得到url_adapter,后者是进行路由匹配,即获取该请求url所对endpoint的重要对象。

    而Request的实例即为全局可用的请求对象pprika.request,跟flask中的那个一样。此处继承了 werkzeug.wrappers.Request 添加了一些属性 ↓

    Request

    class Request(BaseRequest):
        def __init__(self, environ):
            self.rule = None
            self.view_args = None
            self.blueprint = None
            self.routing_exception = None
            super().__init__(environ)
    
        def __load__(self, res):
            self.rule, self.view_args = res
    
            if self.rule and "." in self.rule.endpoint:
                self.blueprint = self.rule.endpoint.rsplit(".", 1)[0]
    
        @property
        def json(self):
            if self.data and self.mimetype == 'application/json':
                return loads(self.data)

    其中的属性如rule、view_args等都是将来在pprika.request上可以直接调用的。

    property装饰的json方法使用了json.load将请求体中的data转化为json,方便之后restful模块的使用

    __load__方法在路由匹配时使用 ↓

    class RequestContext(object):
    
        def match_request(self):
            try:
                res = self.url_adapter.match(return_rule=True)
                self.request.__load__(res)
            except HTTPException as e:
                self.request.routing_exception = e
                # 暂存错误,之后于handle_user_exception尝试处理

    调用url_adapter.match将返回(rule, view_args)元组,并以此调用request.__load__初始化reques.blueprint,这也是为什么当发生路由错误(404/405)时无法被蓝图处理器捕获,因为错误发生于 url_adapter.match,__load__未调用,request.blueprint保持None,因此错误总被认为是发生于app(全局)上。

    那么ctx.bind、ctx.unbind都做了什么事情?

    class RequestContext(object):
    
        def bind(self):
            global _req_ctx_ls
            _req_ctx_ls.ctx = self
            self.match_request()
    
        def unbind(self):
            _req_ctx_ls.__release_local__()

    主要是关于_req_ctx_ls中ctx属性的设置与清空,然后就是match_request的调用

    其中 _req_ctx_ls 表示:request_context_localStorage,顾名思义,是用来存储请求上下文的对象,而且是线程隔离的 ↓

    _req_ctx_ls

    from werkzeug.local import LocalProxy, Local
    
    _req_ctx_ls = Local()  # request_context_localStorage, 只考虑一般情况:一个请求一个ctx
    request = LocalProxy(_get_req_object)

    此处的实现类似于bottle中对 threading.local 的使用,通过Local保证各线程/协程之间的数据隔离,而LocalProxy则是对Local的代理,它接收Local实例或函数作为参数,将所有请求转发给代理的Local,主要是为了多app、多请求的考虑引入(实际上pprika应该不需要它)。

    在flask中 _req_ctx_ls是LocalStack的子类,以栈结构保存请求上下文对象,但pprika并不考虑测试、开发时可能出现的多app、多请求情况,因此简化了。同时比起flask,pprika还有session、current_app、g对象还没实现,但原理大体相同。

    def _get_req_object():
        try:
            ctx = _req_ctx_ls.ctx
            return getattr(ctx, 'request')
        except KeyError:
            raise RuntimeError('脱离请求上下文!')

    因此 request = LocalProxy(_get_req_object) 实际上表示使用request时,使用的就是 _req_ctx_ls.ctx.request,而_req_ctx_ls.ctx又会在wsgi_app中通过ctx.bind初始化为RequestContext实例,并在最后情空,做到一个请求对应一个请求上下文。

    至此请求上下文已经实现,之后就可以通过 from pprika import request 使用request全局变量。

    简要介绍Helpers

    还记得之前 wsgi_app 与 handle_exception 用到的函数make_response吗?它负责接受视图函数返回值并生成响应对象。

    make_response

     1 def make_response(rv=None):
     2     status = headers = None
     3 
     4     if isinstance(rv, (BaseResponse, HTTPException)):
     5         return rv
     6 
     7     if isinstance(rv, tuple):
     8         len_rv = len(rv)
     9         if len_rv == 3:
    10             rv, status, headers = rv
    11         elif len_rv == 2:
    12             if isinstance(rv[1], (Headers, dict, tuple, list)):
    13                 rv, headers = rv
    14             else:
    15                 rv, status = rv
    16         elif len_rv == 1:
    17             rv = rv[0]
    18         else:
    19             raise TypeError(
    20                 '视图函数返回值若为tuple至少要有响应体body,'
    21                 '可选status与headers,如(body, status, headers)'
    22             )
    23 
    24     if isinstance(rv, (dict, list)):
    25         rv = compact_dumps(rv)
    26         headers = Headers(headers)
    27         headers.setdefault('Content-type', 'application/json')
    28     elif rv is None:
    29         pass
    30     elif not isinstance(rv, (str, bytes, bytearray)):
    31         raise TypeError(f'视图函数返回的响应体类型非法: {type(rv)}')
    32 
    33     response = Response(rv, status=status, headers=headers)
    34     return response

    其中rv为视图函数返回值,可以是(body, status, headers)三元组、或仅有body、或body与status,body与headers的二元组、或响应实例,还可以是HTTPException异常实例。根据不同的参数会采取不同的处理措施,最终都是返回Response实例作为响应对象,总体上近似于flask.app.make_response,但没那么细致。

    在行号为24-27处是为restful功能考虑的,它会将dict、list类型的rv尝试转化为json。

    compact_dumps

    本质上是对json.dumps的一个封装,实现紧凑的json格式化功能

    from json import dumps
    from functools import partial
    
    json_config = {'ensure_ascii': False, 'indent': None, 'separators': (',', ':')}
    compact_dumps = partial(dumps, **json_config)

    结语

    这样pprika的主要功能就已经都讲完了,作为框架可以应对小型demo的程度

    下一篇将介绍blueprint的实现,使项目结构更有组织

    [python] pprika:基于werkzeug编写的web框架(5) ——蓝图blueprint

  • 相关阅读:
    vim在插入模式下的粘贴
    关于外语学习的7个“美丽误会”(图)
    美国在线教育的启示:教育领域正在革命
    自动生成计算题
    字符串的截取,分割,替换
    将字符串转成byte,然后再转回来 日期相互转化字符串
    java编写的万年历代码
    小球弹砖块游戏(JAVA)
    随即输入一段字符串,统计出数字,大写字母。小写字母,空格
    邮箱验证信息
  • 原文地址:https://www.cnblogs.com/Stareven233/p/12973988.html
Copyright © 2011-2022 走看看