[英]Pyramid: sessions and static assets
讓我解釋一下這個問題:
我通過金字塔服務我的靜態資產:
config.add_static_view(name='static', path='/var/www/static')
而且效果很好。
現在,我有一個自定義會話工廠,可以在數據庫中創建會話。 它檢查瀏覽器是否顯示會話cookie。 如果是這樣,它將從數據庫中找到一個會話。 如果沒有,那么將在數據庫中創建一個新會話,並將cookie返回到瀏覽器。
到目前為止,一切都很好。
現在,在我的home_view
(生成我的主頁)中,我不會以任何方式訪問請求變量:
@view_config(route_name='home', renderer="package:templates/home.mak")
def home_view(request):
return {}
因此,發生的情況是當用戶訪問主頁時,不會在服務器上創建會話。 我認為這是因為Pyramid 延遲創建了會話-僅當您訪問request.session
。 因此,主頁請求的響應頭不包含任何會話的Set-Cookie
頭。
現在,在主頁的mako模板中,我正在為JavaScript和CSS文件生成靜態URL。
<link rel="stylesheet" href="${request.static_url(...)}"
<script src="${request.static_url(...)}"></script>
現在,由於我正在為Pyramid提供靜態資產,因此對這些資產的所有請求都將貫穿整個Pyramid機械。
因此,發生什么情況是當我的瀏覽器發送請求以獲取靜態資產時,金字塔如何創建會話。 也就是說,Pyramid在數據庫中創建會話,並在瀏覽器發送對靜態資產的請求時發回會話cookie。 這是問題1。
瀏覽器並行發送所有對靜態資產的請求。 我正在使用最新版本的Firefox和Chrome。 由於對實際HTML文檔的HTTP請求沒有返回任何Set-Cookie
標頭,因此對靜態資產的請求沒有任何cookie標頭。 這意味着Pyramid看不到任何請求的會話cookie,並在數據庫中為靜態資產的每個請求創建一個新會話。
如果要在我的主頁上獲取7個靜態資產,則會創建7個會話條目。 這是因為所有這些請求都與服務器並行進行,並且沒有任何請求具有會話cookie,因此Pyramid為每個請求創建一個會話。
如果我作為主頁請求的一部分故意訪問會話,則不會出現此問題。 它在數據庫中創建一個會話,然后向瀏覽器發送一個cookie,然后瀏覽器針對從服務器請求的每個靜態資產(並行)發送回cookie。
@view_config(route_name='home', renderer="package:templates/home.mak")
def home_view(request):
if request.session: pass
return {}
如何防止在靜態資產請求上創建會話。 更好的是,我希望Pyramid在收到靜態資產請求時甚至不接觸會話工廠-這可能嗎?
其次,我不明白為什么金字塔要根據靜態請求創建新會話?
更新
這是會話工廠。
def DBSessionFactory(
secret,
cookie_name="sess",
cookie_max_age=None,
cookie_path='/',
cookie_domain=None,
cookie_secure=False,
cookie_httponly=False,
cookie_on_exception=True
):
# this is the collable that will be called on every request
# and will be passed the request
def factory(request):
cookieval = request.cookies.get(cookie_name)
session_id = None
session = None
# try getting a possible session id from the cookie
if cookieval is not None:
try:
session_id = signed_deserialize(cookieval, secret)
except ValueError:
pass
# if we found a session id from the cookie
# we try loading the session
if session_id is not None:
# _load_session will return an object that implements
# the partial dict interface (not complete, just the basics)
session = _load_session(session_id)
# if no session id from cookie or no session found
# for the id in the database, create new
if session_id is None or session is None:
session = _create_session()
def set_cookie(response):
exc = getattr(request, 'exception', None)
if exc is not None and cookie_on_exception == False:
return
cookieval = signed_serialize(session.session_id, secret)
response.set_cookie(
cookie_name,
value=cookieval,
max_age = cookie_max_age,
path = cookie_path,
domain = cookie_domain,
secure = cookie_secure,
httponly = cookie_httponly,
)
def delete_cookie(response):
response.delete_cookie(
cookie_name,
path = cookie_path,
domain = cookie_domain,
)
def callback(request, response):
if session.destroyed:
_purge_session(session)
delete_cookie(response)
return
if session.new:
set_cookie(response)
# this updates any changes to the session
_update_session(session)
# at the end of request
request.add_response_callback(callback)
# return the session from a call to the factory
return session
# return from session factory
return factory
然后,
factory = DBSessionFactory('secret')
config.set_session_factory(factory)
更新
我的自定義身份驗證:
class RootFactory:
__acl__ = [
(Allow, Authenticated, 'edit'),
# only allow non authenticated users to login
(Deny, Authenticated, 'login'),
(Allow, Everyone, 'login'),
]
def __init__(self, request):
self.request = request
class SessionAuthenticationPolicy(CallbackAuthenticationPolicy):
def __init__(self, callback=None, debug=False):
self.callback = callback
self.debug = debug
def remember(self, request, principal, **kw):
return []
def forget(self, request):
return []
def unauthenticated_userid(self, request):
if request.session.loggedin:
return request.session.userid
else:
return None
然后,
config.set_root_factory(RootFactory)
config.set_authentication_policy(SessionAuthenticationPolicy())
config.set_authorization_policy(ACLAuthorizationPolicy())
我無法在虛擬項目中重現此行為,這使我相信您的某些配置會影響此處未顯示的內容。 顯然,如果根據身份驗證策略調用了任何身份驗證,則會創建一個會話。 靜態資產(默認情況下)已向NO_PERMISSION_REQUIRED
注冊,這意味着它們將不會調用Pyramid中的任何身份驗證API(並且我已經證明是這種情況)。
對靜態資產的請求確實會調用整個請求管道,這意味着,如果您在任何訂戶中有任何代碼,或者您的根工廠調用了has_permission
或其他安全API,或者自己直接觸摸了會話,那么這將解釋您的行為看到,因為您的會話已耦合到您的身份驗證。
這是一個重現該問題的虛擬項目:
設置一個virtualenv環境並在其中安裝Pyramid。
安裝入門項目: pcreate -s starter IssueApp
刪除所有不必要的文件,以便擁有以下簡單樹:
樹
.
├── CHANGES.txt
├── development.ini
├── issueapp
│ ├── __init__.py
│ └── static
│ └── pyramid.png
├── README.txt
└── setup.py
請注意,我們會將整個應用程序寫入__init__.py
文件中-因此將刪除所有其他內容。
現在安裝項目: (env) $ python setup.py develop
這將把您的項目安裝到虛擬環境中。
development.ini
文件:
[app:main]
use = egg:IssueApp#main
pyramid.reload_all = true
pyramid.reload_templates = true
pyramid.debug_all = true
pyramid.debug_notfound = true
pyramid.debug_routematch = true
pyramid.prevent_http_cache = true
pyramid.default_locale_name = en
[server:main]
use = egg:waitress#main
host = 0.0.0.0
port = 7777
[loggers]
keys = root, issueapp
[handlers]
keys = console
[formatters]
keys = generic
[logger_root]
level = INFO
handlers = console
[logger_issueapp]
level = INFO
handlers =
qualname = issueapp
[handler_console]
class = StreamHandler
args = (sys.stderr,)
level = NOTSET
formatter = generic
[formatter_generic]
format = %(asctime)s %(levelname)-5.5s [%(name)s][%(threadName)s] %(message)s
__init__.py
文件:
from pyramid.config import Configurator
from pyramid.view import view_config
from pyramid.response import Response
from pyramid.authentication import CallbackAuthenticationPolicy
from pyramid.authorization import ACLAuthorizationPolicy
from pyramid.security import (
Allow, Deny,
Everyone, Authenticated,
)
def main(global_config, **settings):
""" This function returns a Pyramid WSGI application.
"""
config = Configurator(settings=settings)
#config.add_static_view('static', 'static', cache_max_age=3600)
config.add_static_view(name='static', path='issueapp:static')
config.add_route('home', '/')
config.set_root_factory(RootFactory)
config.set_authentication_policy(DummyAuthPolicy())
config.set_authorization_policy(ACLAuthorizationPolicy())
config.scan()
return config.make_wsgi_app()
@view_config(route_name='home')
def home_view(request):
src = request.static_url('issueapp:static/pyramid.png')
return Response('<img src='+ src + '>')
class RootFactory:
__acl__ = [
(Allow, Authenticated, 'edit'),
(Deny, Authenticated, 'login'),
(Allow, Everyone, 'login'),
]
def __init__(self, request):
self.request = request
class DummyAuthPolicy(CallbackAuthenticationPolicy):
def __init__(self, callback=None, debug=False):
self.callback = callback
self.debug = debug
def remember(self, request, principal, **kw):
return []
def forget(self, request):
return []
def unauthenticated_userid(self, request):
# this will print the request url
# so we can know which request is causing auth code to be called
print('[auth]: ' + request.url)
# this means the user is authenticated
return "user"
現在運行應用
pserve development.ini --reload
Starting subprocess with file monitor
Starting server in PID 2303.
serving on http://0.0.0.0:7777
最后,從瀏覽器中清除所有歷史記錄(這很重要,否則問題可能不會解決)並訪問該頁面。 這被打印在控制台上:
[auth]: http://192.168.56.102:7777/static/pyramid.png
這表明身份驗證代碼正被靜態請求調用。
現在,當我將日志級別設置為DEBUG
,這是訪問該頁面時控制台的輸出:
pserve development.ini --reload Starting subprocess with file monitor Starting server in PID 2339. serving on http://0.0.0.0:7777 2013-03-27 03:40:55,539 DEBUG [issueapp][Dummy-2] route matched for url http://192.168.56.102:7777/; route_name: 'home', path_info: '/', pattern: '/', matchdict: {}, predicates: '' 2013-03-27 03:40:55,540 DEBUG [issueapp][Dummy-2] debug_authorization of url http://192.168.56.102:7777/ (view name '' against context ): Allowed (no permission registered) 2013-03-27 03:40:55,685 DEBUG [issueapp][Dummy-3] route matched for url http://192.168.56.102:7777/static/pyramid.png; route_name: '__static/', path_info: '/static/pyramid.png', pattern: 'static/*subpath', matchdict: {'subpath': ('pyramid.png',)}, predicates: '' [auth]: http://192.168.56.102:7777/static/pyramid.png 2013-03-27 03:40:55,687 DEBUG [issueapp][Dummy-3] debug_authorization of url http://192.168.56.102:7777/static/pyramid.png (view name '' against context ): ACLDenied permission '__no_permission_required__' via ACE '' in ACL [('Allow', 'system.Authenticated', 'edit'), ('Deny', 'system.Authenticated', 'login'), ('Allow', 'system.Everyone', 'login')] on context for principals ['system.Everyone', 'system.Authenticated', 'user']
請注意, [auth]: ...
消息僅被打印一次-用於靜態資產請求,而不用於主頁請求。 這很奇怪,因為這意味着針對靜態資產(而非正常請求)咨詢了auth策略。 (當然,除非涉及許可,但我認為沒有)。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.