flask 源码剖析

flask 基于 wsgi, 依赖 Werkzurg 和模板(Jinja).

innoDB ORM 更像是第三方插件,并不是 flask 需要关心的,当然也是 web 开发少不了的.

wsgi

wsgi wsgi 是 web server 与 web 开发框架之间的协议

    Nginx <-----(U)WSGI protocol-----> Gunicorn(WSGI Server) ----(U)WSGI protocol --- >  Flask(WSGI Application)

大道至简:

import time
class Server():
    def __init__(self):
        self.application = None
        self.http_stuff = None
        self.environ_and_requests = [1, 2 ,'requests' ]
    def set_app(self,app):
        self.application = app
    def construct_response(self,code,header):
        self.response_code = code
        self.response_header = header

    def run_loop(self):

        count = 100
        some_connection_coming = False
        while(count):
            some_connection_coming = False
            ## 异步回调什么的
            time.sleep(1)
            some_connection_coming = True
            #
            preprpcess_request()
            if some_connection_coming:
                # 把environ 和request给application,同时让application回调response
                response_body = self.application(self.environ_and_requests,self.construct_response)
            print('Server  got response stuff %s %s %s ' % (self.response_code,self.response_header,response_body))
            preprpcess_response()
            send_response()
            count -=1


class App():
    def application(self,environ, start_response):
        print('App got environ got requests. %s' % environ)
        start_response('200 OK', [('Content-Type', 'text/plain')])
        return ['Hello World!']


server = Server()
server.set_app(App().application)
server.run_loop()

App 就是 wsgi 的开发框架,Server 是实现了 wsgi 的 server:

App got environ got requests. [1, 2, 'requests']
Server  got response stuff 200 OK [('Content-Type', 'text/plain')] ['Hello World!']

werkzeug

1 个 wsgi 工具包,wsig 实现的底层库 werkzeug

Template

Jinja2:

Screenshot from 2019-01-02 10-13-14-30e1ea96-9c10-4e9d-99c5-326398ce586c

Mako 也不错?值得了解一下,跟 python 结合的比较好,更灵活。

orm

Object relational model.

将 ORUD (open read update delete)的 innoDB 的操作,用对象来实现,通过对象来表现,描述,修改数据库。比直接用 sql 语言,来的更方便。

flask-sqlalchemy

有的查询/插入特别慢,通过简单的配置,就可以专门的记录这些查询到转门的 log 里面.cool!

peewee

更轻量

flask

轻量的 wsgi web 开发框架

route

路由就是灵活性,不同的 url 处理不同的功能,越能应付复杂的 url 路由,web 功能越强大。如何更好的路由?

# basic
@app.add_route('/static/v/')
def v():
    print('hi')
# v替换成其他?
@app.add_route('/static/<id>/')
def v1(id):
    print('hi' +id)
# 多个变量?
@app.add_route('/static/<id1>/<id2>')
def v1(id1,id2):
    print(id1+id2)
# 正则?需要用到flask Converter
# @app.route('/api/(.*?)')

from flask import Flask
from werkzeug.routing import BaseConverter
class RegexConverter(BaseConverter):
    def __init__(self, map, *args):
        self.map = map
        self.regex = args[0]
app = Flask(__name__)
app.url_map.converters['regex'] = RegexConverter

@app.route('/docs/model_utils/<regex(".*"):url>')
def hello(url=None):
    print(url)

context

flask context 介绍

flask context->线程局部变量-->werkzurg的LocalProxy LocalStack

flask 比较重要的知识点了

LocalStack 用 Local 来存各个线程的局部变量,用线程 id 来区分

class Local(object):

    def __init__(self):
        object.__setattr__(self, '__storage__', {})
        object.__setattr__(self, '__ident_func__', get_ident)

    def __getattr__(self, name):
        try:
            return self.__storage__[self.__ident_func__()][name]
        except KeyError:
            raise AttributeError(name)

    def __setattr__(self, name, value):
        ident = self.__ident_func__()
        storage = self.__storage__
        try:
            storage[ident][name] = value
        except KeyError:
            storage[ident] = {name: value}

Local 大概是这么个结构:

{
    'thread_id1':{'stack':[_RequestContext(),_RequestContext(),_RequestContext(),..]},
    'thread_id2':{'stack':[_RequestContext(),_RequestContext(),...]}
}

LocalStack 的操作,无非是把各个线程可能产生的局部变量(name-value) push/pop,或者直接取栈顶元素.

而整个 flask,运行了一个_request_ctx_stack,保存所有的线程的 request,url 等, 全局共享.

_request_ctx_stack = LocalStack();

还有几个关键的关键变量,自然就是拿到当前 top 线程上的各种数据了.

current_app = LocalProxy(lambda: _request_ctx_stack.top.app)
request = LocalProxy(lambda: _request_ctx_stack.top.request)
session = LocalProxy(lambda: _request_ctx_stack.top.session)
g = LocalProxy(lambda: _request_ctx_stack.top.g)

通过 LocalStack 和 LocalProxy 这样的 Python 魔法,每个线程访问当前请求中的数据(request, session)时, 都好像都在访问一个全局变量,但是,互相之间又互不影响。

反过来再思考,如果不用这个机制又会有什么?那肯定视图函数里,肯定要带参数,就像 falcon 做的那样!好像也没啥不方便的.关键都是用什么样的数据结构,维护不同 url 不同请求的数据.

# flask
@add_route('/')
def index(request,environ):
    print('hello index)

# falcon
class IndexHander(object);
    """
    falcon
    """
    def on_get(self,req,response)
        print('hello get')
    def on_post(self,req,response)
        print('hello post')

app.add_route('/',IndexHander())

上面说的主要是 request context,再 flask 0.9 后还引入了新的 application context:

request context

application 机制是为了处理多应用场景的,这个比较少见.

app1 = Flask('app1')
app2 = Flask('app2')

signal

基于 Blinker,封装 signal 的事件分发的库,很多人推荐的 python 初学级别的源码库.

基于 blinker signal,在一些时机可以注册回调, 如模板渲染完成,请求上下文 ready,发送 response 前,等等.

配合 werkzurg

很强的 wsgi 底层库,很多有用的 tools 函数