Пирамида: сессии и статические активы
позвольте мне объяснить проблему:
я обслуживаю свои статические активы через пирамиду:
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 {}
из-за этого, что происходит, когда пользователь посещает домашнюю страницу, сеанс не создается на сервере. Я думаю, это потому, что пирамида создает сеансы лениво -- только при доступе к request.session
. Следовательно, заголовки ответов для запроса домашней страницы НЕ СОДЕРЖАТ никаких Set-Cookie
заголовок для сеансов.
теперь внутри моего шаблона mako для домашней страницы я создаю статические URL-адреса для файлов JavaScript и CSS...
<link rel="stylesheet" href="${request.static_url(...)}"
<script src="${request.static_url(...)}"></script>
Итак, что происходит, когда мой браузер отправляет запросы на извлечение статических активов, пирамида некоторые как создает сеанс. То есть Pyramid создает сеанс в базе данных и отправляет cookie сеанса обратно, когда браузер отправляет запросы на статические активы. Это проблема #1.
браузер отправляет все запросы на статические ресурсы в параллельно. Я использую последние версии Firefox и Chrome. Поскольку HTTP-запрос для фактического HTML-документа не вернул никакого Set-Cookie
заголовки, запросы на статические активы не имеют заголовков cookie. Это означает, что Pyramid не видит cookie сеанса для любого из запросов и создает новый сеанс в базе данных для каждого из запросов ЧТО ОН ПОЛУЧАЕТ ДЛЯ СТАТИЧЕСКОГО АКТИВА.
если я получаю 7 статических активов на моей домашней странице, и создаются 7 записей сеанса. Это связано с тем, что все эти запросы идут параллельно серверу, и ни один из них не имеет cookie сеанса, поэтому Pyramid создает сеанс для каждого.
эта проблема не возникает, если я намеренно получаю доступ к сеансу как часть запроса домашней страницы. Он создает сеанс в DB и отправляет 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())
2 ответов
Я не могу воспроизвести это поведение в фиктивном проекте, что заставляет меня полагать, что у вас есть некоторая конфигурация, влияющая на вещи, которые не показаны здесь. Ясно, что если какая-либо аутентификация вызывается, сеанс будет создан в соответствии с вашей политикой аутентификации. Статические активы (по умолчанию) регистрируются с помощью NO_PERMISSION_REQUIRED
Что означает, что они не будут вызывать ни один из API аутентификации в Pyramid (и я проверил, что это так).
запросы на статические активы do вызовите весь конвейер запросов, то есть, если у вас есть код в каких-либо подписчиках или корневой фабрике, которая вызывает has_permission
или другие API безопасности, или коснитесь сеанса непосредственно сами, тогда это объяснило бы поведение, которое вы видите, так как ваши сеансы связаны с вашей аутентификацией.
вот фиктивный проект для воспроизведения проблемы:
настройте среду virtualenv и установите в ней пирамиду.
установить стартовый проект:
pcreate -s starter IssueApp
удалите все ненужные файлы, чтобы у вас было это простое дерево:
дерево
.
├── CHANGES.txt
├── development.ini
├── issueapp
│ ├── __init__.py
│ └── static
│ └── pyramid.png
├── README.txt
└── setup.py
обратите внимание, что мы будем писать все приложение в __init__.py
file -- так что все остальное удаленный.
теперь установите проекта: (env) $ python setup.py develop
это установит ваш проект в виртуальную среду.
на :
[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
на :
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
который показывает, что код auth получение статических запросов.
теперь, когда я установил уровень журнала в 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 консультируется для статических активов, но не для обычных запросов. (Если, конечно, нет разрешения, которое в моей вид не).