diff --git a/docs/projects/web/howto/using-twistedweb.rst b/docs/projects/web/howto/using-twistedweb.rst index 0be5c2f..70c491e 100644 --- a/docs/projects/web/howto/using-twistedweb.rst +++ b/docs/projects/web/howto/using-twistedweb.rst @@ -438,6 +438,13 @@ Twisted Web provides an abstraction of this browser-tracking behavior called the +The default session cookie name is ``TWISTED_SESSION``. It can be change by +overwriting the default implementation of +:api:`twisted.web.server.Request#getSessionCookieName +`. + + + .. image:: ../img/web-session.png diff --git a/twisted/web/server.py b/twisted/web/server.py index a48f2ea..684f072 100644 --- a/twisted/web/server.py +++ b/twisted/web/server.py @@ -101,9 +101,12 @@ class Request(Copyable, http.Request, components.Componentized): @ivar defaultContentType: A C{bytes} giving the default I{Content-Type} value to send in responses if no other value is set. C{None} disables the default. + @ivar sessionCookieBaseName: A C{bytes} giving the base name when creating + new session cookies. """ defaultContentType = b"text/html" + sessionCookieBaseName = b'TWISTED_SESSION' site = None appRootURL = None @@ -384,7 +387,7 @@ class Request(Copyable, http.Request, components.Componentized): def getSession(self, sessionInterface = None): # Session management if not self.session: - cookiename = b"_".join([b'TWISTED_SESSION'] + self.sitepath) + cookiename = self.getSessionCookieName() sessionCookie = self.getCookie(cookiename) if sessionCookie: try: @@ -400,6 +403,16 @@ class Request(Copyable, http.Request, components.Componentized): return self.session.getComponent(sessionInterface) return self.session + + def getSessionCookieName(self): + """ + Return the name of the cookie used for storing the session id. + + @return: The C{str} name of the session cookie. + """ + return b"_".join([self.sessionCookieBaseName] + self.sitepath) + + def _prePathURL(self, prepath): port = self.getHost().port if self.isSecure(): diff --git a/twisted/web/test/test_web.py b/twisted/web/test/test_web.py index 46b66f2..a42a398 100644 --- a/twisted/web/test/test_web.py +++ b/twisted/web/test/test_web.py @@ -7,7 +7,7 @@ Tests for various parts of L{twisted.web}. import zlib -from zope.interface import implementer +from zope.interface import Attribute, implementer, Interface from zope.interface.verify import verifyObject from twisted.python.compat import (_PY3, networkString, @@ -18,7 +18,7 @@ from twisted.internet.address import IPv4Address from twisted.web import server, resource from twisted.internet import task from twisted.web import iweb, http, error -from twisted.python import log +from twisted.python import components, log from twisted.web.test.requesthelper import DummyChannel, DummyRequest @@ -467,6 +467,116 @@ class RequestTests(unittest.TestCase): self.assertEqual(request.prePathURL(), b'http://example.com/foo%2Fbar') + class DummySession(server.Session): + """ + A session to help with testing. + """ + def __init__(self, site=None, uid=0): + server.Session.__init__( + self, site=site, uid=uid, reactor=task.Clock()) + + def touch(self): + self._reactor.advance(1) + return server.Session.touch(self) + + + def test_getSession(self): + """ + When a session already exists, it will return the same session and + update its modification. + """ + channel = DummyChannel() + request = server.Request(channel, 1) + session = self.DummySession() + initial_time = session.lastModified + request.session = session + + result = request.getSession() + + self.assertIs(session, result) + self.assertGreater(result.lastModified, initial_time) + + + class ISessionObject(Interface): + """ + A simple interface for testing session components. + """ + value = Attribute("A marker value for this component.") + + + @implementer(ISessionObject) + class SessionObject(object): + """ + A simple component. + """ + def __init__(self, session): + self.value = 42 + + + def test_getSessionComponent(self): + """ + When sessionInterface is provided it will return the + C{sessionInterface} component associated with this session. + """ + # Register adapter for this test and remove it once test is done. + components.registerAdapter( + self.SessionObject, server.Session, self.ISessionObject) + # Un-registration is done by registering None. + self.addCleanup( + components.getRegistry().register, + [self.ISessionObject], server.Session, '', None) + channel = DummyChannel() + request = server.Request(channel, 1) + session = self.DummySession() + request.session = session + + result = request.getSession(sessionInterface=self.ISessionObject) + + self.assertIsInstance(result, self.SessionObject) + self.assertEqual(42, result.value) + + + def test_getSessionNonExistent(self): + """ + When request (or the site associated with this request) has no + previous session, a new one is created using the name provided by + `getSessionCookieName` as cookie is set to inform the web client + about session id. + """ + site = server.Site(resource.Resource()) + site.sessionFactory = self.DummySession + channel = DummyChannel() + channel.site = site + request = server.Request(channel, 1) + request.sitepath = [] + request.site = site + + result = request.getSession() + + session_raw_cookie = '%s=%s; Path=/' % ( + request.getSessionCookieName(), result.uid,) + self.assertEqual(session_raw_cookie, request.cookies[0]) + + # Getting the session again, will return the same object. + self.assertIs(result, request.getSession()) + + + def test_getSessionCookieName(self): + """ + Default implementation returns the name based on + I{Request.sessionCookieBaseName}. + """ + baseName = b'CUSTOM_NAME' + channel = DummyChannel() + request = server.Request(channel, 1) + request.sitepath = [] + request.site = channel.site + request.sessionCookieBaseName = baseName + + name = request.getSessionCookieName() + + self.assertEqual(name, baseName) + class GzipEncoderTests(unittest.TestCase): diff --git a/twisted/web/topfiles/6933.feature b/twisted/web/topfiles/6933.feature new file mode 100644 index 0000000..98fd006 --- /dev/null +++ b/twisted/web/topfiles/6933.feature @@ -0,0 +1 @@ +twisted.web.server.Request allows specifying a custom name for the session cookie \ No newline at end of file