Codebase list cherrypy3 / 7852227
Imported Upstream version 3.1.2 SVN-Git Migration 8 years ago
20 changed file(s) with 163 addition(s) and 1223 deletion(s). Raw diff Collapse all Expand all
00 Metadata-Version: 1.0
11 Name: CherryPy
2 Version: 3.1.1
2 Version: 3.1.2
33 Summary: Object-Oriented HTTP framework
44 Home-page: http://www.cherrypy.org
55 Author: CherryPy Team
66 Author-email: team@cherrypy.org
77 License: BSD
8 Download-URL: http://download.cherrypy.org/cherrypy/3.1.1/
8 Download-URL: http://download.cherrypy.org/cherrypy/3.1.2/
99 Description: CherryPy is a pythonic, object-oriented HTTP framework
1010 Platform: UNKNOWN
1111 Classifier: Development Status :: 5 - Production/Stable
5656 http://www.cherrypy.org/wiki/CherryPySpec
5757 """
5858
59 __version__ = "3.1.1"
59 __version__ = "3.1.2"
6060
6161 from urlparse import urljoin as _urljoin
6262
3838 finally:
3939 warnings.formatwarning = oldformatwarning
4040
41 def formatwarning(self, message, category, filename, lineno):
41 def formatwarning(self, message, category, filename, lineno, line=None):
4242 """Function to format a warning."""
4343 return "CherryPy Checker:\n%s\n\n" % message
4444
186186 self.status = status = int(status)
187187 if status < 400 or status > 599:
188188 raise ValueError("status must be between 400 and 599.")
189 self.message = message
189 # See http://www.python.org/dev/peps/pep-0352/
190 # self.message = message
191 self._message = message
190192 CherryPyException.__init__(self, status, message)
191193
192194 def set_response(self):
210212 response.headers['Content-Type'] = "text/html"
211213
212214 content = self.get_error_page(self.status, traceback=tb,
213 message=self.message)
215 message=self._message)
214216 response.body = content
215217 response.headers['Content-Length'] = len(content)
216218
645645 # Handle cookies differently because on Konqueror, multiple
646646 # cookies come on different lines with the same key
647647 if name == 'Cookie':
648 self.cookie.load(value)
648 try:
649 self.cookie.load(value)
650 except Cookie.CookieError:
651 msg = "Illegal cookie name %s" % value.split('=')[0]
652 raise cherrypy.HTTPError(400, msg)
649653
650654 if not dict.__contains__(headers, 'Host'):
651655 # All Internet-based HTTP/1.1 servers MUST respond with a 400
1616 self.clear()
1717 t = threading.Thread(target=self.expire_cache, name='expire_cache')
1818 self.expiration_thread = t
19 t.setDaemon(True)
19 if hasattr(threading.Thread, "daemon"):
20 # Python 2.6+
21 t.daemon = True
22 else:
23 t.setDaemon(True)
2024 t.start()
2125
2226 def clear(self):
00 """Functions for builtin CherryPy tools."""
11
22 import logging
3 import md5
3
4 try:
5 # Python 2.5+
6 from hashlib import md5
7 except ImportError:
8 from md5 import new as md5
9
410 import re
511
612 import cherrypy
3945 if (not etag) and autotags:
4046 if status == 200:
4147 etag = response.collapse_body()
42 etag = '"%s"' % md5.new(etag).hexdigest()
48 etag = '"%s"' % md5(etag).hexdigest()
4349 response.headers['ETag'] = etag
4450
4551 response.ETag = etag
240240 # to the client.
241241 return
242242
243 ct = response.headers.get('Content-Type').split(';')[0]
243 ct = response.headers.get('Content-Type', '').split(';')[0]
244244 for coding in acceptable:
245245 if coding.value == 'identity' and coding.qvalue != 0:
246246 return
5858 "calculateNonce", "SUPPORTED_QOP")
5959
6060 ################################################################################
61 import md5
61
62 try:
63 # Python 2.5+
64 from hashlib import md5
65 except ImportError:
66 from md5 import new as md5
67
6268 import time
6369 import base64
6470 import urllib2
7581 # doAuth
7682 #
7783 DIGEST_AUTH_ENCODERS = {
78 MD5: lambda val: md5.new (val).hexdigest (),
79 MD5_SESS: lambda val: md5.new (val).hexdigest (),
80 # SHA: lambda val: sha.new (val).hexdigest (),
84 MD5: lambda val: md5(val).hexdigest(),
85 MD5_SESS: lambda val: md5(val).hexdigest(),
86 # SHA: lambda val: sha(val).hexdigest(),
8187 }
8288
8389 def calculateNonce (realm, algorithm = MD5):
+0
-698
cherrypy/lib/sessions-r2062.py less more
0 """Session implementation for CherryPy.
1
2 We use cherrypy.request to store some convenient variables as
3 well as data about the session for the current request. Instead of
4 polluting cherrypy.request we use a Session object bound to
5 cherrypy.session to store these variables.
6 """
7
8 import datetime
9 import os
10 try:
11 import cPickle as pickle
12 except ImportError:
13 import pickle
14 import random
15 import sha
16 import time
17 import threading
18 import types
19 from warnings import warn
20
21 import cherrypy
22 from cherrypy.lib import http
23
24
25 missing = object()
26
27 class Session(object):
28 """A CherryPy dict-like Session object (one per request)."""
29
30 __metaclass__ = cherrypy._AttributeDocstrings
31
32 _id = None
33 id_observers = None
34 id_observers__doc = "A list of callbacks to which to pass new id's."
35
36 id__doc = "The current session ID."
37 def _get_id(self):
38 return self._id
39 def _set_id(self, value):
40 self._id = value
41 for o in self.id_observers:
42 o(value)
43 id = property(_get_id, _set_id, doc=id__doc)
44
45 timeout = 60
46 timeout__doc = "Number of minutes after which to delete session data."
47
48 locked = False
49 locked__doc = """
50 If True, this session instance has exclusive read/write access
51 to session data."""
52
53 loaded = False
54 loaded__doc = """
55 If True, data has been retrieved from storage. This should happen
56 automatically on the first attempt to access session data."""
57
58 clean_thread = None
59 clean_thread__doc = "Class-level Monitor which calls self.clean_up."
60
61 clean_freq = 5
62 clean_freq__doc = "The poll rate for expired session cleanup in minutes."
63
64 def __init__(self, id=None, **kwargs):
65 self.id_observers = []
66 self._data = {}
67
68 for k, v in kwargs.iteritems():
69 setattr(self, k, v)
70
71 if id is None:
72 self.regenerate()
73 else:
74 self.id = id
75 if not self._exists():
76 # Expired or malicious session. Make a new one.
77 # See http://www.cherrypy.org/ticket/709.
78 self.id = None
79 self.regenerate()
80
81 def regenerate(self):
82 """Replace the current session (with a new id)."""
83 if self.id is not None:
84 self.delete()
85
86 old_session_was_locked = self.locked
87 if old_session_was_locked:
88 self.release_lock()
89
90 self.id = None
91 while self.id is None:
92 self.id = self.generate_id()
93 # Assert that the generated id is not already stored.
94 if self._exists():
95 self.id = None
96
97 if old_session_was_locked:
98 self.acquire_lock()
99
100 def clean_up(self):
101 """Clean up expired sessions."""
102 pass
103
104 try:
105 os.urandom(20)
106 except (AttributeError, NotImplementedError):
107 # os.urandom not available until Python 2.4. Fall back to random.random.
108 def generate_id(self):
109 """Return a new session id."""
110 return sha.new('%s' % random.random()).hexdigest()
111 else:
112 def generate_id(self):
113 """Return a new session id."""
114 return os.urandom(20).encode('hex')
115
116 def save(self):
117 """Save session data."""
118 try:
119 # If session data has never been loaded then it's never been
120 # accessed: no need to delete it
121 if self.loaded:
122 t = datetime.timedelta(seconds = self.timeout * 60)
123 expiration_time = datetime.datetime.now() + t
124 self._save(expiration_time)
125
126 finally:
127 if self.locked:
128 # Always release the lock if the user didn't release it
129 self.release_lock()
130
131 def load(self):
132 """Copy stored session data into this session instance."""
133 data = self._load()
134 # data is either None or a tuple (session_data, expiration_time)
135 if data is None or data[1] < datetime.datetime.now():
136 # Expired session: flush session data
137 self._data = {}
138 else:
139 self._data = data[0]
140 self.loaded = True
141
142 # Stick the clean_thread in the class, not the instance.
143 # The instances are created and destroyed per-request.
144 cls = self.__class__
145 if self.clean_freq and not cls.clean_thread:
146 # clean_up is in instancemethod and not a classmethod,
147 # so that tool config can be accessed inside the method.
148 t = cherrypy.process.plugins.Monitor(
149 cherrypy.engine, self.clean_up, self.clean_freq * 60)
150 t.subscribe()
151 cls.clean_thread = t
152 t.start()
153
154 def delete(self):
155 """Delete stored session data."""
156 self._delete()
157
158 def __getitem__(self, key):
159 if not self.loaded: self.load()
160 return self._data[key]
161
162 def __setitem__(self, key, value):
163 if not self.loaded: self.load()
164 self._data[key] = value
165
166 def __delitem__(self, key):
167 if not self.loaded: self.load()
168 del self._data[key]
169
170 def pop(self, key, default=missing):
171 """Remove the specified key and return the corresponding value.
172 If key is not found, default is returned if given,
173 otherwise KeyError is raised.
174 """
175 if not self.loaded: self.load()
176 if default is missing:
177 return self._data.pop(key)
178 else:
179 return self._data.pop(key, default)
180
181 def __contains__(self, key):
182 if not self.loaded: self.load()
183 return key in self._data
184
185 def has_key(self, key):
186 """D.has_key(k) -> True if D has a key k, else False."""
187 if not self.loaded: self.load()
188 return self._data.has_key(key)
189
190 def get(self, key, default=None):
191 """D.get(k[,d]) -> D[k] if k in D, else d. d defaults to None."""
192 if not self.loaded: self.load()
193 return self._data.get(key, default)
194
195 def update(self, d):
196 """D.update(E) -> None. Update D from E: for k in E: D[k] = E[k]."""
197 if not self.loaded: self.load()
198 self._data.update(d)
199
200 def setdefault(self, key, default=None):
201 """D.setdefault(k[,d]) -> D.get(k,d), also set D[k]=d if k not in D."""
202 if not self.loaded: self.load()
203 return self._data.setdefault(key, default)
204
205 def clear(self):
206 """D.clear() -> None. Remove all items from D."""
207 if not self.loaded: self.load()
208 self._data.clear()
209
210 def keys(self):
211 """D.keys() -> list of D's keys."""
212 if not self.loaded: self.load()
213 return self._data.keys()
214
215 def items(self):
216 """D.items() -> list of D's (key, value) pairs, as 2-tuples."""
217 if not self.loaded: self.load()
218 return self._data.items()
219
220 def values(self):
221 """D.values() -> list of D's values."""
222 if not self.loaded: self.load()
223 return self._data.values()
224
225
226 class RamSession(Session):
227
228 # Class-level objects. Don't rebind these!
229 cache = {}
230 locks = {}
231
232 def clean_up(self):
233 """Clean up expired sessions."""
234 now = datetime.datetime.now()
235 for id, (data, expiration_time) in self.cache.items():
236 if expiration_time < now:
237 try:
238 del self.cache[id]
239 except KeyError:
240 pass
241 try:
242 del self.locks[id]
243 except KeyError:
244 pass
245
246 def _exists(self):
247 return self.id in self.cache
248
249 def _load(self):
250 return self.cache.get(self.id)
251
252 def _save(self, expiration_time):
253 self.cache[self.id] = (self._data, expiration_time)
254
255 def _delete(self):
256 del self.cache[self.id]
257
258 def acquire_lock(self):
259 """Acquire an exclusive lock on the currently-loaded session data."""
260 self.locked = True
261 self.locks.setdefault(self.id, threading.RLock()).acquire()
262
263 def release_lock(self):
264 """Release the lock on the currently-loaded session data."""
265 self.locks[self.id].release()
266 self.locked = False
267
268 def __len__(self):
269 """Return the number of active sessions."""
270 return len(self.cache)
271
272
273 class FileSession(Session):
274 """Implementation of the File backend for sessions
275
276 storage_path: the folder where session data will be saved. Each session
277 will be saved as pickle.dump(data, expiration_time) in its own file;
278 the filename will be self.SESSION_PREFIX + self.id.
279 """
280
281 SESSION_PREFIX = 'session-'
282 LOCK_SUFFIX = '.lock'
283
284 def __init__(self, id=None, **kwargs):
285 # The 'storage_path' arg is required for file-based sessions.
286 kwargs['storage_path'] = os.path.abspath(kwargs['storage_path'])
287 Session.__init__(self, id=id, **kwargs)
288
289 def setup(cls, **kwargs):
290 """Set up the storage system for file-based sessions.
291
292 This should only be called once per process; this will be done
293 automatically when using sessions.init (as the built-in Tool does).
294 """
295 # The 'storage_path' arg is required for file-based sessions.
296 kwargs['storage_path'] = os.path.abspath(kwargs['storage_path'])
297
298 for k, v in kwargs.iteritems():
299 setattr(cls, k, v)
300
301 # Warn if any lock files exist at startup.
302 lockfiles = [fname for fname in os.listdir(cls.storage_path)
303 if (fname.startswith(cls.SESSION_PREFIX)
304 and fname.endswith(cls.LOCK_SUFFIX))]
305 if lockfiles:
306 plural = ('', 's')[len(lockfiles) > 1]
307 warn("%s session lockfile%s found at startup. If you are "
308 "only running one process, then you may need to "
309 "manually delete the lockfiles found at %r."
310 % (len(lockfiles), plural, cls.storage_path))
311 setup = classmethod(setup)
312
313 def _get_file_path(self):
314 f = os.path.join(self.storage_path, self.SESSION_PREFIX + self.id)
315 if not os.path.abspath(f).startswith(self.storage_path):
316 raise cherrypy.HTTPError(400, "Invalid session id in cookie.")
317 return f
318
319 def _exists(self):
320 path = self._get_file_path()
321 return os.path.exists(path)
322
323 def _load(self, path=None):
324 if path is None:
325 path = self._get_file_path()
326 try:
327 f = open(path, "rb")
328 try:
329 return pickle.load(f)
330 finally:
331 f.close()
332 except (IOError, EOFError):
333 return None
334
335 def _save(self, expiration_time):
336 f = open(self._get_file_path(), "wb")
337 try:
338 pickle.dump((self._data, expiration_time), f)
339 finally:
340 f.close()
341
342 def _delete(self):
343 try:
344 os.unlink(self._get_file_path())
345 except OSError:
346 pass
347
348 def acquire_lock(self, path=None):
349 """Acquire an exclusive lock on the currently-loaded session data."""
350 if path is None:
351 path = self._get_file_path()
352 path += self.LOCK_SUFFIX
353 while True:
354 try:
355 lockfd = os.open(path, os.O_CREAT|os.O_WRONLY|os.O_EXCL)
356 except OSError:
357 time.sleep(0.1)
358 else:
359 os.close(lockfd)
360 break
361 self.locked = True
362
363 def release_lock(self, path=None):
364 """Release the lock on the currently-loaded session data."""
365 if path is None:
366 path = self._get_file_path()
367 os.unlink(path + self.LOCK_SUFFIX)
368 self.locked = False
369
370 def clean_up(self):
371 """Clean up expired sessions."""
372 now = datetime.datetime.now()
373 # Iterate over all session files in self.storage_path
374 for fname in os.listdir(self.storage_path):
375 if (fname.startswith(self.SESSION_PREFIX)
376 and not fname.endswith(self.LOCK_SUFFIX)):
377 # We have a session file: lock and load it and check
378 # if it's expired. If it fails, nevermind.
379 path = os.path.join(self.storage_path, fname)
380 self.acquire_lock(path)
381 try:
382 contents = self._load(path)
383 # _load returns None on IOError
384 if contents is not None:
385 data, expiration_time = contents
386 if expiration_time < now:
387 # Session expired: deleting it
388 os.unlink(path)
389 finally:
390 self.release_lock(path)
391
392 def __len__(self):
393 """Return the number of active sessions."""
394 return len([fname for fname in os.listdir(self.storage_path)
395 if (fname.startswith(self.SESSION_PREFIX)
396 and not fname.endswith(self.LOCK_SUFFIX))])
397
398
399 class PostgresqlSession(Session):
400 """ Implementation of the PostgreSQL backend for sessions. It assumes
401 a table like this:
402
403 create table session (
404 id varchar(40),
405 data text,
406 expiration_time timestamp
407 )
408
409 You must provide your own get_db function.
410 """
411
412 def __init__(self, id=None, **kwargs):
413 Session.__init__(self, id, **kwargs)
414 self.cursor = self.db.cursor()
415
416 def setup(cls, **kwargs):
417 """Set up the storage system for Postgres-based sessions.
418
419 This should only be called once per process; this will be done
420 automatically when using sessions.init (as the built-in Tool does).
421 """
422 for k, v in kwargs.iteritems():
423 setattr(cls, k, v)
424
425 self.db = self.get_db()
426 setup = classmethod(setup)
427
428 def __del__(self):
429 if self.cursor:
430 self.cursor.close()
431 self.db.commit()
432
433 def _exists(self):
434 # Select session data from table
435 self.cursor.execute('select data, expiration_time from session '
436 'where id=%s', (self.id,))
437 rows = self.cursor.fetchall()
438 return bool(rows)
439
440 def _load(self):
441 # Select session data from table
442 self.cursor.execute('select data, expiration_time from session '
443 'where id=%s', (self.id,))
444 rows = self.cursor.fetchall()
445 if not rows:
446 return None
447
448 pickled_data, expiration_time = rows[0]
449 data = pickle.loads(pickled_data)
450 return data, expiration_time
451
452 def _save(self, expiration_time):
453 pickled_data = pickle.dumps(self._data)
454 self.cursor.execute('update session set data = %s, '
455 'expiration_time = %s where id = %s',
456 (pickled_data, expiration_time, self.id))
457
458 def _delete(self):
459 self.cursor.execute('delete from session where id=%s', (self.id,))
460
461 def acquire_lock(self):
462 """Acquire an exclusive lock on the currently-loaded session data."""
463 # We use the "for update" clause to lock the row
464 self.locked = True
465 self.cursor.execute('select id from session where id=%s for update',
466 (self.id,))
467
468 def release_lock(self):
469 """Release the lock on the currently-loaded session data."""
470 # We just close the cursor and that will remove the lock
471 # introduced by the "for update" clause
472 self.cursor.close()
473 self.locked = False
474
475 def clean_up(self):
476 """Clean up expired sessions."""
477 self.cursor.execute('delete from session where expiration_time < %s',
478 (datetime.datetime.now(),))
479
480
481 class MemcachedSession(Session):
482
483 # The most popular memcached client for Python isn't thread-safe.
484 # Wrap all .get and .set operations in a single lock.
485 mc_lock = threading.RLock()
486
487 # This is a seperate set of locks per session id.
488 locks = {}
489
490 servers = ['127.0.0.1:11211']
491
492 def setup(cls, **kwargs):
493 """Set up the storage system for memcached-based sessions.
494
495 This should only be called once per process; this will be done
496 automatically when using sessions.init (as the built-in Tool does).
497 """
498 for k, v in kwargs.iteritems():
499 setattr(cls, k, v)
500
501 import memcache
502 cls.cache = memcache.Client(cls.servers)
503 setup = classmethod(setup)
504
505 def _exists(self):
506 self.mc_lock.acquire()
507 try:
508 return bool(self.cache.get(self.id))
509 finally:
510 self.mc_lock.release()
511
512 def _load(self):
513 self.mc_lock.acquire()
514 try:
515 return self.cache.get(self.id)
516 finally:
517 self.mc_lock.release()
518
519 def _save(self, expiration_time):
520 # Send the expiration time as "Unix time" (seconds since 1/1/1970)
521 td = int(time.mktime(expiration_time.timetuple()))
522 self.mc_lock.acquire()
523 try:
524 if not self.cache.set(self.id, (self._data, expiration_time), td):
525 raise AssertionError("Session data for id %r not set." % self.id)
526 finally:
527 self.mc_lock.release()
528
529 def _delete(self):
530 self.cache.delete(self.id)
531
532 def acquire_lock(self):
533 """Acquire an exclusive lock on the currently-loaded session data."""
534 self.locked = True
535 self.locks.setdefault(self.id, threading.RLock()).acquire()
536
537 def release_lock(self):
538 """Release the lock on the currently-loaded session data."""
539 self.locks[self.id].release()
540 self.locked = False
541
542 def __len__(self):
543 """Return the number of active sessions."""
544 raise NotImplementedError
545
546
547 # Hook functions (for CherryPy tools)
548
549 def save():
550 """Save any changed session data."""
551
552 if not hasattr(cherrypy.serving, "session"):
553 return
554
555 # Guard against running twice
556 if hasattr(cherrypy.request, "_sessionsaved"):
557 return
558 cherrypy.request._sessionsaved = True
559
560 if cherrypy.response.stream:
561 # If the body is being streamed, we have to save the data
562 # *after* the response has been written out
563 cherrypy.request.hooks.attach('on_end_request', cherrypy.session.save)
564 else:
565 # If the body is not being streamed, we save the data now
566 # (so we can release the lock).
567 if isinstance(cherrypy.response.body, types.GeneratorType):
568 cherrypy.response.collapse_body()
569 cherrypy.session.save()
570 save.failsafe = True
571
572 def close():
573 """Close the session object for this request."""
574 sess = getattr(cherrypy.serving, "session", None)
575 if getattr(sess, "locked", False):
576 # If the session is still locked we release the lock
577 sess.release_lock()
578 close.failsafe = True
579 close.priority = 90
580
581
582 def init(storage_type='ram', path=None, path_header=None, name='session_id',
583 timeout=60, domain=None, secure=False, clean_freq=5,
584 persistent=True, **kwargs):
585 """Initialize session object (using cookies).
586
587 storage_type: one of 'ram', 'file', 'postgresql'. This will be used
588 to look up the corresponding class in cherrypy.lib.sessions
589 globals. For example, 'file' will use the FileSession class.
590 path: the 'path' value to stick in the response cookie metadata.
591 path_header: if 'path' is None (the default), then the response
592 cookie 'path' will be pulled from request.headers[path_header].
593 name: the name of the cookie.
594 timeout: the expiration timeout (in minutes) for the stored session data.
595 If 'persistent' is True (the default), this is also the timeout
596 for the cookie.
597 domain: the cookie domain.
598 secure: if False (the default) the cookie 'secure' value will not
599 be set. If True, the cookie 'secure' value will be set (to 1).
600 clean_freq (minutes): the poll rate for expired session cleanup.
601 persistent: if True (the default), the 'timeout' argument will be used
602 to expire the cookie. If False, the cookie will not have an expiry,
603 and the cookie will be a "session cookie" which expires when the
604 browser is closed.
605
606 Any additional kwargs will be bound to the new Session instance,
607 and may be specific to the storage type. See the subclass of Session
608 you're using for more information.
609 """
610
611 request = cherrypy.request
612
613 # Guard against running twice
614 if hasattr(request, "_session_init_flag"):
615 return
616 request._session_init_flag = True
617
618 # Check if request came with a session ID
619 id = None
620 if name in request.cookie:
621 id = request.cookie[name].value
622
623 # Find the storage class and call setup (first time only).
624 storage_class = storage_type.title() + 'Session'
625 storage_class = globals()[storage_class]
626 if not hasattr(cherrypy, "session"):
627 if hasattr(storage_class, "setup"):
628 storage_class.setup(**kwargs)
629
630 # Create and attach a new Session instance to cherrypy.serving.
631 # It will possess a reference to (and lock, and lazily load)
632 # the requested session data.
633 kwargs['timeout'] = timeout
634 kwargs['clean_freq'] = clean_freq
635 cherrypy.serving.session = sess = storage_class(id, **kwargs)
636 def update_cookie(id):
637 """Update the cookie every time the session id changes."""
638 cherrypy.response.cookie[name] = id
639 sess.id_observers.append(update_cookie)
640
641 # Create cherrypy.session which will proxy to cherrypy.serving.session
642 if not hasattr(cherrypy, "session"):
643 cherrypy.session = cherrypy._ThreadLocalProxy('session')
644
645 if persistent:
646 cookie_timeout = timeout
647 else:
648 # See http://support.microsoft.com/kb/223799/EN-US/
649 # and http://support.mozilla.com/en-US/kb/Cookies
650 cookie_timeout = None
651 set_response_cookie(path=path, path_header=path_header, name=name,
652 timeout=cookie_timeout, domain=domain, secure=secure)
653
654
655 def set_response_cookie(path=None, path_header=None, name='session_id',
656 timeout=60, domain=None, secure=False):
657 """Set a response cookie for the client.
658
659 path: the 'path' value to stick in the response cookie metadata.
660 path_header: if 'path' is None (the default), then the response
661 cookie 'path' will be pulled from request.headers[path_header].
662 name: the name of the cookie.
663 timeout: the expiration timeout for the cookie. If 0 or other boolean
664 False, no 'expires' param will be set, and the cookie will be a
665 "session cookie" which expires when the browser is closed.
666 domain: the cookie domain.
667 secure: if False (the default) the cookie 'secure' value will not
668 be set. If True, the cookie 'secure' value will be set (to 1).
669 """
670 # Set response cookie
671 cookie = cherrypy.response.cookie
672 cookie[name] = cherrypy.serving.session.id
673 cookie[name]['path'] = (path or cherrypy.request.headers.get(path_header)
674 or '/')
675
676 # We'd like to use the "max-age" param as indicated in
677 # http://www.faqs.org/rfcs/rfc2109.html but IE doesn't
678 # save it to disk and the session is lost if people close
679 # the browser. So we have to use the old "expires" ... sigh ...
680 ## cookie[name]['max-age'] = timeout * 60
681 if timeout:
682 cookie[name]['expires'] = http.HTTPDate(time.time() + (timeout * 60))
683 if domain is not None:
684 cookie[name]['domain'] = domain
685 if secure:
686 cookie[name]['secure'] = 1
687
688
689 def expire():
690 """Expire the current session cookie."""
691 name = cherrypy.request.config.get('tools.sessions.name', 'session_id')
692 one_year = 60 * 60 * 24 * 365
693 exp = time.gmtime(time.time() - one_year)
694 t = time.strftime("%a, %d-%b-%Y %H:%M:%S GMT", exp)
695 cherrypy.response.cookie[name]['expires'] = t
696
697
1212 except ImportError:
1313 import pickle
1414 import random
15 import sha
15
16 try:
17 # Python 2.5+
18 from hashlib import sha1 as sha
19 except ImportError:
20 from sha import new as sha
21
1622 import time
1723 import threading
1824 import types
107113 # os.urandom not available until Python 2.4. Fall back to random.random.
108114 def generate_id(self):
109115 """Return a new session id."""
110 return sha.new('%s' % random.random()).hexdigest()
116 return sha('%s' % random.random()).hexdigest()
111117 else:
112118 def generate_id(self):
113119 """Return a new session id."""
166166 '/home/me', the Request-URI is 'myapp', and the index arg is
167167 'index.html', the file '/home/me/myapp/index.html' will be sought.
168168 """
169 if cherrypy.request.method not in ('GET', 'HEAD'):
170 return False
171
169172 if match and not re.search(match, cherrypy.request.path_info):
170173 return False
171174
216219 a string (e.g. "gif") and 'content-type' is the value to write
217220 out in the Content-Type response header (e.g. "image/gif").
218221 """
222 if cherrypy.request.method not in ('GET', 'HEAD'):
223 return False
224
219225 if match and not re.search(match, cherrypy.request.path_info):
220226 return False
221227
149149
150150 def stop(self):
151151 """Stop the HTTP server."""
152 self.ready = False
153152 # Forcibly stop the fcgi server main event loop.
154153 self.fcgiserver._keepGoing = False
155154 # Force all worker threads to die off.
156 self.fcgiserver._threadPool.maxSpare = 0
155 self.fcgiserver._threadPool.maxSpare = self.fcgiserver._threadPool._idleCount
156 self.ready = False
157157
158158
159159 class FlupSCGIServer(object):
198198
199199 def exit(self):
200200 """Stop all services and prepare to exit the process."""
201 self.stop()
202
203 self.state = states.EXITING
204 self.log('Bus EXITING')
205 self.publish('exit')
206 # This isn't strictly necessary, but it's better than seeing
207 # "Waiting for child threads to terminate..." and then nothing.
208 self.log('Bus EXITED')
201 try:
202 self.stop()
203
204 self.state = states.EXITING
205 self.log('Bus EXITING')
206 self.publish('exit')
207 # This isn't strictly necessary, but it's better than seeing
208 # "Waiting for child threads to terminate..." and then nothing.
209 self.log('Bus EXITED')
210 except:
211 # This method is often called asynchronously (whether thread,
212 # signal handler, console handler, or atexit handler), so we
213 # can't just let exceptions propagate out unhandled.
214 # Assume it's been logged and just die.
215 os._exit(70) # EX_SOFTWARE
209216
210217 def restart(self):
211218 """Restart the process (may close connections).
222229 self.publish('graceful')
223230
224231 def block(self, interval=0.1):
225 """Wait for the EXITING state, KeyboardInterrupt or SystemExit."""
232 """Wait for the EXITING state, KeyboardInterrupt or SystemExit.
233
234 This function is intended to be called only by the main thread.
235 After waiting for the EXITING state, it also waits for all threads
236 to terminate, and then calls os.execv if self.execv is True. This
237 design allows another thread to call bus.restart, yet have the main
238 thread perform the actual execv call (required on some platforms).
239 """
226240 try:
227241 self.wait(states.EXITING, interval=interval)
228242 except (KeyboardInterrupt, IOError):
242256 # See http://www.cherrypy.org/ticket/751.
243257 self.log("Waiting for child threads to terminate...")
244258 for t in threading.enumerate():
245 if (t != threading.currentThread() and t.isAlive()
259 if t != threading.currentThread() and t.isAlive():
246260 # Note that any dummy (external) threads are always daemonic.
247 and not t.isDaemon()):
248 t.join()
261 if hasattr(threading.Thread, "daemon"):
262 # Python 2.6+
263 d = t.daemon
264 else:
265 d = t.isDaemon()
266 if not d:
267 t.join()
249268
250269 if self.execv:
251270 self._do_execv()
146146 Completed 900 requests
147147
148148
149 Server Software: CherryPy/3.1.1
149 Server Software: CherryPy/3.1.2
150150 Server Hostname: 127.0.0.1
151151 Server Port: 8080
152152
10651065
10661066 def testCookies(self):
10671067 if sys.version_info >= (2, 5):
1068 self.getPage("/cookies/single?name=First",
1069 [('Cookie', 'First=Dinsdale;')])
1070 self.assertHeader('Set-Cookie', 'First=Dinsdale')
1071
1072 self.getPage("/cookies/multiple?names=First&names=Last",
1073 [('Cookie', 'First=Dinsdale; Last=Piranha;'),
1074 ])
1075 self.assertHeader('Set-Cookie', 'First=Dinsdale')
1076 self.assertHeader('Set-Cookie', 'Last=Piranha')
1068 header_value = lambda x: x
10771069 else:
1078 self.getPage("/cookies/single?name=First",
1079 [('Cookie', 'First=Dinsdale;')])
1080 self.assertHeader('Set-Cookie', 'First=Dinsdale;')
1081
1082 self.getPage("/cookies/multiple?names=First&names=Last",
1083 [('Cookie', 'First=Dinsdale; Last=Piranha;'),
1084 ])
1085 self.assertHeader('Set-Cookie', 'First=Dinsdale;')
1086 self.assertHeader('Set-Cookie', 'Last=Piranha;')
1087
1070 header_value = lambda x: x+';'
1071
1072 self.getPage("/cookies/single?name=First",
1073 [('Cookie', 'First=Dinsdale;')])
1074 self.assertHeader('Set-Cookie', header_value('First=Dinsdale'))
1075
1076 self.getPage("/cookies/multiple?names=First&names=Last",
1077 [('Cookie', 'First=Dinsdale; Last=Piranha;'),
1078 ])
1079 self.assertHeader('Set-Cookie', header_value('First=Dinsdale'))
1080 self.assertHeader('Set-Cookie', header_value('Last=Piranha'))
1081
1082 self.getPage("/cookies/single?name=Something-With:Colon",
1083 [('Cookie', 'Something-With:Colon=some-value')])
1084 self.assertStatus(400)
1085
10881086 def testMaxRequestSize(self):
10891087 if getattr(cherrypy.server, "using_apache", False):
10901088 print "skipped due to known Apache differences...",
120120 self.assertEqual(response.fp.read(), "Malformed Request-Line")
121121 c.close()
122122
123 def test_http_over_https(self):
124 if self.scheme != 'https':
125 print "skipped (not running HTTPS)...",
126 return
127
128 # Try connecting without SSL.
129 conn = httplib.HTTPConnection('%s:%s' % (self.interface(), self.PORT))
130 conn.putrequest("GET", "/", skip_host=True)
131 conn.putheader("Host", self.HOST)
132 conn.endheaders()
133 response = conn.response_class(conn.sock, method="GET")
134 response.begin()
135 self.assertEqual(response.status, 400)
136 self.body = response.read()
137 self.assertBody("The client sent a plain HTTP request, but this "
138 "server only speaks HTTPS on this port.")
139
123140
124141 if __name__ == '__main__':
125142 setup_server()
+0
-460
cherrypy/test/test_session-r2062.py less more
0 from cherrypy.test import test
1 test.prefer_parent_path()
2
3 import httplib
4 import os
5 localDir = os.path.dirname(__file__)
6 import sys
7 import threading
8 import time
9
10 import cherrypy
11 from cherrypy.lib import sessions
12
13 def http_methods_allowed(methods=['GET', 'HEAD']):
14 method = cherrypy.request.method.upper()
15 if method not in methods:
16 cherrypy.response.headers['Allow'] = ", ".join(methods)
17 raise cherrypy.HTTPError(405)
18
19 cherrypy.tools.allow = cherrypy.Tool('on_start_resource', http_methods_allowed)
20
21
22 def setup_server():
23
24 class Root:
25
26 _cp_config = {'tools.sessions.on': True,
27 'tools.sessions.storage_type' : 'ram',
28 'tools.sessions.storage_path' : localDir,
29 'tools.sessions.timeout': (1.0 / 60),
30 'tools.sessions.clean_freq': (1.0 / 60),
31 }
32
33 def clear(self):
34 cherrypy.session.cache.clear()
35 clear.exposed = True
36
37 def testGen(self):
38 counter = cherrypy.session.get('counter', 0) + 1
39 cherrypy.session['counter'] = counter
40 yield str(counter)
41 testGen.exposed = True
42
43 def testStr(self):
44 counter = cherrypy.session.get('counter', 0) + 1
45 cherrypy.session['counter'] = counter
46 return str(counter)
47 testStr.exposed = True
48
49 def setsessiontype(self, newtype):
50 self.__class__._cp_config.update({'tools.sessions.storage_type': newtype})
51 if hasattr(cherrypy, "session"):
52 del cherrypy.session
53 setsessiontype.exposed = True
54 setsessiontype._cp_config = {'tools.sessions.on': False}
55
56 def index(self):
57 sess = cherrypy.session
58 c = sess.get('counter', 0) + 1
59 time.sleep(0.01)
60 sess['counter'] = c
61 return str(c)
62 index.exposed = True
63
64 def keyin(self, key):
65 return str(key in cherrypy.session)
66 keyin.exposed = True
67
68 def delete(self):
69 cherrypy.session.delete()
70 sessions.expire()
71 return "done"
72 delete.exposed = True
73
74 def delkey(self, key):
75 del cherrypy.session[key]
76 return "OK"
77 delkey.exposed = True
78
79 def blah(self):
80 return self._cp_config['tools.sessions.storage_type']
81 blah.exposed = True
82
83 def iredir(self):
84 raise cherrypy.InternalRedirect('/blah')
85 iredir.exposed = True
86
87 def restricted(self):
88 return cherrypy.request.method
89 restricted.exposed = True
90 restricted._cp_config = {'tools.allow.on': True,
91 'tools.allow.methods': ['GET']}
92
93 def regen(self):
94 cherrypy.tools.sessions.regenerate()
95 return "logged in"
96 regen.exposed = True
97
98 def length(self):
99 return str(len(cherrypy.session))
100 length.exposed = True
101
102 def session_cookie(self):
103 cherrypy.session.load()
104 return cherrypy.session.id
105 session_cookie.exposed = True
106 session_cookie._cp_config = {
107 'tools.sessions.path': '/session_cookie',
108 'tools.sessions.name': 'temp',
109 'tools.sessions.persistent': False}
110
111 cherrypy.tree.mount(Root())
112 cherrypy.config.update({'environment': 'test_suite'})
113
114
115 from cherrypy.test import helper
116
117 class SessionTest(helper.CPWebCase):
118
119 def tearDown(self):
120 # Clean up sessions.
121 for fname in os.listdir(localDir):
122 if fname.startswith(sessions.FileSession.SESSION_PREFIX):
123 os.unlink(os.path.join(localDir, fname))
124
125 def test_0_Session(self):
126 self.getPage('/setsessiontype/ram')
127 self.getPage('/clear')
128
129 self.getPage('/testStr')
130 self.assertBody('1')
131 cookie_parts = dict([p.strip().split('=')
132 for p in self.cookies[0][1].split(";")])
133 # Assert there is an 'expires' param
134 self.assertEqual(set(cookie_parts.keys()),
135 set(['session_id', 'expires', 'Path']))
136 self.getPage('/testGen', self.cookies)
137 self.assertBody('2')
138 self.getPage('/testStr', self.cookies)
139 self.assertBody('3')
140 self.getPage('/length', self.cookies)
141 self.assertBody('1')
142 self.getPage('/delkey?key=counter', self.cookies)
143 self.assertStatus(200)
144
145 self.getPage('/setsessiontype/file')
146 self.getPage('/testStr')
147 self.assertBody('1')
148 self.getPage('/testGen', self.cookies)
149 self.assertBody('2')
150 self.getPage('/testStr', self.cookies)
151 self.assertBody('3')
152 self.getPage('/delkey?key=counter', self.cookies)
153 self.assertStatus(200)
154
155 # Wait for the session.timeout (1 second)
156 time.sleep(2)
157 self.getPage('/')
158 self.assertBody('1')
159 self.getPage('/length', self.cookies)
160 self.assertBody('1')
161
162 # Test session __contains__
163 self.getPage('/keyin?key=counter', self.cookies)
164 self.assertBody("True")
165 cookieset1 = self.cookies
166
167 # Make a new session and test __len__ again
168 self.getPage('/')
169 self.getPage('/length', self.cookies)
170 self.assertBody('2')
171
172 # Test session delete
173 self.getPage('/delete', self.cookies)
174 self.assertBody("done")
175 self.getPage('/delete', cookieset1)
176 self.assertBody("done")
177 f = lambda: [x for x in os.listdir(localDir) if x.startswith('session-')]
178 self.assertEqual(f(), [])
179
180 # Wait for the cleanup thread to delete remaining session files
181 self.getPage('/')
182 f = lambda: [x for x in os.listdir(localDir) if x.startswith('session-')]
183 self.assertNotEqual(f(), [])
184 time.sleep(2)
185 self.assertEqual(f(), [])
186
187 def test_1_Ram_Concurrency(self):
188 self.getPage('/setsessiontype/ram')
189 self._test_Concurrency()
190
191 def test_2_File_Concurrency(self):
192 self.getPage('/setsessiontype/file')
193 self._test_Concurrency()
194
195 def _test_Concurrency(self):
196 client_thread_count = 5
197 request_count = 30
198
199 # Get initial cookie
200 self.getPage("/")
201 self.assertBody("1")
202 cookies = self.cookies
203
204 data_dict = {}
205 errors = []
206
207 def request(index):
208 if self.scheme == 'https':
209 c = httplib.HTTPSConnection('127.0.0.1:%s' % self.PORT)
210 else:
211 c = httplib.HTTPConnection('127.0.0.1:%s' % self.PORT)
212 for i in xrange(request_count):
213 c.putrequest('GET', '/')
214 for k, v in cookies:
215 c.putheader(k, v)
216 c.endheaders()
217 response = c.getresponse()
218 body = response.read()
219 if response.status != 200 or not body.isdigit():
220 errors.append((response.status, body))
221 else:
222 data_dict[index] = max(data_dict[index], int(body))
223 # Uncomment the following line to prove threads overlap.
224 ## print index,
225
226 # Start <request_count> requests from each of
227 # <client_thread_count> concurrent clients
228 ts = []
229 for c in xrange(client_thread_count):
230 data_dict[c] = 0
231 t = threading.Thread(target=request, args=(c,))
232 ts.append(t)
233 t.start()
234
235 for t in ts:
236 t.join()
237
238 hitcount = max(data_dict.values())
239 expected = 1 + (client_thread_count * request_count)
240
241 for e in errors:
242 print e
243 self.assertEqual(hitcount, expected)
244
245 def test_3_Redirect(self):
246 # Start a new session
247 self.getPage('/testStr')
248 self.getPage('/iredir', self.cookies)
249 self.assertBody("file")
250
251 def test_4_File_deletion(self):
252 # Start a new session
253 self.getPage('/testStr')
254 # Delete the session file manually and retry.
255 id = self.cookies[0][1].split(";", 1)[0].split("=", 1)[1]
256 path = os.path.join(localDir, "session-" + id)
257 os.unlink(path)
258 self.getPage('/testStr', self.cookies)
259
260 def test_5_Error_paths(self):
261 self.getPage('/unknown/page')
262 self.assertErrorPage(404, "The path '/unknown/page' was not found.")
263
264 # Note: this path is *not* the same as above. The above
265 # takes a normal route through the session code; this one
266 # skips the session code's before_handler and only calls
267 # before_finalize (save) and on_end (close). So the session
268 # code has to survive calling save/close without init.
269 self.getPage('/restricted', self.cookies, method='POST')
270 self.assertErrorPage(405, "Specified method is invalid for this server.")
271
272 def test_6_regenerate(self):
273 self.getPage('/testStr')
274 # grab the cookie ID
275 id1 = self.cookies[0][1].split(";", 1)[0].split("=", 1)[1]
276 self.getPage('/regen')
277 self.assertBody('logged in')
278 id2 = self.cookies[0][1].split(";", 1)[0].split("=", 1)[1]
279 self.assertNotEqual(id1, id2)
280
281 self.getPage('/testStr')
282 # grab the cookie ID
283 id1 = self.cookies[0][1].split(";", 1)[0].split("=", 1)[1]
284 self.getPage('/testStr',
285 headers=[('Cookie',
286 'session_id=maliciousid; '
287 'expires=Sat, 27 Oct 2017 04:18:28 GMT; Path=/;')])
288 id2 = self.cookies[0][1].split(";", 1)[0].split("=", 1)[1]
289 self.assertNotEqual(id1, id2)
290 self.assertNotEqual(id2, 'maliciousid')
291
292 def test_7_session_cookies(self):
293 self.getPage('/setsessiontype/ram')
294 self.getPage('/session_cookie')
295 # grab the cookie ID
296 cookie_parts = dict([p.strip().split('=') for p in self.cookies[0][1].split(";")])
297 # Assert there is no 'expires' param
298 self.assertEqual(set(cookie_parts.keys()), set(['temp', 'Path']))
299 id1 = cookie_parts['temp']
300 self.assertEqual(sessions.RamSession.cache.keys(), [id1])
301
302 # Send another request in the same "browser session".
303 self.getPage('/session_cookie', self.cookies)
304 cookie_parts = dict([p.strip().split('=') for p in self.cookies[0][1].split(";")])
305 # Assert there is no 'expires' param
306 self.assertEqual(set(cookie_parts.keys()), set(['temp', 'Path']))
307 self.assertBody(id1)
308 self.assertEqual(sessions.RamSession.cache.keys(), [id1])
309
310 # Wait 0.5 seconds to separate the two sessions
311 time.sleep(0.5)
312
313 # Simulate a browser close by just not sending the cookies
314 self.getPage('/session_cookie')
315 # grab the cookie ID
316 cookie_parts = dict([p.strip().split('=') for p in self.cookies[0][1].split(";")])
317 # Assert there is no 'expires' param
318 self.assertEqual(set(cookie_parts.keys()), set(['temp', 'Path']))
319 # Assert a new id has been generated...
320 id2 = cookie_parts['temp']
321 self.assertNotEqual(id1, id2)
322 self.assertEqual(set(sessions.RamSession.cache.keys()), set([id1, id2]))
323 # Wait for the session.timeout on the first session (1 second)
324 time.sleep(1)
325 cache = sessions.RamSession.cache.keys()
326 if cache != [id2]:
327 if set(cache) == set([id1, id2]):
328 self.fail("The first session did not time out.")
329 elif not cache:
330 self.fail("The second session may have been cleaned up "
331 "prematurely, but it may just be thread timing.")
332 else:
333 self.fail("Unknown session id in cache: %r", cache)
334 # Wait for the session.timeout on the second session (1 second)
335 time.sleep(1)
336 cache = sessions.RamSession.cache.keys()
337 if cache:
338 if cache == [id2]:
339 self.fail("The second session did not time out.")
340 else:
341 self.fail("Unknown session id in cache: %r", cache)
342
343
344 import socket
345 try:
346 import memcache
347
348 host, port = '127.0.0.1', 11211
349 for res in socket.getaddrinfo(host, port, socket.AF_UNSPEC,
350 socket.SOCK_STREAM):
351 af, socktype, proto, canonname, sa = res
352 s = None
353 try:
354 s = socket.socket(af, socktype, proto)
355 # See http://groups.google.com/group/cherrypy-users/
356 # browse_frm/thread/bbfe5eb39c904fe0
357 s.settimeout(1.0)
358 s.connect((host, port))
359 s.close()
360 except socket.error:
361 if s:
362 s.close()
363 raise
364 break
365 except (ImportError, socket.error):
366 class MemcachedSessionTest(helper.CPWebCase):
367
368 def test(self):
369 print "skipped",
370 else:
371 class MemcachedSessionTest(helper.CPWebCase):
372
373 def test_0_Session(self):
374 self.getPage('/setsessiontype/memcached')
375
376 self.getPage('/testStr')
377 self.assertBody('1')
378 self.getPage('/testGen', self.cookies)
379 self.assertBody('2')
380 self.getPage('/testStr', self.cookies)
381 self.assertBody('3')
382 self.getPage('/length', self.cookies)
383 self.assertErrorPage(500)
384 self.assertInBody("NotImplementedError")
385 self.getPage('/delkey?key=counter', self.cookies)
386 self.assertStatus(200)
387
388 # Wait for the session.timeout (1 second)
389 time.sleep(1.25)
390 self.getPage('/')
391 self.assertBody('1')
392
393 # Test session __contains__
394 self.getPage('/keyin?key=counter', self.cookies)
395 self.assertBody("True")
396
397 # Test session delete
398 self.getPage('/delete', self.cookies)
399 self.assertBody("done")
400
401 def test_1_Concurrency(self):
402 client_thread_count = 5
403 request_count = 30
404
405 # Get initial cookie
406 self.getPage("/")
407 self.assertBody("1")
408 cookies = self.cookies
409
410 data_dict = {}
411
412 def request(index):
413 for i in xrange(request_count):
414 self.getPage("/", cookies)
415 # Uncomment the following line to prove threads overlap.
416 ## print index,
417 if not self.body.isdigit():
418 self.fail(self.body)
419 data_dict[index] = v = int(self.body)
420
421 # Start <request_count> concurrent requests from
422 # each of <client_thread_count> clients
423 ts = []
424 for c in xrange(client_thread_count):
425 data_dict[c] = 0
426 t = threading.Thread(target=request, args=(c,))
427 ts.append(t)
428 t.start()
429
430 for t in ts:
431 t.join()
432
433 hitcount = max(data_dict.values())
434 expected = 1 + (client_thread_count * request_count)
435 self.assertEqual(hitcount, expected)
436
437 def test_3_Redirect(self):
438 # Start a new session
439 self.getPage('/testStr')
440 self.getPage('/iredir', self.cookies)
441 self.assertBody("memcached")
442
443 def test_5_Error_paths(self):
444 self.getPage('/unknown/page')
445 self.assertErrorPage(404, "The path '/unknown/page' was not found.")
446
447 # Note: this path is *not* the same as above. The above
448 # takes a normal route through the session code; this one
449 # skips the session code's before_handler and only calls
450 # before_finalize (save) and on_end (close). So the session
451 # code has to survive calling save/close without init.
452 self.getPage('/restricted', self.cookies, method='POST')
453 self.assertErrorPage(405, "Specified method is invalid for this server.")
454
455
456
457 if __name__ == "__main__":
458 setup_server()
459 helper.testmain()
11901190 # Close the connection.
11911191 return
11921192 except NoSSLError:
1193 # Unwrap our wfile
1194 req.wfile = CP_fileobject(self.socket, "wb", -1)
11951193 if req and not req.sent_headers:
1194 # Unwrap our wfile
1195 req.wfile = CP_fileobject(self.socket._sock, "wb", -1)
11961196 req.simple_response("400 Bad Request",
11971197 "The client sent a plain HTTP request, but "
11981198 "this server only speaks HTTPS on this port.")
1199 self.linger = True
11991200 except Exception, e:
12001201 if req and not req.sent_headers:
12011202 req.simple_response("500 Internal Server Error", format_exc())
12021203
1204 linger = False
1205
12031206 def close(self):
12041207 """Close the socket underlying this connection."""
12051208 self.rfile.close()
12061209
1207 # Python's socket module does NOT call close on the kernel socket
1208 # when you call socket.close(). We do so manually here because we
1209 # want this server to send a FIN TCP segment immediately. Note this
1210 # must be called *before* calling socket.close(), because the latter
1211 # drops its reference to the kernel socket.
1212 self.socket._sock.close()
1213
1214 self.socket.close()
1210 if not self.linger:
1211 # Python's socket module does NOT call close on the kernel socket
1212 # when you call socket.close(). We do so manually here because we
1213 # want this server to send a FIN TCP segment immediately. Note this
1214 # must be called *before* calling socket.close(), because the latter
1215 # drops its reference to the kernel socket.
1216 self.socket._sock.close()
1217 self.socket.close()
1218 else:
1219 # On the other hand, sometimes we want to hang around for a bit
1220 # to make sure the client has a chance to read our entire
1221 # response. Skipping the close() calls here delays the FIN
1222 # packet until the socket object is garbage-collected later.
1223 # Someday, perhaps, we'll do the full lingering_close that
1224 # Apache does, but not today.
1225 pass
12151226
12161227
12171228 def format_exc(limit=None):
14561467
14571468 protocol = "HTTP/1.1"
14581469 _bind_addr = "127.0.0.1"
1459 version = "CherryPy/3.1.1"
1470 version = "CherryPy/3.1.2"
14601471 ready = False
14611472 _interrupt = None
14621473
17081719 try:
17091720 host, port = sock.getsockname()[:2]
17101721 except socket.error, x:
1711 if x.args[1] != "Bad file descriptor":
1722 if x.args[0] not in socket_errors_to_ignore:
17121723 raise
17131724 else:
17141725 # Note that we're explicitly NOT using AI_PASSIVE,
1717 # arguments for the setup command
1818 ###############################################################################
1919 name = "CherryPy"
20 version = "3.1.1"
20 version = "3.1.2"
2121 desc = "Object-Oriented HTTP framework"
2222 long_desc = "CherryPy is a pythonic, object-oriented HTTP framework"
2323 classifiers=[
4141 "cherrypy.wsgiserver", "cherrypy.process",
4242 "cherrypy.scaffold",
4343 ]
44 download_url="http://download.cherrypy.org/cherrypy/3.1.1/"
44 download_url="http://download.cherrypy.org/cherrypy/3.1.2/"
4545 data_files=[
4646 ('cherrypy', ['cherrypy/cherryd',
4747 'cherrypy/favicon.ico',
7171 # end arguments for setup
7272 ###############################################################################
7373
74 def fix_data_files(data_files):
75 """
76 bdist_wininst seems to have a bug about where it installs data files.
77 I found a fix the django team used to work around the problem at
78 http://code.djangoproject.com/changeset/8313 . This function
79 re-implements that solution.
80 Also see http://mail.python.org/pipermail/distutils-sig/2004-August/004134.html
81 for more info.
82 """
83 def fix_dest_path(path):
84 return '\\PURELIB\\%(path)s' % vars()
85
86 if not 'bdist_wininst' in sys.argv: return
87
88 data_files[:] = [
89 (fix_dest_path(path), files)
90 for path, files in data_files]
91 fix_data_files(data_files)
7492
7593 def main():
7694 if sys.version < required_python_version:
95113 packages=packages,
96114 download_url=download_url,
97115 data_files=data_files,
116 scripts=[os.path.join("cherrypy", "cherryd")],
98117 )
99118
100119