金字塔:会话和静态资产

tre*_*der 8 python pyramid

让我解释一下这个问题:

我通过Pyramid服务我的静态资产:

config.add_static_view(name='static', path='/var/www/static')
Run Code Online (Sandbox Code Playgroud)

它工作正常.

现在,我有一个自定义会话工厂,可以在数据库中创建会话.它检查浏览器是否提供会话cookie.如果是,它会从数据库中找到一个会话.如果没有,则在DB中创建新会话,并将cookie返回给浏览器.

到现在为止还挺好.

现在,在我的home_view(生成我的主页)内部,我不以任何方式访问请求变量:

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

因此,发生的情况是当用户访问主页时,会话不会在服务器上创建.我认为这是因为金字塔懒洋洋地创建会话- 只有当你访问时request.session.因此,主页请求的响应头不包含任何Set-Cookie会话头.

现在在主页的mako模板中,我正在为JavaScript和CSS文件生成静态URL ...

<link rel="stylesheet" href="${request.static_url(...)}"

<script src="${request.static_url(...)}"></script>
Run Code Online (Sandbox Code Playgroud)

现在,由于我正在为金字塔提供静态资产,所有对这些资产的请求都要通过整个金字塔机制.

所以,当我的浏览器发送获取静态资产的请求时,会发生什么,Pyramid会创建会话.也就是说,Pyramid在数据库中创建会话,并在浏览器发送静态资产请求时发回会话cookie.这是问题#1.

浏览器并行发送静态资产的所有请求.我使用的是最新版本的Firefox和Chrome.由于对实际HTML文档的HTTP请求未返回任何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 {}
Run Code Online (Sandbox Code Playgroud)

我应该如何阻止在静态资产请求上创建会话.更好的是,我希望Pyramid在收到静态资产请求时甚至不会触及会话工厂 - 这可能吗?

其次,我不明白为什么Pyramid正在创建静态请求的新会话?

UPDATE

这是会议工厂.

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
Run Code Online (Sandbox Code Playgroud)

然后,

factory = DBSessionFactory('secret')
config.set_session_factory(factory)
Run Code Online (Sandbox Code Playgroud)

UPDATE

我的自定义验证:

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
Run Code Online (Sandbox Code Playgroud)

然后,

config.set_root_factory(RootFactory)

config.set_authentication_policy(SessionAuthenticationPolicy())
config.set_authorization_policy(ACLAuthorizationPolicy())
Run Code Online (Sandbox Code Playgroud)

tre*_*der 2

这是一个重现该问题的虚拟项目:

\n\n
    \n
  1. 设置 virtualenv 环境并在其中安装 Pyramid。

  2. \n
  3. 安装启动项目:pcreate -s starter IssueApp

  4. \n
  5. 删除所有不必要的文件,这样你就有了这个简单的树:

  6. \n
\n\n

\n\n
.\n\xe2\x94\x9c\xe2\x94\x80\xe2\x94\x80 CHANGES.txt\n\xe2\x94\x9c\xe2\x94\x80\xe2\x94\x80 development.ini\n\xe2\x94\x9c\xe2\x94\x80\xe2\x94\x80 issueapp\n\xe2\x94\x82\xc2\xa0\xc2\xa0 \xe2\x94\x9c\xe2\x94\x80\xe2\x94\x80 __init__.py\n\xe2\x94\x82\xc2\xa0\xc2\xa0 \xe2\x94\x94\xe2\x94\x80\xe2\x94\x80 static\n\xe2\x94\x82\xc2\xa0\xc2\xa0     \xe2\x94\x94\xe2\x94\x80\xe2\x94\x80 pyramid.png\n\xe2\x94\x9c\xe2\x94\x80\xe2\x94\x80 README.txt\n\xe2\x94\x94\xe2\x94\x80\xe2\x94\x80 setup.py\n
Run Code Online (Sandbox Code Playgroud)\n\n

请注意,我们将在文件中写入整个应用程序__init__.py- 因此其他所有内容都将被删除。

\n\n

现在安装项目:(env) $ python setup.py develop这会将您的项目安装到虚拟环境中。

\n\n

文件development.ini

\n\n
[app:main]\nuse = egg:IssueApp#main\n\npyramid.reload_all = true\npyramid.reload_templates = true\npyramid.debug_all = true\npyramid.debug_notfound = true\npyramid.debug_routematch = true\npyramid.prevent_http_cache = true\npyramid.default_locale_name = en\n\n[server:main]\nuse = egg:waitress#main\nhost = 0.0.0.0\nport = 7777\n\n[loggers]\nkeys = root, issueapp\n\n[handlers]\nkeys = console\n\n[formatters]\nkeys = generic\n\n[logger_root]\nlevel = INFO\nhandlers = console\n\n[logger_issueapp]\nlevel = INFO\nhandlers =\nqualname = issueapp\n\n[handler_console]\nclass = StreamHandler\nargs = (sys.stderr,)\nlevel = NOTSET\nformatter = generic\n\n[formatter_generic]\nformat = %(asctime)s %(levelname)-5.5s [%(name)s][%(threadName)s] %(message)s\n
Run Code Online (Sandbox Code Playgroud)\n\n

