Flask 请求处理流程(一):WSGI 和 路由

flask 一个基于 python 实现的Web开发微框架,主要依赖:

  • Werkzeug:一个 python 的 WSGI 工具包,也可以作为一个 Web 框架的底层库。
  • Jinja2:为 python 提供的一个功能齐全的模板引擎

flask 只建立 WerkezugJinja2 的桥梁,前者实现一个合适的 WSGI 应用,后者处理模板。 Flask 也绑定了一些通用的标准库包,比如 logging 。其它所有一切取决于扩展。

### 一、WSGI

WSGI(Web Server Gateway Interface)的本质是一种约定,是 Python web 开发中 web 服务器与 web 应用程序之间数据交互的约定。它封装了接受 HTTP 请求解析 HTTP 请求发送 HTTP,响应等等的这些底层的代码和操作,使开发者可以高效的编写Web应用。

网关协议的本质是为了解耦,实现 web 服务器(提供 web 服务)和 web 应用程序(资源处理)的分离,WSGI 就是一个支持 WSGI 的 web 服务器与 Python web 应用程序之间的约定。

WSGI

WSGI 服务器

一个 WSGI 服务器需要实现两个函数

1、解析 http 请求,为应用程序提供 environ 字典
def get_environ(self):
    env = {}
    env['wsgi.version']      = (1, 0)
    env['wsgi.url_scheme']   = 'http'
    env['wsgi.input']        = StringIO.StringIO(self.request_data)
    env['wsgi.errors']       = sys.stderr
    env['wsgi.multithread']  = False
    env['wsgi.multiprocess'] = False
    env['wsgi.run_once']     = False
    env['REQUEST_METHOD']    = self.request_method    # GET
    env['PATH_INFO']         = self.path              # /hello
    env['SERVER_NAME']       = self.server_name       # localhost
    env['SERVER_PORT']       = str(self.server_port)  # 8888
    return env
2、实现 start_response 函数
def start_response(self, status, response_headers, exc_info=None):
    # Add necessary server headers
    server_headers = [
        ('Date', 'Tue, 31 Mar 2015 12:54:48 GMT'),
        ('Server', 'WSGIServer 0.2'),
    ]
    self.headers_set = [status, response_headers + server_headers]
    # To adhere to WSGI specification the start_response must return
    # a 'write' callable. We simplicity's sake we'll ignore that detail
    # for now.

WSGI 服务器

服务器与应用程序交互过程

主要过程是:

服务器从客户端获取到请求,然后通过 get_env 获得 env 变量。再调用应用程序,传入 env 变量(字典) 和 start_response 函数, 并获得响应。最后将响应返回给客户端。

服务器与应用程序交互过程

代码如下:

def handle_one_request(self):
    self.request_data = request_data = self.client_connection.recv(1024)
    print(''.join(
        '< {line}\n'.format(line=line)
        for line in request_data.splitlines()
    ))
    self.parse_request(request_data)
    env = self.get_environ()   #获取 environ
    result = self.application(env, self.start_response)  #调用应用程序
    self.finish_response(result)

在上述这个过程中,Python 应用程序主要工作就是根据输入的 environ 字典信息生成相应的 http 报文返回给服务器

下面是一个简单的例子:

from wsgiref.simple_server import make_server

def simple_app(environ, start_response):
  status = '200 OK'
  response_headers = [('Content-type', 'text/plain')]
  start_response(status, response_headers)
  return [u"This is hello wsgi app".encode('utf8')]

httpd = make_server('', 8000, simple_app)
print "Serving on port 8000..."
httpd.serve_forever()

其中:

  • environ: 一个包含全部 HTTP 请求信息的字典,由 WSGI Server 解包 HTTP 请求生成。
  • start_response: 一个 WSGI Server 提供的函数,调用可以发送响应的状态码和 HTTP 报文头, 函数在返回前必须调用一次 start_response()
  • simple_app() 应当返回一个可以迭代的对象(HTTP正文)。
  • simple_app() 函数由 WSGI Server 直接调用和提供参数。
  • Python 内置了一个 WSGIREFWSGI Server,不过性能不是很好,一般只用在开发环境。可以选择其他的如 Gunicorn
总结

WSGI Server 和 App交互图

#### flask 中实现 WSGI 接口

1.通过 __call__ 方法将 Flask 对象变为可调用
def __call__(self, environ, start_response):
    """Shortcut for :attr:`wsgi_app`."""
    return self.wsgi_app(environ, start_response)
