Flask 源碼閱讀筆記,flask源碼閱讀

來源:互聯網
上載者:User

Flask 源碼閱讀筆記,flask源碼閱讀

我覺得我已經養成了一個壞習慣,在使用一個架構過程中對它的內部原理非常感興趣,有時候需要花不少精力才

明白,這也導致了學習的緩慢,但換來的是對架構的內部機理的熟悉,正如侯捷所說,源碼面前,了無秘密。這也是

本文產生的直接原因。

一.flask session原理flask的session是通過用戶端的cookie實現的,不同於diango的伺服器端實現,flask通過itsdangerous這個苦將session的內容序列化到瀏覽器的cookie,當瀏覽器再次請求時將還原序列化cookie內容,也就得到我們的session內容。比如說session['name']='kehan',用戶端session如下,


我們來解密這個cookie儲存了什麼值該cookie通過.分割,分成了三部分:內容序列化+時間+防篡改值


通過第一部分我們就獲得了session['name']的值,我們看看第二部分


第二部分儲存的是時間,itsdangerous庫為了減少時間戳記的值,之前減掉了118897263,所以我們要加上。這個時間flask是用來判斷session是否到期使用的。第三部分是session值和時間戳記以及我們SECRET_KEY的防篡改值,通過HMAC演算法簽名。也就是說即使你修改了前面的值,由於簽名值有誤,flask不會使用該session。所以一定要儲存好SECRET_KEY,以及確保它的複查度,不然一旦讓別人知道了SECRET_KEY,就可以通過構造cookie偽造session值,這是很恐怖的一件事。
我們知道一般為了保護session,所以session的產生還會包含用戶端user_agent,remete_addr等,如果你覺得使用flask提供的保護力度不夠,可以使用flask_login這個擴充,一幫在flask使用認證時都會使用這個擴充,簡單易用,還提供了更加強度的session保護。
二. flask擴充import 原理我喜歡flask的一個理由就是匯入簡單,非擴充的都可以通過from flask匯入,擴充的都是通過from flask.ext.匯入,非常簡潔。用django的過程中,經常不記得該從哪裡匯入,在flask的世界裡,你無需煩惱。那麼flask的擴充匯入原理是什麼呢?主要通過sys.meta_path實現的
當匯入 from falsk.ext.example import E是將會執行flask/ext/__init__.py
def setup():    from ..exthook import ExtensionImporter    importer = ExtensionImporter(['flask_%s', 'flaskext.%s'], __name__)    importer.install()
install將會向sys.meta_path添加模組裝載類,當import時會調用其find_module,如果返回非None,會調用load_module載入
比如當我們 from flask.ext.script import Manager時
會調用find_module('flask.ext.script'),prefinx是flask.ext所以將會調用load_module()
此時將會嘗試import flask_script模組或flaskext.script
   def install(self):        sys.meta_path[:] = [x for x in sys.meta_path if self != x] + [self]    def find_module(self, fullname, path=None):        if fullname.startswith(self.prefix):            return self    def load_module(self, fullname):        modname = fullname.split('.', self.prefix_cutoff)[self.prefix_cutoff]        for path in self.module_choices:            realname = path % modname            __import__(realname)

三. flask sqlalchemy原理
sqlalchemy是python中最強大的orm架構,無疑sqlalchemy的使用比django內建的orm要複雜的多,使用flask sqlalchemy擴充將拉近和django的簡單易用距離。先來說兩個比較重要的配置
app.config['SQLALCHEMY_ECHO'] = True =》配置輸出sql語句
app.config['SQLALCHEMY_COMMIT_ON_TEARDOWN'] = True =》每次request自動認可db.session.commit(),
如果有一天你發現別的寫的視圖中有db.session.add,但沒有db.session.commit,不要疑惑,他肯定配置了上面的選項。這是通過app.teardown_appcontext註冊實現
        @teardown        def shutdown_session(response_or_exc):            if app.config['SQLALCHEMY_COMMIT_ON_TEARDOWN']:                if response_or_exc is None:                    self.session.commit()            self.session.remove()            return response_or_exc
