Changeset 652
- Timestamp:
- 09/14/05 22:30:18
- Files:
-
- branches/mikerobi-experimental/cherrypy/_cpcgifs.py (modified) (1 diff)
- branches/mikerobi-experimental/cherrypy/_cperror.py (modified) (4 diffs)
- branches/mikerobi-experimental/cherrypy/_cphttptools.py (modified) (3 diffs)
- branches/mikerobi-experimental/cherrypy/_cputil.py (modified) (2 diffs)
- branches/mikerobi-experimental/cherrypy/_cpwsgiserver.py (modified) (1 diff)
- branches/mikerobi-experimental/cherrypy/lib/filter/sessionfilter.py (modified) (29 diffs)
- branches/mikerobi-experimental/cherrypy/test/test.py (modified) (1 diff)
- branches/mikerobi-experimental/cherrypy/test/test_core.py (modified) (2 diffs)
- branches/mikerobi-experimental/cherrypy/test/webtest.py (modified) (1 diff)
- branches/mikerobi-experimental/cherrypy/tutorial/custom_error.html (copied) (copied from trunk/cherrypy/tutorial/custom_error.html)
- branches/mikerobi-experimental/cherrypy/tutorial/tut10_http_errors.py (modified) (4 diffs)
- branches/mikerobi-experimental/cherrypy/tutorial/tutorial.conf (modified) (1 diff)
- branches/mikerobi-experimental/docs/book/xml/apireference.xml (modified) (1 diff)
- branches/mikerobi-experimental/docs/book/xml/appdeveloperreference.xml (modified) (1 diff)
- branches/mikerobi-experimental/docs/book/xml/errorhandling.xml (copied) (copied from trunk/docs/book/xml/errorhandling.xml)
- branches/mikerobi-experimental/setup.py (modified) (2 diffs)
Legend:
- Unmodified
- Added
- Removed
- Modified
- Copied
- Moved
branches/mikerobi-experimental/cherrypy/_cpcgifs.py
r634 r652 9 9 except ValueError, ex: 10 10 if str(ex) == 'Maximum content length exceeded': 11 print 50*'____\n'12 11 raise cherrypy.HTTPError(status=413) 13 12 else: branches/mikerobi-experimental/cherrypy/_cperror.py
r637 r652 28 28 29 29 import urllib 30 import os 30 31 31 32 … … 174 175 175 176 class HTTPError(Error): 176 """ Exception raised when the client has made an error in its request.177 """ Exception used to return an HTTP error code to the client. 177 178 This exception will automatically set the response status and body. 178 179 179 A custom body can be pased to the init method in place of the standard error page. 180 A custom body can be pased to the init method in place of the 181 standard error page. 180 182 """ 181 183 … … 184 186 if status < 400 or status > 599: 185 187 raise ValueError("status must be between 400 and 599.") 186 187 # these 4 lines might dissapear 188 import cherrypy 189 self.statusString = cherrypy._cputil.getErrorStatusAndPage(status)[0] 190 cherrypy.response.status = self.statusString 191 192 if body is _missing: 193 # because the init method is called before the exception is raised 194 # it is impossible to embed the traceback in the error page at this point. 195 # We use a generator so that the error page is generated at a later point ( 196 # after the exception is raised). 197 cherrypy.response.body = self.pageGenerator() 198 else: 199 cherrypy.response.body = body 188 189 self.body = body 190 191 def set_response(self): 192 import cherrypy 193 194 # we now now have access to the traceback 195 statusString, defaultBody = cherrypy._cputil.getErrorStatusAndPage(self.status) 196 197 if self.body is _missing: 198 self.body = defaultBody 199 # try to look up a custom error page in the config map 200 # if there is no error page then use the pageGenerator 201 202 # The page generator is used because the init method is called 203 # before the exception is raised. It is impossible to embed the 204 # traceback in the error page at this piont so we use the generator 205 # to render the error page at a later point 206 207 import cherrypy 208 # try and read the page from a file 209 # we use the default if the page can't be read 210 try: 211 errorPageFile = cherrypy.config.get('errorPage.%s' % status, '') 212 self.body = file(errorPageFile, 'r') 213 except: 214 # we have alread set the body 215 pass 216 217 cherrypy.response.status = statusString 218 cherrypy.response.body = self.body 200 219 201 220 def __str__(self): 202 return self.statusString 203 204 def pageGenerator(self): 205 import cherrypy 206 yield cherrypy._cputil.getErrorStatusAndPage(self.status)[1] 221 import cherrypy 222 return cherrypy._cputil.getErrorStatusAndPage(self.status)[0] 207 223 208 224 class NotFound(HTTPError): … … 212 228 self.args = (path,) 213 229 HTTPError.__init__(self, 404) 230 231 def __str__(self): 232 return self.args[0] branches/mikerobi-experimental/cherrypy/_cphttptools.py
r634 r652 45 45 46 46 import cherrypy 47 from cherrypy import _cputil, _cpcgifs, _cpwsgiserver 47 from cherrypy import _cputil, _cpcgifs, _cpwsgiserver, _cperror 48 48 from cherrypy.lib import cptools 49 49 … … 286 286 applyFilters('beforeFinalize') 287 287 finalize() 288 except cherrypy.HTTPError, inst: 289 # This includes NotFound 290 inst.set_response() 291 applyFilters('beforeFinalize') 292 finalize() 293 288 294 finally: 289 295 applyFilters('onEndResource') … … 422 428 try: 423 429 applyFilters('beforeErrorResponse') 424 430 425 431 # _cpOnError will probably change cherrypy.response.body. 426 # Theymay also change the headerMap, etc.432 # It may also change the headerMap, etc. 427 433 _cputil.getSpecialAttribute('_cpOnError')() 428 434 branches/mikerobi-experimental/cherrypy/_cputil.py
r634 r652 210 210 return "".join(traceback.format_exception(*exc)) 211 211 212 def getErrorStatusAndPage(status, traceback = None):213 statusString, message = _HTTPResponses[status]214 statusString = '%d %s' % (status, statusString)215 216 if traceback is None:217 traceback = ''218 # get the traceback from formatExc219 developmentMode = (cherrypy.config.get('server.environment') == 'development')220 if cherrypy.config.get('server.showTracebacks') or developmentMode:221 traceback = formatExc()222 223 page = _HTTPErrorTemplate(statusString, message, traceback, cherrypy.__version__)224 225 return statusString, page226 227 """formatExc(exc=None) -> exc (or sys.exc_info), formatted."""228 if exc is None:229 exc = sys.exc_info()230 231 if exc == (None, None, None):232 return ""233 234 return "".join(traceback.format_exception(*exc))235 236 212 def _cpOnError(): 237 213 """ Default _cpOnError method """ … … 243 219 response = cherrypy.response 244 220 245 if isinstance(sys.exc_info()[1], cherrypy.HTTPError): 246 # status, body already set 247 pass 248 else: 249 response.status, response.body = getErrorStatusAndPage(500) 221 response.status, response.body = getErrorStatusAndPage(500) 250 222 251 223 if cherrypy.response.headerMap.has_key('Content-Encoding'): branches/mikerobi-experimental/cherrypy/_cpwsgiserver.py
r634 r652 83 83 self.rfile.close() 84 84 85 def __iter__(self): 86 return self.rfile 87 88 def next(self): 89 data = self.rfile.next() 90 self.bytes_read += len(data) 91 self._check_length() 92 ## Normally the next method must raise StopIteration when it 93 ## fails but CP expects MaxSizeExceeded 94 ## try: 95 ## self._check_length() 96 ## except: 97 ## raise StopIteration() 98 return data 85 99 86 100 class HTTPRequest(object): branches/mikerobi-experimental/cherrypy/lib/filter/sessionfilter.py
r634 r652 53 53 54 54 import datetime 55 import sha56 55 import os 57 56 import pickle 58 57 import random 58 import sha 59 59 import StringIO 60 60 import time … … 64 64 import basefilter 65 65 66 66 67 class EmptyClass: 67 68 """ An empty class """ 68 69 pass 69 70 71 70 72 class SessionDeadlockError(Exception): 71 """ Happens when a session can't acquire a lock after a 72 certain time 73 """ 73 """ The session could not acquire a lock after a certain time """ 74 74 pass 75 75 76 76 77 class SessionNotEnabledError(Exception): 77 """ Happens if user forgot to set sessionFilter.on to True """78 """ User forgot to set sessionFilter.on to True """ 78 79 pass 79 80 81 80 82 class SessionFilter(basefilter.BaseFilter): 83 81 84 def beforeRequestBody(self): 82 85 # We have to dynamically import cherrypy because Python can't handle … … 84 87 global cherrypy 85 88 import cherrypy 89 conf = cherrypy.config.get 90 86 91 cherrypy.threadData._session = EmptyClass() 87 92 sess = cherrypy.threadData._session … … 89 94 # Dont enable session if sessionFilter is off or if this is a 90 95 # request for static data 91 if ( not cherrypy.config.get('sessionFilter.on', False)) or \92 cherrypy.config.get('staticFilter.on', False):96 if ((not conf('sessionFilter.on', False)) 97 or conf('staticFilter.on', False)): 93 98 sess.sessionStorage = None 94 99 return 95 100 96 101 sess.locked = False # Not locked by default 97 102 98 103 # Read config options 99 sess.sessionTimeout = \ 100 cherrypy.config.get('sessionFilter.timeout', 60) 101 102 sess.sessionLocking = \ 103 cherrypy.config.get('sessionFilter.locking', 'implicit') 104 105 sess.onCreateSession = \ 106 cherrypy.config.get('sessionFilter.onCreateSession', 107 lambda data: None) 108 109 sess.onDeleteSession = \ 110 cherrypy.config.get('sessionFilter.onDeleteSession', 111 lambda data: None) 112 113 cleanUpDelay = \ 114 cherrypy.config.get('sessionFilter.cleanUpDelay', 5) 115 116 cookieName = \ 117 cherrypy.config.get('sessionFilter.cookieName', 'sessionID') 118 119 sess.deadlockTimeout = \ 120 cherrypy.config.get('sessionFilter.deadlockTimeout', 30) 121 122 storage = cherrypy.config.get('sessionFilter.storageType', 'Ram') 104 sess.sessionTimeout = conf('sessionFilter.timeout', 60) 105 sess.sessionLocking = conf('sessionFilter.locking', 'implicit') 106 sess.onCreateSession = conf('sessionFilter.onCreateSession', 107 lambda data: None) 108 sess.onDeleteSession = conf('sessionFilter.onDeleteSession', 109 lambda data: None) 110 111 cleanUpDelay = conf('sessionFilter.cleanUpDelay', 5) 112 cleanUpDelay = datetime.timedelta(seconds = cleanUpDelay * 60) 113 114 cookieName = conf('sessionFilter.cookieName', 'sessionID') 115 sess.deadlockTimeout = conf('sessionFilter.deadlockTimeout', 30) 116 117 storage = conf('sessionFilter.storageType', 'Ram') 123 118 storage = storage[0].upper() + storage[1:] 124 119 125 120 # People can set their own custom class 126 121 # through sessionFilter.storageClass 127 sess.sessionStorage = \ 128 cherrypy.config.get('sessionFilter.storageClass', None) 122 sess.sessionStorage = conf('sessionFilter.storageClass', None) 129 123 if sess.sessionStorage is None: 130 124 sess.sessionStorage = globals()[storage + 'Storage']() 131 125 132 126 # Check if we need to clean up old sessions 133 if cherrypy._sessionLastCleanUpTime + \ 134 datetime.timedelta(seconds = cleanUpDelay * 60) < now: 127 if cherrypy._sessionLastCleanUpTime + cleanUpDelay < now: 135 128 sess.sessionStorage.cleanUp() 136 129 137 130 # Check if request came with a session ID 138 131 if cookieName in cherrypy.request.simpleCookie: 139 132 # It did: we try to load the session data 140 133 sess.sessionID = cherrypy.request.simpleCookie[cookieName].value 134 141 135 # If using implicit locking, acquire lock 142 136 if sess.sessionLocking == 'implicit': 143 137 sess.sessionData = {'_id': sess.sessionID} 144 138 sess.sessionStorage.acquireLock() 139 145 140 data = sess.sessionStorage.load(sess.sessionID) 146 141 # data is either None or a tuple (sessionData, expirationTime) 147 142 if data is None or data[1] < now: 148 # Expired session: flush session data (but keep the same149 # sessionID)143 # Expired session: 144 # flush session data (but keep the same sessionID) 150 145 sess.sessionData = {'_id': sess.sessionID} 151 146 else: … … 157 152 sess.onCreateSession(sess.sessionData) 158 153 # Set response cookie 159 c herrypy.response.simpleCookie[cookieName] = sess.sessionID160 c herrypy.response.simpleCookie[cookieName]['path'] = '/'161 c herrypy.response.simpleCookie[cookieName]['max-age'] = \162 sess.sessionTimeout * 60163 c herrypy.response.simpleCookie[cookieName]['version'] = 1164 154 cookie = cherrypy.response.simpleCookie 155 cookie[cookieName] = sess.sessionID 156 cookie[cookieName]['path'] = '/' 157 cookie[cookieName]['max-age'] = sess.sessionTimeout * 60 158 cookie[cookieName]['version'] = 1 159 165 160 def beforeFinalize(self): 166 def returnBodyAndSaveData(body, sess):161 def saveData(body, sess): 167 162 try: 168 163 # If the body is a generator, we have to save the data … … 171 166 for line in body: 172 167 yield line 173 168 174 169 # Save session data 175 expirationTime = datetime.datetime.now() + \176 datetime.timedelta(seconds = sess.sessionTimeout * 60)177 sess.sessionStorage.save( 178 sess.sessionID, sess.sessionData,expirationTime)170 t = datetime.timedelta(seconds = sess.sessionTimeout * 60) 171 expirationTime = datetime.datetime.now() + t 172 sess.sessionStorage.save(sess.sessionID, sess.sessionData, 173 expirationTime) 179 174 if sess.locked: 180 175 # Always release the lock if the user didn't release it 181 176 sess.sessionStorage.releaseLock() 182 177 183 178 # If the body is not a generator, we save the data 184 179 # before the body is returned … … 191 186 raise 192 187 self._clean(sess) 193 188 194 189 sess = cherrypy.threadData._session 195 190 if not sess.sessionStorage: 196 191 # Sessions are not enabled: do nothing 197 192 return 198 193 199 194 # Make a wrapper around the body in order to save the session 200 195 # either before or after the body is returned 201 cherrypy.response.body = \ 202 returnBodyAndSaveData(cherrypy.response.body, sess) 203 196 cherrypy.response.body = saveData(cherrypy.response.body, sess) 197 204 198 def afterErrorResponse(self): 205 199 sess = cherrypy.threadData._session … … 208 202 return 209 203 self._clean(sess) 210 204 211 205 def _clean(self, sess): 212 206 if getattr(sess, 'locked', None): … … 221 215 class RamStorage: 222 216 """ Implementation of the RAM backend for sessions """ 217 223 218 def load(self, id): 224 219 return cherrypy._sessionDataHolder.get(id) 220 225 221 def save(self, id, data, expirationTime): 226 222 cherrypy._sessionDataHolder[id] = (data, expirationTime) 223 227 224 def acquireLock(self): 228 225 sess = cherrypy.threadData._session … … 239 236 raise SessionDeadlockError() 240 237 sess.locked = True 238 241 239 def releaseLock(self): 242 240 sess = cherrypy.threadData._session … … 244 242 cherrypy._sessionLockDict[id].release() 245 243 sess.locked = False 244 246 245 def cleanUp(self): 247 246 sess = cherrypy.threadData._session … … 255 254 del cherrypy._sessionDataHolder[id] 256 255 256 257 257 class FileStorage: 258 258 """ Implementation of the File backend for sessions """ 259 259 260 SESSION_PREFIX = 'session-' 260 261 LOCK_SUFFIX = '.lock' 262 261 263 def load(self, id): 262 264 filePath = self._getFilePath(id) … … 268 270 except IOError: 269 271 return None 272 270 273 def save(self, id, data, expirationTime): 271 274 filePath = self._getFilePath(id) … … 273 276 pickle.dump((data, expirationTime), f) 274 277 f.close() 278 275 279 def acquireLock(self): 276 280 sess = cherrypy.threadData._session … … 279 283 self._lockFile(lockFilePath) 280 284 sess.locked = True 281 285 282 286 def releaseLock(self): 283 287 sess = cherrypy.threadData._session … … 286 290 self._unlockFile(lockFilePath) 287 291 sess.locked = False 288 292 289 293 def cleanUp(self): 290 294 sess = cherrypy.threadData._session … … 294 298 # and lock files 295 299 for fname in os.listdir(storagePath): 296 if fname.startswith(self.SESSION_PREFIX) and \297 (not fname.endswith(self.LOCK_SUFFIX)):300 if (fname.startswith(self.SESSION_PREFIX) 301 and not fname.endswith(self.LOCK_SUFFIX)): 298 302 # We have a session file: lock it, load it and check 299 303 # if it's expired … … 303 307 try: 304 308 f = open(filePath, "rb") 305 (data, expirationTime)= pickle.load(f)309 data, expirationTime = pickle.load(f) 306 310 f.close() 307 311 if expirationTime < now: … … 314 318 pass 315 319 self._unlockFile(lockFilePath) 316 320 317 321 def _getFilePath(self, id): 318 322 storagePath = cherrypy.config.get('sessionFilter.storagePath') … … 320 324 filePath = os.path.join(storagePath, fileName) 321 325 return filePath 322 326 323 327 def _lockFile(self, path): 324 328 sess = cherrypy.threadData._session … … 330 334 if time.time() - startTime > sess.deadlockTimeout: 331 335 raise SessionDeadlockError() 336 time.sleep(0.5) 332 337 else: 333 338 os.close(lockfd) 334 339 break 340 335 341 def _unlockFile(self, path): 336 342 os.unlink(path) 343 337 344 338 345 class PostgreSQLStorage: … … 346 353 ) 347 354 """ 355 348 356 def __init__(self): 349 357 self.db = cherrypy.config.get('sessionFilter.getDB')() 350 358 self.cursor = self.db.cursor() 359 351 360 def __del__(self): 352 361 if self.cursor: 353 362 self.cursor.close() 354 363 self.db.commit() 364 355 365 def load(self, id): 356 366 # Select session data from table … … 366 376 data = pickle.load(f) 367 377 return (data, expirationTime) 378 368 379 def save(self, id, data, expirationTime): 369 380 # Try to delete session if it was already there … … 378 389 'insert into session (id, data, expiration_time) values (%s, %s, %s)', 379 390 (id, f.getvalue(), expirationTime)) 380 391 381 392 def acquireLock(self): 382 393 # We use the "for update" clause to lock the row … … 384 395 'select id from session where id=%s for update', 385 396 (cherrypy.session['_id'],)) 386 397 387 398 def releaseLock(self): 388 399 # We just close the cursor and that will remove the lock … … 390 401 self.cursor.close() 391 402 self.cursor = None 403 392 404 def cleanUp(self): 393 405 sess = cherrypy.threadData._session … … 403 415 (now,)) 404 416 417 405 418 def generateSessionID(): 406 """ Return a new sessionID """ 407 return sha.new('%s' % random.random()).hexdigest() 419 """ Return a new sessionID """ 420 return sha.new('%s' % random.random()).hexdigest() 421 408 422 409 423 # Users access sessions through cherrypy.session, but we want this … … 412 426 # cherrypy.threadData._session.sessionData 413 427 class SessionWrapper: 428 414 429 def __getattr__(self, name): 415 430 sess = cherrypy.threadData._session … … 423 438 return sess.sessionStorage.releaseLock 424 439 return getattr(sess.sessionData, name) 440 branches/mikerobi-experimental/cherrypy/test/test.py
r615 r652 124 124 print """CherryPy Test Program 125 125 Usage: 126 test.py --server --1.1 --cover --basedir=path --profile --test 126 test.py --servers* --1.1 --cover --basedir=path --profile --tests** 127 127 128 """ 128 print ' servers:'129 print ' * servers:' 129 130 s = [(val, name) for name, val in self.available_servers.iteritems()] 130 131 s.sort() 131 132 for val, name in s: 132 133 if name == self.default_server: 133 print ' ',name, '(default)'134 print ' --' + name, '(default)' 134 135 else: 135 print ' ',name136 137 print """ all (runs all servers in order)138 139 1.1: use HTTP/1.1 servers instead of default HTTP/1.0140 141 cover: turn on code-coverage tool142 basedir=path: display coverage stats for some path other than cherrypy.143 144 profile: turn on profiling tool136 print ' --' + name 137 138 print """ --all (runs all servers in order) 139 140 --1.1: use HTTP/1.1 servers instead of default HTTP/1.0 141 142 --cover: turn on code-coverage tool 143 --basedir=path: display coverage stats for some path other than cherrypy. 144 145 --profile: turn on profiling tool 145 146 """ 146 147 147 print ' tests:'148 print ' ** tests:' 148 149 for name in self.available_tests: 149 print ' ',name150 print ' --' + name 150 151 151 152 def start_coverage(self): branches/mikerobi-experimental/cherrypy/test/test_core.py
r634 r652 639 639 self.assertHeader('Set-Cookie', 'Last=Piranha;') 640 640 641 def __testMaxRequestSize(self):641 def testMaxRequestSize(self): 642 642 self.getPage("/maxrequestsize/index") 643 643 self.assertBody("OK") 644 cherrypy.config.update({'server.maxRequestHeaderSize': 10}) 645 self.getPage("/maxrequestsize/index") 646 self.assertStatus("413 Request Entity Too Large") 647 self.assertBody("Request Entity Too Large") 648 cherrypy.config.update({'server.maxRequestHeaderSize': 0}) 649 644 645 if cherrypy._httpserver.__class__.__name__ == "WSGIServer": 646 cherrypy.config.update({'server.maxRequestHeaderSize': 10}) 647 self.getPage("/maxrequestsize/index") 648 self.assertStatus("413 Request Entity Too Large") 649 self.assertBody("Request Entity Too Large") 650 cherrypy.config.update({'server.maxRequestHeaderSize': 0}) 651 650 652 # Test upload 651 653 h = [("Content-type", "multipart/form-data; boundary=x"), … … 660 662 self.getPage('/maxrequestsize/upload', h, "POST", b) 661 663 self.assertBody('Size: 5') 662 cherrypy.config.update({ 663 '/maxrequestsize': {'server.maxRequestBodySize': 3}}) 664 self.getPage('/maxrequestsize/upload', h, "POST", b) 665 self.assertStatus("413 Request Entity Too Large") 666 self.assertInBody("Request Entity Too Large") 664 665 if cherrypy._httpserver.__class__.__name__ == "WSGIServer": 666 cherrypy.config.update({ 667 '/maxrequestsize': {'server.maxRequestBodySize': 3}}) 668 self.getPage('/maxrequestsize/upload', h, "POST", b) 669 self.assertStatus("413 Request Entity Too Large") 670 self.assertInBody("Request Entity Too Large") 667 671 668 672 if __name__ == '__main__': branches/mikerobi-experimental/cherrypy/test/webtest.py
r637 r652 286 286 msg = 'No match for %s in body' % `pattern` 287 287 self._handlewebError(msg) 288 288 289 289 290 290 291 def cleanHeaders(headers, method, body, host, port): branches/mikerobi-experimental/cherrypy/tutorial/tut10_http_errors.py
r634 r652 3 3 import cherrypy 4 4 5 # we want to customize 403 errors 6 customErrors = { 7 'errorPage.403' : "custom_error.html" 8 } 9 10 cherrypy.config.update({'/' : customErrors}) 5 11 6 12 class HTTPErrorDemo(object): … … 16 22 return """ 17 23 <html><body> 18 < a href="toggleTracebacks">Toggle tracebacks %s</a><br/><br/>24 <h2><a href="toggleTracebacks">Toggle tracebacks %s</a><br/><br/></h2> 19 25 <a href="/doesNotExist">Click me i'm a broken link!</a> 20 <br/> 21 <a href="/ customMessage">Use a custom error message</a>26 <br/><br/> 27 <a href="/error?code=403">Use a custom an error page from a file.</a> 22 28 <br/><br/> 23 29 These errors are explicitly raised by the application. … … 26 32 <a href="/error?code=402">402</a> 27 33 <a href="/error?code=500">500</a> 34 <br/><br/> 35 <a href="/bodyArg">You can also set the response body when you raise an error</a> 28 36 </body></html> 29 37 """ % trace … … 45 53 error.exposed = True 46 54 47 def customMessage(self): 48 raise cherrypy.HTTPError(500, "Plain text message") 49 customMessage.exposed = True 55 def bodyArg(self): 56 message = """ If you construct a HTTPError wiht body argument, the body argument 57 will overide any default or custom error page. 58 """ 59 raise cherrypy.HTTPError(403, body = message) 60 bodyArg.exposed = True 50 61 51 62 cherrypy.root = HTTPErrorDemo() branches/mikerobi-experimental/cherrypy/tutorial/tutorial.conf
r629 r652 2 2 server.socketPort = 8080 3 3 server.threadPool = 10 4 #server.environment = "production"5 # server.showTracebacks = True4 server.environment = "production" 5 # server.showTracebacks = True 6 6 # server.logToScreen = False branches/mikerobi-experimental/docs/book/xml/apireference.xml
r615 r652 299 299 <listitem> 300 300 <section> 301 <title>cherrypy.HTTP StatusError</title>301 <title>cherrypy.HTTPError</title> 302 302 <para> 303 303 This exception can be used to automatically send a response branches/mikerobi-experimental/docs/book/xml/appdeveloperreference.xml
r637 r652 17 17 <xi:include href="staticcontenthandling.xml" /> 18 18 <xi:include href="fileuploadbehavior.xml" /> 19 <xi:include href="errorhandling.xml" /> 19 20 </section> branches/mikerobi-experimental/setup.py
r615 r652 42 42 'cherrypy/tutorial/tutorial.conf', 43 43 'cherrypy/tutorial/README.txt', 44 'cherrypy/tutorial/ReturnVsYield.pdf', 45 'cherrypy/tutorial/custom_error.html', 44 46 ] 45 47 ), … … 81 83 if __name__ == "__main__": 82 84 main() 85