2. 实现 wsgi_app 函数处理 web 服务器转发的请求
def wsgi_app(self, environ, start_response):
    """
    The actual WSGI application.
    :param environ: a WSGI environment
    :param start_response: a callable accepting a status code,
                       a list of headers and 
                       an optional exception context to start the responseresponse
    """
    ctx = self.request_context(environ)
    error = None
    try:
        try:
            ctx.push()
            response = self.full_dispatch_request()
        except Exception as e:
            error = e
            response = self.handle_exception(e)
        except:
            error = sys.exc_info()[1]
            raise
        return response(environ, start_response)
    finally:
        if self.should_ignore_error(error):
            error = None
        ctx.auto_pop(error)

### 二、路由

Flask 类中支持路由功能的数据结构,在 __init__ 函数中初始化:

url_rule_class = Rule
self.url_map = Map()
self.view_functions = {}

Rule 和 Map 是 werkzeug 中实现的 路由类映射类

>>> m = Map([
  ...     # Static URLs
  ...     Rule('/', endpoint='static/index'),
  ...     Rule('/about', endpoint='static/about'),
  ...     Rule('/help', endpoint='static/help'),
  ...     # Knowledge Base
  ...     Subdomain('something', [
  ...         Rule('/', endpoint='something/index'),
  ...         Rule('/browse/', endpoint='something/browse'),
  ...         Rule('/browse/<int:id>/', endpoint='something/browse'),
  ...         Rule('/browse/<int:id>/<int:page>', endpoint='something/browse')
  ...     ])
  ... ], default_subdomain='www')

通过 Map 我们就可以实现动态路由的功能。这里我们注意到 Map 类先建立了 url 到 endpoint 的映射,而不是直接映射到函数,这是为什么呢?

主要是两个原因:

  • 一是为了实现动态路由功能,
  • 二是为不同的 url 映射到同一个视图函数提供了便利。

view_functions 是一个字典,它负责建立 endpoint 和视图函数之间的映射关系。

下面是一个小实验,证明我们所说的映射关系:

>>> from flask import Flask
>>> app = Flask(__name__)
>>> @app.route('/')
... def index():
...     return "hello world"
... 
>>> app.url_map
Map([<Rule '/' (HEAD, GET, OPTIONS) -> index>,
<Rule '/static/<filename>' (HEAD, GET, OPTIONS) -> static>])

>>> app.view_functions
{'index': <function index at 0x7f6ced14c840>, 'static': <bound method _PackageBoundObject.send_static_file of <Flask '__main__'>>}

这里我们可以看到

<Rule '/' (HEAD, GET, OPTIONS) -> index>,'index': <function index at 0x7f6ced14c840>

通过 endpoint 这个中间量,我们让把 路由函数 建立了映射关系。

要注意一下,为什么会有 '/static/<filename>' 这个路由呢,这是因为在初始化时 flask 调用了 add_url_rule 函数做了如下绑定:

if self.has_static_folder:
    assert bool(static_host) == host_matching, 'Invalid static_host/host_matching combination'
    self.add_url_rule(
        self.static_url_path + '/<path:filename>',
        endpoint='static',
        host=static_host,
        view_func=self.send_static_file
    )
总结

Flask 理由

注册路由

在 flask 中注册路由有两种方式,

  • 一种是用 route 装饰器,如上所示,@app.route()
  • 另一种是直接调用 add_url_rule 函数绑定视图类,

但是本质上二者都是调用 add_url_rule 函数,下面我们来看一下 add_url_rule 函数的实现。

在 Flask 的 add_url_rule 函数很长,但是核心的代码为以下几行:

self.url_map.add(rule)
rule = self.url_rule_class(rule, methods=methods, **options)
self.view_functions[endpoint] = view_func
1. 装饰器
def route(self, rule, **options):
    def decorator(f):
        endpoint = options.pop('endpoint', None)
        self.add_url_rule(rule, endpoint, f, **options)
        return f
    return decorator
2. 视图类
class CounterAPI(MethodView):
    def get(self):
        return session.get('counter', 0)
    def post(self):
        session['counter'] = session.get('counter', 0) + 1
        return 'OK'
    app.add_url_rule('/counter', view_func=CounterAPI.as_view('counter'))

注册路由之后,flask 就需要分发路由,调用相应的视图函数。

def dispatch_request(self):
    req = _request_ctx_stack.top.request
    if req.routing_exception is not None:
        self.raise_routing_exception(req)
    rule = req.url_rule
    if getattr(rule, 'provide_automatic_options', False) \
       and req.method == 'OPTIONS':
        return self.make_default_options_response()
    return self.view_functions[rule.endpoint](**req.view_args)

转载于:https://www.cnblogs.com/wbsn/p/10189138.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值