文件__init__.py

\n\n
from pyramid.config import Configurator\n\nfrom pyramid.view import view_config\nfrom pyramid.response import Response\n\nfrom pyramid.authentication import CallbackAuthenticationPolicy\nfrom pyramid.authorization import ACLAuthorizationPolicy\n\nfrom pyramid.security import (\n    Allow, Deny,\n    Everyone, Authenticated,\n)\n\n\ndef main(global_config, **settings):\n    """ This function returns a Pyramid WSGI application.\n    """\n    config = Configurator(settings=settings)\n\n    #config.add_static_view(\'static\', \'static\', cache_max_age=3600)\n    config.add_static_view(name=\'static\', path=\'issueapp:static\')\n    config.add_route(\'home\', \'/\')\n\n    config.set_root_factory(RootFactory)\n    config.set_authentication_policy(DummyAuthPolicy())\n    config.set_authorization_policy(ACLAuthorizationPolicy())\n\n    config.scan()\n    return config.make_wsgi_app()\n\n\n@view_config(route_name=\'home\')\ndef home_view(request):\n    src = request.static_url(\'issueapp:static/pyramid.png\')\n    return Response(\'<img src=\'+ src + \'>\')\n\n\nclass RootFactory:\n    __acl__ = [\n        (Allow, Authenticated, \'edit\'),\n        (Deny, Authenticated, \'login\'),\n        (Allow, Everyone, \'login\'),\n    ]\n\n    def __init__(self, request):\n        self.request = request\n\n\nclass DummyAuthPolicy(CallbackAuthenticationPolicy):\n    def __init__(self, callback=None, debug=False):\n        self.callback = callback\n        self.debug = debug\n\n    def remember(self, request, principal, **kw):\n        return []\n\n    def forget(self, request):\n        return []\n\n    def unauthenticated_userid(self, request):\n        # this will print the request url\n        # so we can know which request is causing auth code to be called            \n        print(\'[auth]: \' + request.url)\n\n        # this means the user is authenticated\n        return "user"\n
Run Code Online (Sandbox Code Playgroud)\n\n

现在运行应用程序

\n\n
pserve  development.ini  --reload\nStarting subprocess with file monitor\nStarting server in PID 2303.\nserving on http://0.0.0.0:7777\n
Run Code Online (Sandbox Code Playgroud)\n\n

最后,清除浏览器中的所有历史记录(这很重要,否则问题可能不会自行暴露)并访问该页面。这会打印在控制台上:

\n\n
[auth]: http://192.168.56.102:7777/static/pyramid.png   \n
Run Code Online (Sandbox Code Playgroud)\n\n

这表明正在为静态请求调用身份验证代码。

\n\n

现在,当我将日志级别设置为 时DEBUG,这是访问页面时控制台的输出:

\n\n
\npservedevelopment.ini --reload\n使用文件监视器启动子进程\n以 PID 2339 启动服务器。\n在 http://0.0.0.0:7777\n2013-03-27 03:40:55,539 DEBUG [issueapp][Dummy -2] 与 url http://192.168.56.102:7777/ 匹配的路由;路由名称:\'home\',路径信息:\'/\',模式:\'/\',matchdict:{},谓词:\'\'\n2013-03-27 03:40:55,540 DEBUG [issueapp] [Dummy-2] url http://192.168.56.102:7777/ 的 debug_authorization(根据上下文查看名称 \'\'):允许(未注册权限)\n2013-03-27 03:40:55,685 DEBUG [issueapp] [Dummy-3] 与 url http://192.168.56.102:7777/static/pyramid.png 匹配的路由;路由名称:\'__static/\',路径信息:\'/static/pyramid.png\',模式:\'static/*subpath\',matchdict:{\'subpath\':(\'pyramid.png\' ,)},谓词:\'\'\n[auth]:http://192.168.56.102:7777/static/pyramid.png\n2013-03-27 03:40:55,687 DEBUG [issueapp][Dummy-3 ] url http://192.168.56.102:7777/static/pyramid.png 的 debug_authorization(根据上下文查看名称 \'\' ):ACLDenied 权限 \'__no_permission_required__\' via ACE \'\' in ACL [(\'Allow \', \'system.Authenticated\', \'编辑\'), (\'拒绝\', \'system.Authenticated\', \'登录\'), (\'允许\', \'系统.Everyone\', \'login\')] 主体上下文 [\'system.Everyone\', \'system.Authenticated\', \'user\']\n\n
\n\n
\n\n

请注意,[auth]: ...消息仅打印一次 - 对于静态资产请求,而不是主页请求。这很奇怪,因为这意味着针对静态资产而不是针对正常请求咨询身份验证策略。(当然,除非涉及许可,但在我看来不是)。

\n