response_or_exc為異常值,預設為sys.exc_info()[1]
上面self.session.remove()表示每次請求後都會銷毀self.session,為什麼要這麼做呢?
這就要說說sqlalchemy的session對象了。
from sqlalchemy.orm import sessionmaker
session = sessionmaker()
一幫我們會通過sessionmaker()這個工廠函數建立session,但這個session並不能用在多線程中,為了支援多線程操作,sqlalchemy提供了scoped_session,通過名字反映出scoped_session是通過某個範圍實現的
所以在多線程中一幫都是如下使用session
from sqlalchemy.orm import scoped_session, sessionmaker
session = scoped_session(sessionmaker())

我們來看看scoped_session是如何提供多線程環境支援的
class scoped_session(object):    def __init__(self, session_factory, scopefunc=None):                self.session_factory = session_factory        if scopefunc:            self.registry = ScopedRegistry(session_factory, scopefunc)        else:            self.registry = ThreadLocalRegistry(session_factory)
__init__中,session_factory是建立session的工廠函數,而sessionmaker就是一工廠函數(其實是定義了__call__的函數)而scopefunc就是能產生某個範圍的函數,如果不提供將使用ThreadLocalRegistry
class ThreadLocalRegistry(ScopedRegistry):    def __init__(self, createfunc):        self.createfunc = createfunc        self.registry = threading.local()    def __call__(self):        try:            return self.registry.value        except AttributeError:            val = self.registry.v
從上面__call__可以看出,每次都會建立新的session,並發線上程本地變數中,你可能會好奇__call__是在哪裡調用的?
def instrument(name):    def do(self, *args, **kwargs):        return getattr(self.registry(), name)(*args, **kwargs)    return dofor meth in Session.public_methods:    setattr(scoped_session, meth, instrument(meth))
正如我們所看到的,當我們調用session.query將會調用 getattr(self.registry(), 'query'),self.registry()就是調用__call__的時機,但是在flask_sqlalchemy中並沒有使用ThreadLocalRegistry,建立scoped_session過程如下

# Which stack should we use?  _app_ctx_stack is new in 0.9connection_stack = _app_ctx_stack or _request_ctx_stack    def __init__(self, app=None,                 use_native_unicode=True,                 session_options=None):        session_options.setdefault(            'scopefunc', connection_stack.__ident_func__        )        self.session = self.create_scoped_session(session_options)    def create_scoped_session(self, options=None):        """Helper factory method that creates a scoped session."""        if options is None:            options = {}        scopefunc=options.pop('scopefunc', None)        return orm.scoped_session(            partial(_SignallingSession, self, **options), scopefunc=scopefunc        )
我們看到scopefunc被設定為connection_stack.__ident_func__,而connection_stack就是flask中app上下文,
如果你看過前一篇文章你就知道__ident_func__其實就是在多線程中就是thrading.get_ident,也就是線程id
我們看看ScopedRegistry是如何通過_操作的
class ScopedRegistry(object):    def __init__(self, createfunc, scopefunc):        self.createfunc = createfunc        self.scopefunc = scopefunc        self.registry = {}    def __call__(self):        key = self.scopefunc()        try:            return self.registry[key]        except KeyError:            return self.registry.setdefault(key, self.createfunc())
代碼也很簡單,其實也就是根據線程id建立對應的session對象,到這裡我們基本已經瞭解了flask_sqlalchemy的魔法了,和flask cookie,g有異曲同工之妙,這裡有兩個小問題?
1.flask_sqlalchemy能否使用ThreadLocalRegistry?
    大部分情況都是可以的,但如果wsgi對多並發使用的是greenlet的模式就不適用了
2.上面create_scoped_session中partial是幹嘛的?
    前面我們說過scoped_session的session_factory是可調用對象,但_SignallingSession類並沒有定義__call__,所以通過partial支援

到這裡你就知道為什麼每次請求結束要self.session.remove(),不然為導致存放session的欄位太大

這裡說一下對db.relationship lazy的理解,看如下代碼
class Role(db.Model):    __tablename__ = 'roles'    id = db.Column(db.Integer, primary_key=True)    name = db.Column(db.String(64), unique=True)    users = db.relationship('User', backref='role', lazy='dynamic')class User(db.Model):    __tablename__ = 'users'    id = db.Column(db.Integer, primary_key=True)    username = db.Column(db.String(64), unique=True, index=True)    role_id = db.Column(db.Integer, db.ForeignKey('roles.id'))
假設role是已經擷取的一個Role的執行個體
lazy:dynamic => role.users不會返回User的列表, 返回的是sqlalchemy.orm.dynamic.AppenderBaseQuery對象
                當執行role.users.all()是才會真正執行sql,這樣的好處就是可以繼續過濾

