小男孩‘自慰网亚洲一区二区,亚洲一级在线播放毛片,亚洲中文字幕av每天更新,黄aⅴ永久免费无码,91成人午夜在线精品,色网站免费在线观看,亚洲欧洲wwwww在线观看

分享

python Flask

 java_laq小館 2014-11-18

Flask 源碼閱讀筆記

分類: python 545人閱讀 評論(0) 收藏 舉報

我覺得我已經(jīng)養(yǎng)成了一個壞習慣,在使用一個框架過程中對它的內(nèi)部原理非常感興趣,有時候需要花不少精力才

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

本文產(chǎn)生的直接原因。

一.flask session原理

flask的session是通過客戶端的cookie實現(xiàn)的,不同于diango的服務器端實現(xiàn),flask通過itsdangerous這個苦
將session的內(nèi)容序列化到瀏覽器的cookie,當瀏覽器再次請求時將反序列化cookie內(nèi)容,也就得到我們的session內(nèi)容。
比如說session['name']='kehan',客戶端session如下,



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



通過第一部分我們就獲得了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的過程中,經(jīng)常不記得該從哪里導入,在flask的世界里,你無需煩惱。那么flask的擴展
導入原理是什么呢?
主要通過sys.meta_path實現(xiàn)的
當導入 from falsk.ext.example import E是將會執(zhí)行flask/ext/__init__.py
  1. def setup():  
  2.     from ..exthook import ExtensionImporter  
  3.     importer = ExtensionImporter(['flask_%s''flaskext.%s'], __name__)  
  4.     importer.install()  
install將會向sys.meta_path添加模塊裝載類,當import時會調(diào)用其find_module,如果返回非None,會調(diào)用load_module加載
比如當我們 from flask.ext.script import Manager時
會調(diào)用find_module('flask.ext.script'),prefinx是flask.ext所以將會調(diào)用load_module()
此時將會嘗試import flask_script模塊或flaskext.script

  1. def install(self):  
  2.      sys.meta_path[:] = [x for x in sys.meta_path if self != x] + [self]  
  3.   
  4.  def find_module(self, fullname, path=None):  
  5.      if fullname.startswith(self.prefix):  
  6.          return self  
  7.   
  8.  def load_module(self, fullname):  
  9.      modname = fullname.split('.'self.prefix_cutoff)[self.prefix_cutoff]  
  10.      for path in self.module_choices:  
  11.          realname = path % modname  
  12.          __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(),
如果有一天你發(fā)現(xiàn)別的寫的視圖中有db.session.add,但沒有db.session.commit,不要疑惑,他肯定配置了上面
的選項。
這是通過app.teardown_appcontext注冊實現(xiàn)
  1. @teardown  
  2. def shutdown_session(response_or_exc):  
  3.     if app.config['SQLALCHEMY_COMMIT_ON_TEARDOWN']:  
  4.         if response_or_exc is None:  
  5.             self.session.commit()  
  6.     self.session.remove()  
  7.     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()這個工廠函數(shù)創(chuàng)建session,但這個session并不能用在多線程中,為了支持多線程
操作,sqlalchemy提供了scoped_session,通過名字反映出scoped_session是通過某個作用域?qū)崿F(xiàn)的
所以在多線程中一幫都是如下使用session
from sqlalchemy.orm import scoped_session, sessionmaker
session = scoped_session(sessionmaker())

我們來看看scoped_session是如何提供多線程環(huán)境支持的
  1. class scoped_session(object):  
  2.     def __init__(self, session_factory, scopefunc=None):  
  3.           
  4.         self.session_factory = session_factory  
  5.         if scopefunc:  
  6.             self.registry = ScopedRegistry(session_factory, scopefunc)  
  7.         else:  
  8.             self.registry = ThreadLocalRegistry(session_factory)  
__init__中,session_factory是創(chuàng)建session的工廠函數(shù),而sessionmaker就是一工廠函數(shù)(其實是定義了__call__的
函數(shù))而scopefunc就是能產(chǎn)生某個作用域的函數(shù),如果不提供將使用ThreadLocalRegistry
  1. class ThreadLocalRegistry(ScopedRegistry):  
  2.     def __init__(self, createfunc):  
  3.         self.createfunc = createfunc  
  4.         self.registry = threading.local()  
  5.   
  6.     def __call__(self):  
  7.         try:  
  8.             return self.registry.value  
  9.         except AttributeError:  
  10.             val = self.registry.v  
從上面__call__可以看出,每次都會創(chuàng)建新的session,并發(fā)在線程本地變量中,你可能會好奇__call__是在哪里調(diào)用的?
  1. def instrument(name):  
  2.     def do(self, *args, **kwargs):  
  3.         return getattr(self.registry(), name)(*args, **kwargs)  
  4.     return do  
  5.   
  6. for meth in Session.public_methods:  
  7.     setattr(scoped_session, meth, instrument(meth))  
