Пирамида: сеансы и статические активы

Позвольте мне объяснить проблему:

Я обслуживаю свои статические активы через Pyramid:

config.add_static_view(name='static', path='/var/www/static') 

И он отлично работает.

Теперь у меня есть настраиваемая фабрика сеансов, которая создает сеансы в базе данных. Он проверяет, содержит ли браузер сеансовый файл cookie. Если это так, он находит сеанс из БД. Если это не так, то в БД создается новый сеанс, и куки-файл возвращается в браузер.

Все идет нормально.

Теперь, внутри моего home_view (который генерирует мою домашнюю страницу), я никак не обращаюсь к переменной запроса:

 @view_config(route_name='home', renderer="package:templates/home.mak") def home_view(request): return {} 

Из-за этого происходит то, что пользователь посещает домашнюю страницу, сеанс НЕ создается на сервере. Я думаю, это потому, что Pyramid создает сеансы лениво – только при доступе к request.session . Следовательно, заголовки ответов для запроса домашней страницы НЕ содержат заголовка Set-Cookie для сеансов.

Теперь в моем шаблоне mako для домашней страницы я создаю статические URL-адреса для файлов JavaScript и CSS …

 <link rel="stylesheet" href="${request.static_url(...)}" <script src="${request.static_url(...)}"></script> 

Теперь, поскольку я обслуживаю статические активы от Pyramid, все запросы на эти активы проходят через весь механизм Pyramid.

Итак, что происходит, когда мой браузер отправляет запросы на получение статических активов, Pyramid каким-то образом создает сеанс. То есть, Pyramid создает сеанс в базе данных и отправляет cookie сессии обратно, когда браузер отправляет запросы на статические активы. Это проблема №1.

Браузер посылает все запросы на статические активы параллельно . Я использую последние версии Firefox и Chrome. Поскольку HTTP-запрос для фактического HTML-документа не возвращал заголовки Set-Cookie , запросы на статические активы НЕ имеют заголовков файлов cookie. Это означает, что Pyramid не видит cookie сеанса для любого из запросов и создает новый сеанс в базе данных ДЛЯ КАЖДОГО ЗАПРОСОВ, ЧТО ЭТО ПОЛУЧАЕТ ДЛЯ СТАТИЧЕСКОЙ АКТИВЫ.

Если вы набираете 7 статических активов на моей домашней странице и создаете 7 записей сеанса. Это связано с тем, что все эти запросы идут параллельно с сервером, и ни один из них не имеет cookie сеанса, поэтому Pyramid создает сеанс для каждого.

Эта проблема не возникает, если я намеренно обращаюсь к сеансу как часть запроса домашней страницы. Он создает сеанс в БД и отправляет 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 Solutions collect form web for “Пирамида: сеансы и статические активы”

Я не могу воспроизвести это поведение в фиктивном проекте, который заставляет меня поверить, что у вас есть какая-то конфигурация, влияющая на вещи, которые здесь не показаны. Очевидно, что при вызове любой аутентификации будет создан сеанс в соответствии с политикой проверки подлинности. Статические активы (по умолчанию) зарегистрированы NO_PERMISSION_REQUIRED что означает, что они не будут вызывать ни один из API аутентификации в Pyramid (и я проверял, что это так).

Запросы на статические активы вызывают весь конвейер запросов, что означает, что если у вас есть какой-либо код у любых подписчиков или корневой фабрики, которые вызывают has_permission или другие API-интерфейсы безопасности, или напрямую касаются сеанса, то это объясняет поведение, которое вы потому что ваши сеансы связаны с вашей аутентификацией.

Вот фиктивный проект, чтобы воспроизвести проблему:

  1. настроить виртуальную среду и установить в ней Pyramid.

  2. Установите стартовый проект: pcreate -s starter IssueApp

  3. Удалите все ненужные файлы, чтобы у вас было это простое дерево:

дерево

 . ├── 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 

Что показывает, что код auth получает вызов для статических запросов.

Теперь, когда я устанавливаю уровень журнала в DEBUG , это вывод консоли при доступе к странице:

 pserve development.ini --reload
 Запуск подпроцесса с файловым монитором
 Запуск сервера в PID 2339.
 обслуживание по адресу http://0.0.0.0:7777
 2013-03-27 03: 40: 55,539 DEBUG [issueapp] [Dummy-2] маршрут соответствует 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 url http://192.168.56.102:7777/ (просмотреть имя «против контекста»): разрешено (разрешение не зарегистрировано)
 2013-03-27 03: 40: 55,685 DEBUG [issueapp] [Dummy-3] маршрут соответствует 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',)}, предикаты: ''
 [auth]: http://192.168.56.102:7777/static/pyramid.png
 2013-03-27 03: 40: 55,687 DEBUG [issueapp] [Dummy-3] debug_authorization url http://192.168.56.102:7777/static/pyramid.png (просмотреть имя «против контекста»): разрешение ACLDenied '__no_permission_required__ '' ACE '' в ACL [('Allow', 'system.Authenticated', 'edit'), ('Deny', 'system.Authenticated', 'login'), ('Allow', 'system.Everyone' , 'login')] в контексте для участников ['system.Everyone', 'system.Authenticated', 'user']


Обратите внимание, что сообщение [auth]: ... печатается ТОЛЬКО ОДИН – для запроса статического актива и НЕ для запроса домашней страницы. Это странно, потому что это означает, что с помощью политики авторизации для статических активов, но не для обычных запросов. (Если, конечно, не имеется разрешения, которое, на мой взгляд, нет).

  • Отключение кэширования в колбе
  • где находится виджет кнопки в pyglet?
  • Динамически добавлять правила URL в приложение Flask
  • бегущий нос - с охватом, чтобы получить все файлы пакета, но не другие зависимости и библиотеки
  • Вырыв из вложенных циклов
  • Ошибка отправки формы, Flask
  • Возврат и выход в ту же функцию
  • Проектирование фильтра и извлечение частоты в Python
  • Есть ли эквивалент python Ruby's 'rvm'?
  • Как украсить генератор в python
  • Использование метода populate_obj () WTForms с фреймворком micro framework
  • Python - лучший язык программирования в мире.