[Zope3-checkins]
SVN: Zope3/branches/stub-session/src/zope/app/session/
Recover from screwed svn commit wierdness
Stuart Bishop
zen at shangri-la.dropbear.id.au
Mon Jul 5 18:10:57 EDT 2004
Log message for revision 26103:
Recover from screwed svn commit wierdness
-=-
Deleted: Zope3/branches/stub-session/src/zope/app/session/README.txt
===================================================================
--- Zope3/branches/stub-session/src/zope/app/session/README.txt 2004-07-05 21:58:09 UTC (rev 26102)
+++ Zope3/branches/stub-session/src/zope/app/session/README.txt 2004-07-05 22:10:57 UTC (rev 26103)
@@ -1,148 +0,0 @@
-Sessions
-========
-
-Sessions provide a way to temporarily associate information with a
-client without requiring the authentication of a principal. We
-associate an identifier with a particular client. Whenever we get a
-request from that client, we compute the identifier and use the
-identifier to look up associated information, which is stored on the
-server.
-
-A major disadvantage of sessions is that they require management of
-information on the server. This can have major implications for
-scalability. It is possible for a framework to make use of session
-data very easy for the developer. This is great if scalability is not
-an issue, otherwise, it is a booby trap.
-
-Design Issues
--------------
-
-Sessions introduce a number of issues to be considered:
-
-- Clients have to be identified. A number of approaches are possible,
- including:
-
- o Using HTTP cookies. The application assigns a client identifier,
- which is stored in a cookie. This technique is the most
- straightforward, but can be defeated if the client does not
- support HTTP cookies (usually because the feature has been
- disabled).
-
- o Using URLs. The application assigns a client identifier, which is
- stored in the URL. This makes URLs a bit uglier and requires some
- care. If people copy URLs and send them to others, then you could
- end up with multiple clients with the same session
- identifier. There are a number of ways to reduce the risk of
- accidental reuse of session identifiers:
-
- - Embed the client IP address in the identifier
-
- - Expire the identifier
-
- o Use hidden form variables. This complicates applications. It
- requires all requests to be POST requests and requires the
- maintenance of the hidden variables.
-
- o Use the client IP address
-
- This doesn't work very well, because an IP address may be shared by
- many clients.
-
-- Data storage
-
- Data can be simply stored in the object database. This provides lots
- of flexibility. You can store pretty much anything you want as long
- as it is persistent. You get the full benefit of the object database,
- such as transactions, transparency, clustering, and so on. Using
- the object database is especially useful when:
-
- - Writes are infrequent
-
- - Data are complex
-
- If writes are frequent, then the object database introduces
- scalability problems. Really, any transactional database is likely
- to introduce problems with frequent writes. If you are tempted to
- update session data on every request, think very hard about it. You
- are creating a scalability problem.
-
- If you know that scalability is not (and never will be) an issue,
- you can just use the object database.
-
- If you have client data that needs to be updated often (as in every
- request), consider storing the data on the client. (Like all data
- received from a client, it may be tainted and, in most instances,
- should not be trusted. Sensitive information that the user should
- not see should likewise not be stored on the client, unless
- encrypted with a key the client has no access to.) If you can't
- store it on the client, then consider some other storage mechanism,
- like a fast database, possibly without transaction support.
-
- You may be tempted to store session data in memory for speed. This
- doesn't turn out to work very well. If you need scalability, then
- you need to be able to use an application-server cluster and storage
- of session data in memory defeats that. You can use
- "server-affinity" to assure that requests from a client always go
- back to the same server, but not all load balancers support server
- affinity, and, for those that do, enabling server affinity tends to
- defeat load balancing.
-
-- Session expiration
-
- You may wish to ensure that sessions terminate after some period of
- time. This may be for security reasons, or to avoid accidental
- sharing of a session among multiple clients. The policy might be
- expressed in terms of total session time, or maximum inactive time,
- or some combination.
-
- There are a number of ways to approach this. You can expire client
- ids. You can expire session data.
-
-- Data expiration
-
- Because HTTP is a stateless protocol, you can't tell whether a user
- is thinking about a task or has simply stopped working on it. Some
- means is needed to free server session storage that is no-longer needed.
-
- The simplest strategy is to never remove data. This strategy has
- some obvious disadvantages. Other strategies can be viewed as
- optimizations of the basic strategy. It is important to realize that
- a data expiration strategy can be informed by, but need not be
- constrained by a session-expiration strategy.
-
-Application programming interface
----------------------------------
-
-Application code will merely adapt request objects to a session data
-interface. Initially, we will define the session data interface
-`IPersistentSessionData'. `IPersistentSessionData` provides a mapping
-interface. Keys in the mapping are application identifiers. Values are
-persistent mapping objects.
-
-Application code that wants to get object session data for a request
-adapts the request to `IPersistentSessionData`::
-
- data = IPersistentSessionData(request)[appkey]
-
-where `appkey` is a dotted name that identifies the application, such
-as ``zope.app.actionplan``. Given the session data, the application
-can then store data in it::
-
- data['original'] = original_actions
- data['new'] = new_actions
-
-From ZPT, you can access session data using adapter syntax::
-
- request*PersistentSession
-
-So, for example, to access the `old` key for the session data for the
-sample key above:
-
- request*PersistentSession/zope.app.actionplan/old
-
-In this example, the data for an aplication key was a mapping object.
-The semantics of a session data for a particular application key are
-determined by the session data type interface.
-`IPersistentSessionData` defines the application data to be a mapping
-object by default. Other data interfaces could specify different
-bahavior.
Copied: Zope3/branches/stub-session/src/zope/app/session/api.txt (from rev 26102, Zope3/branches/stub-session/src/zope/app/session/session.stx)
Copied: Zope3/branches/stub-session/src/zope/app/session/design.txt (from rev 26102, Zope3/branches/stub-session/src/zope/app/session/README.txt)
Deleted: Zope3/branches/stub-session/src/zope/app/session/persist.py
===================================================================
--- Zope3/branches/stub-session/src/zope/app/session/persist.py 2004-07-05 21:58:09 UTC (rev 26102)
+++ Zope3/branches/stub-session/src/zope/app/session/persist.py 2004-07-05 22:10:57 UTC (rev 26103)
@@ -1,259 +0,0 @@
-##############################################################################
-#
-# Copyright (c) 2004 Zope Corporation and Contributors.
-# All Rights Reserved.
-#
-# This software is subject to the provisions of the Zope Public License,
-# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution.
-# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
-# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
-# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
-# FOR A PARTICULAR PURPOSE.
-#
-##############################################################################
-"""
-Session implementation using cookies
-
-$Id$
-"""
-import sha, time, string, random, hmac, warnings, thread
-from UserDict import IterableUserDict
-from heapq import heapify, heappop
-
-from persistent import Persistent
-from zope.server.http.http_date import build_http_date
-from zope.interface import implements
-from zope.component import ComponentLookupError
-from zope.app.zapi import getUtility
-from BTrees.OOBTree import OOBTree
-from zope.app.utility.interfaces import ILocalUtility
-from zope.app.annotation.interfaces import IAttributeAnnotatable
-
-from interfaces import \
- IBrowserIdManager, IBrowserId, ICookieBrowserIdManager, \
- ISessionDataContainer, ISession, ISessionProductData, ISessionData
-
-import ZODB
-import ZODB.MappingStorage
-
-cookieSafeTrans = string.maketrans("+/", "-.")
-
-def digestEncode(s):
- """Encode SHA digest for cookie."""
- return s.encode("base64")[:-2].translate(cookieSafeTrans)
-
-
-class BrowserId(str):
- """See zope.app.interfaces.utilities.session.IBrowserId"""
- implements(IBrowserId)
-
- def __new__(cls, request):
- return str.__new__(
- cls, getUtility(IBrowserIdManager).getBrowserId(request)
- )
-
-
-class CookieBrowserIdManager(Persistent):
- """Session service implemented using cookies."""
-
- implements(IBrowserIdManager, ICookieBrowserIdManager,
- ILocalUtility, IAttributeAnnotatable,
- )
-
- __parent__ = __name__ = None
-
- def __init__(self):
- self.namespace = "zope3_cs_%x" % (int(time.time()) - 1000000000)
- self.secret = "%.20f" % random.random()
- self.cookieLifetime = None
-
- def generateUniqueId(self):
- """Generate a new, random, unique id."""
- data = "%.20f%.20f%.20f" % (random.random(), time.time(), time.clock())
- digest = sha.sha(data).digest()
- s = digestEncode(digest)
- # we store a HMAC of the random value together with it, which makes
- # our session ids unforgeable.
- mac = hmac.new(s, self.secret, digestmod=sha).digest()
- return s + digestEncode(mac)
-
- def getRequestId(self, request):
- """Return the browser id encoded in request as a string,
- or None if it's non-existent."""
- # If there is an id set on the response, use that but don't trust it.
- # We need to check the response in case there has already been a new
- # session created during the course of this request.
- response_cookie = request.response.getCookie(self.namespace)
- if response_cookie:
- sid = response_cookie['value']
- else:
- sid = request.cookies.get(self.namespace)
- if sid is None or len(sid) != 54:
- return None
- s, mac = sid[:27], sid[27:]
- if (digestEncode(hmac.new(s, self.secret, digestmod=sha).digest())
- != mac):
- return None
- else:
- return sid
-
- def setRequestId(self, request, id):
- """Set cookie with id on request."""
- # XXX Currently, the path is the ApplicationURL. This is reasonable,
- # and will be adequate for most purposes.
- # A better path to use would be that of the folder that contains
- # the service-manager this service is registered within. However,
- # that would be expensive to look up on each request, and would
- # have to be altered to take virtual hosting into account.
- # Seeing as this service instance has a unique namespace for its
- # cookie, using ApplicationURL shouldn't be a problem.
-
- if self.cookieLifetime is not None:
- if self.cookieLifetime:
- expires = build_http_date(time.time() + self.cookieLifetime)
- else:
- expires = 'Tue, 19 Jan 2038 00:00:00 GMT'
- request.response.setCookie(
- self.namespace, id, expires=expires,
- path=request.getApplicationURL(path_only=True)
- )
- else:
- request.response.setCookie(
- self.namespace, id,
- path=request.getApplicationURL(path_only=True)
- )
-
- def getBrowserId(self, request):
- """See zope.app.session.interfaces.IBrowserIdManager"""
- sid = self.getRequestId(request)
- if sid is None:
- sid = self.generateUniqueId()
- self.setRequestId(request, sid)
- return sid
-
-
-class PersistentSessionDataContainer(Persistent, IterableUserDict):
- ''' A SessionDataContainer that stores data in the ZODB '''
- __parent__ = __name__ = None
-
- implements(ISessionDataContainer, ILocalUtility, IAttributeAnnotatable)
-
- def __init__(self):
- self.data = OOBTree()
- self.sweepInterval = 5*60
-
- def __getitem__(self, product_id):
- rv = IterableUserDict.__getitem__(self, product_id)
- now = time.time()
- # Only update lastAccessTime once every few minutes, rather than
- # every hit, to avoid ZODB bloat and conflicts
- if rv.lastAccessTime + self.sweepInterval < now:
- rv.lastAccessTime = int(now)
- # XXX: When scheduler exists, this method should just schedule
- # a sweep later since we are currently busy handling a request
- # and may end up doing simultaneous sweeps
- self.sweep()
- return rv
-
- def __setitem__(self, product_id, session_data):
- session_data.lastAccessTime = int(time.time())
- return IterableUserDict.__setitem__(self, product_id, session_data)
-
- def sweep(self):
- ''' Clean out stale data '''
- expire_time = time.time() - self.sweepInterval
- heap = [(v.lastAccessTime, k) for k,v in self.data.items()]
- heapify(heap)
- while heap:
- lastAccessTime, key = heappop(heap)
- if lastAccessTime < expire_time:
- del self.data[key]
- else:
- return
-
-
-class RAMSessionDataContainer(PersistentSessionDataContainer):
- ''' A SessionDataContainer that stores data in RAM. Currently session
- data is not shared between Zope clients, so server affinity will
- need to be maintained to use this in a ZEO cluster.
- '''
- def __init__(self):
- self.sweepInterval = 5*60
- self.key = sha.new(str(time.time() + random.random())).hexdigest()
-
- _ram_storage = ZODB.MappingStorage.MappingStorage()
- _ram_db = ZODB.DB(_ram_storage)
- _conns = {}
-
- def _getData(self):
-
- # Open a connection to _ram_storage per thread
- tid = thread.get_ident()
- if not self._conns.has_key(tid):
- self._conns[tid] = self._ram_db.open()
-
- root = self._conns[tid].root()
- if not root.has_key(self.key):
- root[self.key] = OOBTree()
- return root[self.key]
-
- data = property(_getData, None)
-
- def sweep(self):
- super(RAMSessionDataContainer, self).sweep()
- self._ram_db.pack(time.time())
-
-
-class Session:
- """See zope.app.session.interfaces.ISession"""
- implements(ISession)
- __slots__ = ('browser_id',)
- def __init__(self, request):
- self.browser_id = str(IBrowserId(request))
-
- def __getitem__(self, product_id):
- """See zope.app.session.interfaces.ISession"""
-
- # First locate the ISessionDataContainer by looking up
- # the named Utility, and falling back to the unnamed one.
- try:
- sdc = getUtility(ISessionDataContainer, product_id)
- except ComponentLookupError:
- # XXX: Do we want this?
- warnings.warn(
- 'Unable to find ISessionDataContainer named %s. '
- 'Using default' % repr(product_id),
- RuntimeWarning
- )
- sdc = getUtility(ISessionDataContainer)
-
- # The ISessionDataContainer contains two levels:
- # ISessionDataContainer[product_id] == ISessionProductData
- # ISessionDataContainer[product_id][browser_id] == ISessionData
- try:
- spd = sdc[product_id]
- except KeyError:
- sdc[product_id] = SessionProductData()
- spd = sdc[product_id]
-
- try:
- return spd[self.browser_id]
- except KeyError:
- spd[self.browser_id] = SessionData()
- return spd[self.browser_id]
-
-
-class SessionProductData(Persistent, IterableUserDict):
- """See zope.app.session.interfaces.ISessionProductData"""
- implements(ISessionProductData)
- lastAccessTime = 0
- def __init__(self):
- self.data = OOBTree()
-
-
-class SessionData(Persistent, IterableUserDict):
- """See zope.app.session.interfaces.ISessionData"""
- implements(ISessionData)
- def __init__(self):
- self.data = OOBTree()
-
Deleted: Zope3/branches/stub-session/src/zope/app/session/session.stx
===================================================================
--- Zope3/branches/stub-session/src/zope/app/session/session.stx 2004-07-05 21:58:09 UTC (rev 26102)
+++ Zope3/branches/stub-session/src/zope/app/session/session.stx 2004-07-05 22:10:57 UTC (rev 26103)
@@ -1,108 +0,0 @@
-Zope3 Session Implementation
-============================
-
-Overview
---------
-
-Sessions allow us to fake state over a stateless protocol - HTTP. We do this
-by having a unique identifier stored across multiple HTTP requests, be it
-a cookie or some id mangled into the URL.
-
-The IClientIdManager Utility provides this unique id. It is responsible
-for propagating this id so that future requests from the client get
-the same id (eg. by setting an HTTP cookie). This utility is used
-when we adapt the request to the unique client id:
-
- >>> client_id = IClientId(request)
-
-The ISession adapter gives us a mapping that can be used to store
-and retrieve session data. A unique key (the package id) is used
-to avoid namespace clashes:
-
- >>> pkg_id = 'products.foo'
- >>> session = ISession(request)[pkg_id]
- >>> session['color'] = 'red'
-
- >>> session2 = ISession(request)['products.bar']
- >>> session2['color'] = 'blue'
-
- >>> session['color']
- 'red'
- >>> session2['color']
- 'blue'
-
-
-Data Storage
-------------
-
-The actual data is stored in an ISessionDataContainer utility.
-ISession chooses which ISessionDataContainer should be used by
-looking up as a named utility using the package id. This allows
-the site administrator to configure where the session data is actually
-stored by adding a registration for desired ISessionDataContainer
-with the correct name.
-
- >>> sdc = zapi.getUtility(ISessionDataContainer, pkg_id)
- >>> sdc[client_id][pkg_id] is session
- True
- >>> sdc[client_id][pkg_id]['color']
- 'red'
-
-If no ISessionDataContainer utility can be located by name using the
-package id, then the unnamed ISessionDataContainer utility is used as
-a fallback. An unnamed ISessionDataContainer is automatically created
-for you, which may replaced with a different implementation if desired.
-
- >>> ISession(request)['unknown'] \
- ... is zapi.getUtility(ISessionDataContainer)[client_id]['unknown']
- True
-
-The ISessionDataContainer contains ISessionData objects, and ISessionData
-objects in turn contain ISessionPkgData objects. You should never need
-to know this unless you are writing administrative views for the session
-machinery.
-
- >>> ISessionData.providedBy(sdc[client_id])
- True
- >>> ISessionPkgData.providedBy(sdc[client_id][pkg_id])
- True
-
-The ISessionDataContainer is responsible for expiring session data.
-The expiry time can be configured by settings its 'timeout' attribute.
-
- >>> sdc.timeout = 1200 # 1200 seconds or 20 minutes
-
-
-Restrictions
-------------
-
-Data stored in the session must be persistent or picklable.
-
- >>> session['oops'] = open(__file__)
- >>> import transaction
- >>> transaction.commit()
- Traceback (most recent call last):
- [...]
- TypeError: can't pickle file objects
-
-
-Page Templates
---------------
-
- Session data may be accessed in page template documents using the
- TALES adaptor syntax::
-
- <span tal:content="request*Session/products.foo/color | default">
- green
- </span>
-
- <div tal:define="session request*Session/products.foo">
- <tal:x condition="not:exists:session/count">
- <tal:x condition="python: session['count'] = 1" />
- </tal:x>
- <tal:x condition="exists:session/count">
- <tal:x condition="python: session['count'] += 1" />
- </tal:x>
- <span content="session/count">6</span>
- </div>
-
Modified: Zope3/branches/stub-session/src/zope/app/session/tests.py
===================================================================
--- Zope3/branches/stub-session/src/zope/app/session/tests.py 2004-07-05 21:58:09 UTC (rev 26102)
+++ Zope3/branches/stub-session/src/zope/app/session/tests.py 2004-07-05 22:10:57 UTC (rev 26103)
@@ -82,9 +82,7 @@
>>> tearDown()
- ''' % (open(
- os.path.join(os.path.dirname(__file__), 'session.stx')
- ).read(),)
+ ''' % (open(os.path.join(os.path.dirname(__file__), 'api.txt')).read(),)
def test_suite():
return unittest.TestSuite((
More information about the Zope3-Checkins
mailing list