lazy:select => role.users直接返回User執行個體的列表,也就是直接執行sql

注意:db.session.commit只有在對象有變化時才會真的執行update

四. flask moment原理flask moment簡單封裝了moment.js,moment.js通過js提供了對時間的支援,感興趣的童鞋可以關注下,功能非常強大。flask moment原理很簡單,使用帶有時間的格式話字串在dom載入後,使用moment.js處理一下,
該步操作有moment.include_moment()完成。如果使用其它語言,如中文,調用moment.lang('zh-cn')
如果使用了flask bootstrap,只需要在最後添加以下代碼即可(需要jquery支援)
{% block scripts %}{{ super() }}{{ moment.include_moment() }}{{ moment.lang('zh-cn') }}{% endblock %}
flask moment還提供了過了多長時間統計,refresh為True時,每分鐘重新整理一次,refresh也可為具體的重新整理時間,單位為分鐘
{{ moment(current_time).fromNow(refresh=True) }}

看上面我們知道,flask moment在模板中匯入了moment這個對象,這是如何?的呢?
    def init_app(self, app):        if not hasattr(app, 'extensions'):            app.extensions = {}        app.extensions['moment'] = _moment        app.context_processor(self.context_processor)    @staticmethod    def context_processor():        return {            'moment': current_app.extensions['moment']        }
通過app.context_processor給模板上下文添加了額為屬性
def render_template(template_name_or_list, **context):
    ctx.app.update_template_context(context)

在render_template中會把前面註冊的變數添加到context,所以在模板中就可以使用moment了,
而flask bootstrap是通過app.jinja_env.globals['bootstrap_find_resource'] = bootstrap_find_resource實現的

我們知道flask在初始化jinja環境的時候就將request,g,session等注入到全域了
rv.globals.update(            url_for=url_for,            get_flashed_messages=get_flashed_messages,            config=self.config,            # request, session and g are normally added with the            # context processor for efficiency reasons but for imported            # templates we also want the proxies in there.            request=request,            session=session,            g=g        )
但我在看源碼時發現_default_template_ctx_processor也會注入g,request,如下
def _default_template_ctx_processor():    """Default template context processor.  Injects `request`,    `session` and `g`.    """    reqctx = _request_ctx_stack.top    appctx = _app_ctx_stack.top    rv = {}    if appctx is not None:        rv['g'] = appctx.g    if reqctx is not None:        rv['request'] = reqctx.request        rv['session'] = reqctx.session    return rv
這不是重複嘛,有啥必要呢?
哈哈,認真看上面rv.globals.update的注釋部分能大概明白。
flask模板可以使用宏,需要使用import匯入,此時匯入的模板不能訪問不能訪問當前模板的本地變數,只能使用全域變數。
這也就是為什麼global中有g,request,session的理由,也即是為了支援在宏中使用g對象而本地變數匯入g等是為了效率的原因,具體細節需要參考jinja2的文檔。







跪:H264標準的T264原始碼的閱讀筆記或註解

zhidao.baidu.com/...%C2%EB
 
怎閱讀開原始碼

通過閱讀開原始碼,可以學習其中的架構和模式、代碼技巧、演算法等。但是經常有些人拿到開原始碼以後不知道如何下手,這裡分享一下我的一些經驗。
1.拿到代碼以後編譯運行它,瞭解它幹什麼的,實現了功能。
2.從進入點開始閱讀,按照流程,瞭解這個軟體是怎麼運行起來的。
3.從整體上觀察這個軟體是有哪些模組組成的,最好能畫一畫圖。
4.修改軟體的功能,並且能夠調試它。不能僅僅停留在閱讀代碼上,一定要改代碼,這樣才能增加對開原始碼的理解。
5.做筆記,把這個開原始碼實現的功能,實現方式、設計架構、模組組織、以及代碼技巧等記錄下來。

以上,這些僅僅是我個人的經驗,希望可以幫組到您。
 

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.