2 localDir
= os
.path
.dirname(__file__
)
8 from cherrypy
._cpcompat
import copykeys
, HTTPConnection
, HTTPSConnection
9 from cherrypy
.lib
import sessions
10 from cherrypy
.lib
.httputil
import response_codes
12 def http_methods_allowed(methods
=['GET', 'HEAD']):
13 method
= cherrypy
.request
.method
.upper()
14 if method
not in methods
:
15 cherrypy
.response
.headers
['Allow'] = ", ".join(methods
)
16 raise cherrypy
.HTTPError(405)
18 cherrypy
.tools
.allow
= cherrypy
.Tool('on_start_resource', http_methods_allowed
)
25 _cp_config
= {'tools.sessions.on': True,
26 'tools.sessions.storage_type' : 'ram',
27 'tools.sessions.storage_path' : localDir
,
28 'tools.sessions.timeout': (1.0 / 60),
29 'tools.sessions.clean_freq': (1.0 / 60),
33 cherrypy
.session
.cache
.clear()
37 cherrypy
.session
['aha'] = 'foo'
38 return repr(cherrypy
.session
._data
)
42 counter
= cherrypy
.session
.get('counter', 0) + 1
43 cherrypy
.session
['counter'] = counter
45 testGen
.exposed
= True
48 counter
= cherrypy
.session
.get('counter', 0) + 1
49 cherrypy
.session
['counter'] = counter
51 testStr
.exposed
= True
53 def setsessiontype(self
, newtype
):
54 self
.__class
__._cp
_config
.update({'tools.sessions.storage_type': newtype
})
55 if hasattr(cherrypy
, "session"):
57 cls
= getattr(sessions
, newtype
.title() + 'Session')
59 cls
.clean_thread
.stop()
60 cls
.clean_thread
.unsubscribe()
62 setsessiontype
.exposed
= True
63 setsessiontype
._cp
_config
= {'tools.sessions.on': False}
66 sess
= cherrypy
.session
67 c
= sess
.get('counter', 0) + 1
74 return str(key
in cherrypy
.session
)
78 cherrypy
.session
.delete()
83 def delkey(self
, key
):
84 del cherrypy
.session
[key
]
89 return self
._cp
_config
['tools.sessions.storage_type']
93 raise cherrypy
.InternalRedirect('/blah')
97 return cherrypy
.request
.method
98 restricted
.exposed
= True
99 restricted
._cp
_config
= {'tools.allow.on': True,
100 'tools.allow.methods': ['GET']}
103 cherrypy
.tools
.sessions
.regenerate()
108 return str(len(cherrypy
.session
))
109 length
.exposed
= True
111 def session_cookie(self
):
112 # Must load() to start the clean thread.
113 cherrypy
.session
.load()
114 return cherrypy
.session
.id
115 session_cookie
.exposed
= True
116 session_cookie
._cp
_config
= {
117 'tools.sessions.path': '/session_cookie',
118 'tools.sessions.name': 'temp',
119 'tools.sessions.persistent': False}
121 cherrypy
.tree
.mount(Root())
124 from cherrypy
.test
import helper
126 class SessionTest(helper
.CPWebCase
):
127 setup_server
= staticmethod(setup_server
)
131 for fname
in os
.listdir(localDir
):
132 if fname
.startswith(sessions
.FileSession
.SESSION_PREFIX
):
133 os
.unlink(os
.path
.join(localDir
, fname
))
135 def test_0_Session(self
):
136 self
.getPage('/setsessiontype/ram')
137 self
.getPage('/clear')
139 # Test that a normal request gets the same id in the cookies.
140 # Note: this wouldn't work if /data didn't load the session.
141 self
.getPage('/data')
142 self
.assertBody("{'aha': 'foo'}")
144 self
.getPage('/data', self
.cookies
)
145 self
.assertEqual(self
.cookies
[0], c
)
147 self
.getPage('/testStr')
149 cookie_parts
= dict([p
.strip().split('=')
150 for p
in self
.cookies
[0][1].split(";")])
151 # Assert there is an 'expires' param
152 self
.assertEqual(set(cookie_parts
.keys()),
153 set(['session_id', 'expires', 'Path']))
154 self
.getPage('/testGen', self
.cookies
)
156 self
.getPage('/testStr', self
.cookies
)
158 self
.getPage('/data', self
.cookies
)
159 self
.assertBody("{'aha': 'foo', 'counter': 3}")
160 self
.getPage('/length', self
.cookies
)
162 self
.getPage('/delkey?key=counter', self
.cookies
)
163 self
.assertStatus(200)
165 self
.getPage('/setsessiontype/file')
166 self
.getPage('/testStr')
168 self
.getPage('/testGen', self
.cookies
)
170 self
.getPage('/testStr', self
.cookies
)
172 self
.getPage('/delkey?key=counter', self
.cookies
)
173 self
.assertStatus(200)
175 # Wait for the session.timeout (1 second)
179 self
.getPage('/length', self
.cookies
)
182 # Test session __contains__
183 self
.getPage('/keyin?key=counter', self
.cookies
)
184 self
.assertBody("True")
185 cookieset1
= self
.cookies
187 # Make a new session and test __len__ again
189 self
.getPage('/length', self
.cookies
)
192 # Test session delete
193 self
.getPage('/delete', self
.cookies
)
194 self
.assertBody("done")
195 self
.getPage('/delete', cookieset1
)
196 self
.assertBody("done")
197 f
= lambda: [x
for x
in os
.listdir(localDir
) if x
.startswith('session-')]
198 self
.assertEqual(f(), [])
200 # Wait for the cleanup thread to delete remaining session files
202 f
= lambda: [x
for x
in os
.listdir(localDir
) if x
.startswith('session-')]
203 self
.assertNotEqual(f(), [])
205 self
.assertEqual(f(), [])
207 def test_1_Ram_Concurrency(self
):
208 self
.getPage('/setsessiontype/ram')
209 self
._test
_Concurrency
()
211 def test_2_File_Concurrency(self
):
212 self
.getPage('/setsessiontype/file')
213 self
._test
_Concurrency
()
215 def _test_Concurrency(self
):
216 client_thread_count
= 5
222 cookies
= self
.cookies
228 if self
.scheme
== 'https':
229 c
= HTTPSConnection('%s:%s' % (self
.interface(), self
.PORT
))
231 c
= HTTPConnection('%s:%s' % (self
.interface(), self
.PORT
))
232 for i
in range(request_count
):
233 c
.putrequest('GET', '/')
237 response
= c
.getresponse()
238 body
= response
.read()
239 if response
.status
!= 200 or not body
.isdigit():
240 errors
.append((response
.status
, body
))
242 data_dict
[index
] = max(data_dict
[index
], int(body
))
243 # Uncomment the following line to prove threads overlap.
244 ## sys.stdout.write("%d " % index)
246 # Start <request_count> requests from each of
247 # <client_thread_count> concurrent clients
249 for c
in range(client_thread_count
):
251 t
= threading
.Thread(target
=request
, args
=(c
,))
258 hitcount
= max(data_dict
.values())
259 expected
= 1 + (client_thread_count
* request_count
)
263 self
.assertEqual(hitcount
, expected
)
265 def test_3_Redirect(self
):
266 # Start a new session
267 self
.getPage('/testStr')
268 self
.getPage('/iredir', self
.cookies
)
269 self
.assertBody("file")
271 def test_4_File_deletion(self
):
272 # Start a new session
273 self
.getPage('/testStr')
274 # Delete the session file manually and retry.
275 id = self
.cookies
[0][1].split(";", 1)[0].split("=", 1)[1]
276 path
= os
.path
.join(localDir
, "session-" + id)
278 self
.getPage('/testStr', self
.cookies
)
280 def test_5_Error_paths(self
):
281 self
.getPage('/unknown/page')
282 self
.assertErrorPage(404, "The path '/unknown/page' was not found.")
284 # Note: this path is *not* the same as above. The above
285 # takes a normal route through the session code; this one
286 # skips the session code's before_handler and only calls
287 # before_finalize (save) and on_end (close). So the session
288 # code has to survive calling save/close without init.
289 self
.getPage('/restricted', self
.cookies
, method
='POST')
290 self
.assertErrorPage(405, response_codes
[405])
292 def test_6_regenerate(self
):
293 self
.getPage('/testStr')
295 id1
= self
.cookies
[0][1].split(";", 1)[0].split("=", 1)[1]
296 self
.getPage('/regen')
297 self
.assertBody('logged in')
298 id2
= self
.cookies
[0][1].split(";", 1)[0].split("=", 1)[1]
299 self
.assertNotEqual(id1
, id2
)
301 self
.getPage('/testStr')
303 id1
= self
.cookies
[0][1].split(";", 1)[0].split("=", 1)[1]
304 self
.getPage('/testStr',
306 'session_id=maliciousid; '
307 'expires=Sat, 27 Oct 2017 04:18:28 GMT; Path=/;')])
308 id2
= self
.cookies
[0][1].split(";", 1)[0].split("=", 1)[1]
309 self
.assertNotEqual(id1
, id2
)
310 self
.assertNotEqual(id2
, 'maliciousid')
312 def test_7_session_cookies(self
):
313 self
.getPage('/setsessiontype/ram')
314 self
.getPage('/clear')
315 self
.getPage('/session_cookie')
317 cookie_parts
= dict([p
.strip().split('=') for p
in self
.cookies
[0][1].split(";")])
318 # Assert there is no 'expires' param
319 self
.assertEqual(set(cookie_parts
.keys()), set(['temp', 'Path']))
320 id1
= cookie_parts
['temp']
321 self
.assertEqual(copykeys(sessions
.RamSession
.cache
), [id1
])
323 # Send another request in the same "browser session".
324 self
.getPage('/session_cookie', self
.cookies
)
325 cookie_parts
= dict([p
.strip().split('=') for p
in self
.cookies
[0][1].split(";")])
326 # Assert there is no 'expires' param
327 self
.assertEqual(set(cookie_parts
.keys()), set(['temp', 'Path']))
329 self
.assertEqual(copykeys(sessions
.RamSession
.cache
), [id1
])
331 # Simulate a browser close by just not sending the cookies
332 self
.getPage('/session_cookie')
334 cookie_parts
= dict([p
.strip().split('=') for p
in self
.cookies
[0][1].split(";")])
335 # Assert there is no 'expires' param
336 self
.assertEqual(set(cookie_parts
.keys()), set(['temp', 'Path']))
337 # Assert a new id has been generated...
338 id2
= cookie_parts
['temp']
339 self
.assertNotEqual(id1
, id2
)
340 self
.assertEqual(set(sessions
.RamSession
.cache
.keys()), set([id1
, id2
]))
342 # Wait for the session.timeout on both sessions
344 cache
= copykeys(sessions
.RamSession
.cache
)
347 self
.fail("The second session did not time out.")
349 self
.fail("Unknown session id in cache: %r", cache
)
356 host
, port
= '127.0.0.1', 11211
357 for res
in socket
.getaddrinfo(host
, port
, socket
.AF_UNSPEC
,
359 af
, socktype
, proto
, canonname
, sa
= res
362 s
= socket
.socket(af
, socktype
, proto
)
363 # See http://groups.google.com/group/cherrypy-users/
364 # browse_frm/thread/bbfe5eb39c904fe0
366 s
.connect((host
, port
))
373 except (ImportError, socket
.error
):
374 class MemcachedSessionTest(helper
.CPWebCase
):
375 setup_server
= staticmethod(setup_server
)
378 return self
.skip("memcached not reachable ")
380 class MemcachedSessionTest(helper
.CPWebCase
):
381 setup_server
= staticmethod(setup_server
)
383 def test_0_Session(self
):
384 self
.getPage('/setsessiontype/memcached')
386 self
.getPage('/testStr')
388 self
.getPage('/testGen', self
.cookies
)
390 self
.getPage('/testStr', self
.cookies
)
392 self
.getPage('/length', self
.cookies
)
393 self
.assertErrorPage(500)
394 self
.assertInBody("NotImplementedError")
395 self
.getPage('/delkey?key=counter', self
.cookies
)
396 self
.assertStatus(200)
398 # Wait for the session.timeout (1 second)
403 # Test session __contains__
404 self
.getPage('/keyin?key=counter', self
.cookies
)
405 self
.assertBody("True")
407 # Test session delete
408 self
.getPage('/delete', self
.cookies
)
409 self
.assertBody("done")
411 def test_1_Concurrency(self
):
412 client_thread_count
= 5
418 cookies
= self
.cookies
423 for i
in range(request_count
):
424 self
.getPage("/", cookies
)
425 # Uncomment the following line to prove threads overlap.
426 ## sys.stdout.write("%d " % index)
427 if not self
.body
.isdigit():
429 data_dict
[index
] = v
= int(self
.body
)
431 # Start <request_count> concurrent requests from
432 # each of <client_thread_count> clients
434 for c
in range(client_thread_count
):
436 t
= threading
.Thread(target
=request
, args
=(c
,))
443 hitcount
= max(data_dict
.values())
444 expected
= 1 + (client_thread_count
* request_count
)
445 self
.assertEqual(hitcount
, expected
)
447 def test_3_Redirect(self
):
448 # Start a new session
449 self
.getPage('/testStr')
450 self
.getPage('/iredir', self
.cookies
)
451 self
.assertBody("memcached")
453 def test_5_Error_paths(self
):
454 self
.getPage('/unknown/page')
455 self
.assertErrorPage(404, "The path '/unknown/page' was not found.")
457 # Note: this path is *not* the same as above. The above
458 # takes a normal route through the session code; this one
459 # skips the session code's before_handler and only calls
460 # before_finalize (save) and on_end (close). So the session
461 # code has to survive calling save/close without init.
462 self
.getPage('/restricted', self
.cookies
, method
='POST')
463 self
.assertErrorPage(405, response_codes
[405])