正如我們所看到的,當我們調(diào)用session.query將會調(diào)用 getattr(self.registry(), 'query'),self.registry()就是
調(diào)用__call__的時機,但是在flask_sqlalchemy中并沒有使用ThreadLocalRegistry,創(chuàng)建scoped_session過程如下

  1. # Which stack should we use?  _app_ctx_stack is new in 0.9  
  2. connection_stack = _app_ctx_stack or _request_ctx_stack  
  3.   
  4.     def __init__(self, app=None,  
  5.                  use_native_unicode=True,  
  6.                  session_options=None):  
  7.         session_options.setdefault(  
  8.             'scopefunc', connection_stack.__ident_func__  
  9.         )  
  10.         self.session = self.create_scoped_session(session_options)  
  11.   
  12.     def create_scoped_session(self, options=None):  
  13.         """Helper factory method that creates a scoped session."""  
  14.         if options is None:  
  15.             options = {}  
  16.         scopefunc=options.pop('scopefunc'None)  
  17.         return orm.scoped_session(  
  18.             partial(_SignallingSession, self, **options), scopefunc=scopefunc  
  19.         )  
我們看到scopefunc被設置為connection_stack.__ident_func__,而connection_stack就是flask中app上下文,
如果你看過前一篇文章你就知道__ident_func__其實就是在多線程中就是thrading.get_ident,也就是線程id
我們看看ScopedRegistry是如何通過_操作的

  1. class ScopedRegistry(object):  
  2.     def __init__(self, createfunc, scopefunc):  
  3.         self.createfunc = createfunc  
  4.         self.scopefunc = scopefunc  
  5.         self.registry = {}  
  6.   
  7.   
  8.     def __call__(self):  
  9.         key = self.scopefunc()  
  10.         try:  
  11.             return self.registry[key]  
  12.         except KeyError:  
  13.             return self.registry.setdefault(key, self.createfunc())  
代碼也很簡單,其實也就是根據(jù)線程id創(chuàng)建對應的session對象,到這里我們基本已經(jīng)了解了flask_sqlalchemy的
魔法了,和flask cookie,g有異曲同工之妙,這里有兩個小問題?
1.flask_sqlalchemy能否使用ThreadLocalRegistry?

    大部分情況都是可以的,但如果wsgi對多并發(fā)使用的是greenlet的模式就不適用了
2.上面create_scoped_session中partial是干嘛的?
    前面我們說過scoped_session的session_factory是可調(diào)用對象,但_SignallingSession類并沒有定義__call__,所以通過partial支持

到這里你就知道為什么每次請求結束要self.session.remove(),不然為導致存放session的字段太大

這里說一下對db.relationship lazy的理解,看如下代碼
  1. class Role(db.Model):  
  2.     __tablename__ = 'roles'  
  3.     id = db.Column(db.Integer, primary_key=True)  
  4.     name = db.Column(db.String(64), unique=True)  
  5.     users = db.relationship('User', backref='role', lazy='dynamic')  
  6.   
  7.   
  8. class User(db.Model):  
  9.     __tablename__ = 'users'  
  10.     id = db.Column(db.Integer, primary_key=True)  
  11.     username = db.Column(db.String(64), unique=True, index=True)  
  12.     role_id = db.Column(db.Integer, db.ForeignKey('roles.id'))  
假設role是已經(jīng)獲取的一個Role的實例
lazy:dynamic => role.users不會返回User的列表, 返回的是sqlalchemy.orm.dynamic.AppenderBaseQuery對象
                當執(zhí)行role.users.all()是才會真正執(zhí)行sql,這樣的好處就是可以繼續(xù)過濾

lazy:select => role.users直接返回User實例的列表,也就是直接執(zhí)行sql

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


四. flask moment原理

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


看上面我們知道,flask moment在模板中導入了moment這個對象,這是如何實現(xiàn)的呢?
  1. def init_app(self, app):  
  2.     if not hasattr(app, 'extensions'):  
  3.         app.extensions = {}  
  4.     app.extensions['moment'] = _moment  
  5.     app.context_processor(self.context_processor)  
  6.  
  7. @staticmethod  
  8. def context_processor():  
  9.     return {  
  10.         'moment': current_app.extensions['moment']  
  11.     }  
通過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實現(xiàn)的

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

來自:http://blog.csdn.net/yueguanghaidao/article/details/40016235

    本站是提供個人知識管理的網(wǎng)絡存儲空間,所有內(nèi)容均由用戶發(fā)布,不代表本站觀點。請注意甄別內(nèi)容中的聯(lián)系方式、誘導購買等信息,謹防詐騙。如發(fā)現(xiàn)有害或侵權內(nèi)容,請點擊一鍵舉報。
    轉(zhuǎn)藏 分享 獻花(0

    0條評論

    發(fā)表

    請遵守用戶 評論公約

    類似文章 更多