Changeset 229
- Timestamp:
- 06/01/05 16:59:18
- Files:
-
- trunk/CHANGELOG.txt (modified) (1 diff)
- trunk/CHERRYPYTEAM.txt (modified) (1 diff)
- trunk/cherrypy/_cpconfig.py (modified) (2 diffs)
- trunk/cherrypy/_cpdefaults.py (modified) (2 diffs)
- trunk/cherrypy/_cphttpserver.py (modified) (13 diffs)
- trunk/cherrypy/_cphttptools.py (modified) (9 diffs)
- trunk/cherrypy/_cpserver.py (modified) (2 diffs)
- trunk/cherrypy/_cpthreadinglocal.py (modified) (1 diff)
- trunk/cherrypy/_cputil.py (modified) (2 diffs)
- trunk/cherrypy/_cpwsgi.py (copied) (copied from branches/ticket-151/cherrypy/_cpwsgi.py)
- trunk/cherrypy/lib/autoreload.py (copied) (copied from branches/ticket-151/cherrypy/lib/autoreload.py)
- trunk/cherrypy/lib/csauthenticate.py (modified) (2 diffs)
- trunk/cherrypy/lib/defaultformmask.py (modified) (previous)
- trunk/cherrypy/lib/filter/basefilter.py (modified) (1 diff)
- trunk/cherrypy/lib/filter/baseurlfilter.py (modified) (2 diffs)
- trunk/cherrypy/lib/filter/cachefilter.py (modified) (6 diffs)
- trunk/cherrypy/lib/filter/decodingfilter.py (modified) (3 diffs)
- trunk/cherrypy/lib/filter/encodingfilter.py (modified) (2 diffs)
- trunk/cherrypy/lib/filter/gzipfilter.py (modified) (4 diffs)
- trunk/cherrypy/lib/filter/logdebuginfofilter.py (modified) (3 diffs)
- trunk/cherrypy/lib/filter/sessionfilter.py (copied) (copied from branches/ticket-151/cherrypy/lib/filter/sessionfilter.py)
- trunk/cherrypy/lib/filter/staticfilter.py (modified) (2 diffs)
- trunk/cherrypy/lib/filter/tidyfilter.py (modified) (9 diffs)
- trunk/cherrypy/lib/filter/virtualhostfilter.py (modified) (2 diffs)
- trunk/cherrypy/lib/filter/xmlrpcfilter.py (modified) (3 diffs)
- trunk/cherrypy/lib/form.py (modified) (previous)
- trunk/cherrypy/lib/httptools.py (modified) (1 diff)
- trunk/cherrypy/test/helper.py (modified) (6 diffs)
- trunk/cherrypy/test/test.py (modified) (4 diffs)
- trunk/cherrypy/test/testCore.py (copied) (copied from branches/ticket-151/cherrypy/test/testCore.py)
- trunk/cherrypy/test/testDecodingEncodingFilter.py (modified) (1 diff)
- trunk/cherrypy/test/testObjectMapping.py (modified) (2 diffs)
- trunk/cherrypy/test/testStaticFilter.py (modified) (1 diff)
- trunk/cherrypy/test/testwsgiapp.py (modified) (1 diff)
- trunk/cherrypy/tutorial/03_get_and_post.py (modified) (1 diff)
- trunk/cherrypy/tutorial/tutorial.conf (modified) (1 diff)
- trunk/cherrypy/wsgiapp.py (deleted)
Legend:
- Unmodified
- Added
- Removed
- Modified
- Copied
- Moved
trunk/CHANGELOG.txt
r198 r229 1 2005-06-01: 2 * Core (_cphttptools) was completely rewritten to use iterators throughout. (fumanchu) 3 * HTTP server has been decoupled from _cpserver. (fumanchu) 4 * New WSGI interface to the builtin http server; WSGI is now the default. (fumanchu) 5 * Moved session handling into a filter. (fumanchu) 6 * New testCore.py for testing basic request and error handling. (fumanchu) 7 * autoreload disabled if cpg.server.start is called with initOnly=True. (fumanchu) 8 * BACKWARD INCOMPATIBILITY: filters changed, see http://www.cherrypy.org/wiki/WhatsNewIn21#filter_methods. (fumanchu) 9 10 2005-05-20: 1 11 * BACKWARD INCOMPATIBILITY: New config system, see http://www.cherrypy.org/wiki/ConfigSystem21 (Remi) 2 12 * Fixed small bug in httptools.redirect (Remi) trunk/CHERRYPYTEAM.txt
r214 r229 1 See http://trac.cherrypy.org/ wiki/CherryPyTeam1 See http://trac.cherrypy.org/cgi-bin/trac.cgi/wiki/CherryPyTeam trunk/cherrypy/_cpconfig.py
r218 r229 156 156 value = _cputil.unrepr(value) 157 157 except cperror.WrongUnreprValue, s: 158 raise cperror.WrongConfigValue, "section: %s, option: %s, value: %s" % ( 159 repr(section), repr(option), repr(value)) 158 msg = ("section: %s, option: %s, value: %s" % 159 (repr(section), repr(option), repr(value))) 160 raise cperror.WrongConfigValue, msg 160 161 configMap[section][option] = value 161 162 … … 163 164 _cpLogMessage = _cputil.getSpecialFunction('_cpLogMessage') 164 165 _cpLogMessage("Server parameters:", 'CONFIG') 165 _cpLogMessage(" server.environment: %s" % cpg.config.get('server.env ironment'), 'CONFIG')166 _cpLogMessage(" server.environment: %s" % cpg.config.get('server.env'), 'CONFIG') 166 167 _cpLogMessage(" server.logToScreen: %s" % cpg.config.get('server.logToScreen'), 'CONFIG') 167 168 _cpLogMessage(" server.logFile: %s" % cpg.config.get('server.logFile'), 'CONFIG') trunk/cherrypy/_cpdefaults.py
r213 r229 48 48 level = "ERROR" 49 49 else: 50 le bel = "UNKNOWN"50 level = "UNKNOWN" 51 51 try: 52 logToScreen = int(cpg.config.get('server.logToScreen'))52 logToScreen = cpg.config.get('server.logToScreen') 53 53 except: 54 54 logToScreen = True … … 170 170 from cherrypy.lib.filter import baseurlfilter, cachefilter, \ 171 171 decodingfilter, encodingfilter, gzipfilter, logdebuginfofilter, \ 172 staticfilter, tidyfilter, virtualhostfilter, xmlrpcfilter 172 sessionfilter, staticfilter, tidyfilter, virtualhostfilter, \ 173 xmlrpcfilter 174 175 # These are in order for a reason! 173 176 _cpDefaultInputFilterList = [ 174 cachefilter.Cache InputFilter(),175 logdebuginfofilter.LogDebugInfo InputFilter(),177 cachefilter.CacheFilter(), 178 logdebuginfofilter.LogDebugInfoFilter(), 176 179 virtualhostfilter.VirtualHostFilter(), 177 180 baseurlfilter.BaseUrlFilter(), 178 181 decodingfilter.DecodingFilter(), 182 sessionfilter.SessionFilter(), 179 183 staticfilter.StaticFilter(), 180 xmlrpcfilter.XmlRpcInputFilter(), 184 tidyfilter.TidyFilter(), 185 xmlrpcfilter.XmlRpcFilter(), 181 186 ] 182 187 _cpDefaultOutputFilterList = [ 183 xmlrpcfilter.XmlRpc OutputFilter(),188 xmlrpcfilter.XmlRpcFilter(), 184 189 encodingfilter.EncodingFilter(), 185 190 tidyfilter.TidyFilter(), 186 logdebuginfofilter.LogDebugInfo OutputFilter(),191 logdebuginfofilter.LogDebugInfoFilter(), 187 192 gzipfilter.GzipFilter(), 188 cachefilter.CacheOutputFilter(), 193 sessionfilter.SessionFilter(), 194 cachefilter.CacheFilter(), 189 195 ] 190 trunk/cherrypy/_cphttpserver.py
r213 r229 27 27 """ 28 28 29 import cpg, sys, threading, SocketServer, _cphttptools 30 import BaseHTTPServer, socket, Queue, _cputil 31 from lib import autoreload 32 33 def stop(): 34 cpg._server.shutdown() 35 36 def start(): 37 if cpg.config.get("server.environment") == "development": 38 autoreload.main(_start) 39 else: 40 _start() 41 42 def _start(): 43 """ Prepare the HTTP server and then run it """ 44 45 # If sessions are stored in files and we 46 # use threading, we need a lock on the file 47 if (cpg.config.get('server.threadPool') > 1) and \ 48 cpg.config.get('session.storageType') == 'file': 49 cpg._sessionFileLock = threading.RLock() 50 51 52 if cpg.config.get('server.socketFile'): 53 # AF_UNIX socket 54 # TODO: Handle threading here 55 class MyCherryHTTPServer(CherryHTTPServer): address_family = socket.AF_UNIX 56 else: 57 # AF_INET socket 58 if cpg.config.get('server.threadPool') > 1: 59 MyCherryHTTPServer = PooledThreadServer 60 else: 61 MyCherryHTTPServer = CherryHTTPServer 62 63 MyCherryHTTPServer.request_queue_size = cpg.config.get('server.socketQueueSize') 64 65 # Set protocol_version 66 CherryHTTPRequestHandler.protocol_version = cpg.config.get('server.protocolVersion') 67 68 run_server(CherryHTTPRequestHandler, MyCherryHTTPServer, \ 69 (cpg.config.get('server.socketHost'), cpg.config.get('server.socketPort')), \ 70 cpg.config.get('server.socketFile')) 71 72 def run_server(HandlerClass, ServerClass, server_address, socketFile): 73 """Run the HTTP request handler class.""" 74 75 if cpg.config.get('server.socketFile'): 76 try: os.unlink(cpg.config.get('server.socketFile')) # So we can reuse the socket 77 except: pass 78 server_address = cpg.config.get('server.socketFile') 79 if cpg.config.get('server.threadPool') > 1: 80 myCherryHTTPServer = ServerClass(server_address, cpg.config.get('server.threadPool'), HandlerClass) 81 else: 82 myCherryHTTPServer = ServerClass(server_address, HandlerClass) 83 cpg._server = myCherryHTTPServer 84 if cpg.config.get('server.socketFile'): 85 try: os.chmod(socketFile, 0777) # So everyone can access the socket 86 except: pass 87 global _cpLogMessage 88 _cpLogMessage = _cputil.getSpecialFunction('_cpLogMessage') 89 90 servingWhat = "HTTP" 91 if cpg.config.get('server.socketPort'): 92 onWhat = ("socket: ('%s', %s)" % 93 (cpg.config.get('server.socketHost'), cpg.config.get('server.socketPort'))) 94 else: onWhat = "socket file: %s" % cpg.config.get('server.socketFile') 95 _cpLogMessage("Serving %s on %s" % (servingWhat, onWhat), 'HTTP') 96 97 try: 98 # Call the functions from cpg.server.onStartServerList 99 for func in cpg.server.onStartServerList: 100 func() 101 myCherryHTTPServer.serve_forever() 102 except KeyboardInterrupt: 103 _cpLogMessage("<Ctrl-C> hit: shutting down", "HTTP") 104 myCherryHTTPServer.server_close() 105 # Call the functions from cpg.server.onStartServerList 106 for func in cpg.server.onStopServerList: 107 func() 29 import threading, SocketServer, BaseHTTPServer, socket, Queue 30 import cpg, _cpserver, _cputil, _cphttptools 31 32 try: 33 import cStringIO as StringIO 34 except ImportError: 35 import StringIO 36 108 37 109 38 class CherryHTTPRequestHandler(BaseHTTPServer.BaseHTTPRequestHandler): 110 39 111 40 """CherryPy HTTP request handler with the following commands: 112 41 113 42 o GET 114 43 o HEAD 115 44 o POST 116 45 o HOTRELOAD 117 46 118 47 """ 119 48 120 49 def address_string(self): 121 50 """ Try to do a reverse DNS based on [server]reverseDNS in the config file """ … … 124 53 else: 125 54 return self.client_address[0] 126 55 56 def _headerlist(self): 57 list = [] 58 hit = 0 59 for line in self.headers.headers: 60 if line: 61 if line[0] in ' \t': 62 # Continuation line. Add to previous entry. 63 if list: 64 list[-1][1] += " " + line.lstrip() 65 else: 66 # New header. Add a new entry. We don't catch 67 # ValueError here because we trust rfc822.py. 68 name, value = line.split(":", 1) 69 list.append((name.strip(), value.strip())) 70 return list 71 72 def doMethod(self): 73 cpg.request.multithread = cpg.config.get("server.threadPool") > 1 74 cpg.request.multiprocess = False 75 r = _cpserver.request(self.client_address[0], 76 self.address_string(), 77 self.raw_requestline, 78 self._headerlist(), 79 self.rfile) 80 wfile = self.wfile 81 wfile.write("%s %s\r\n" % (self.protocol_version, cpg.response.status)) 82 83 for name, value in cpg.response.headers: 84 wfile.write("%s: %s\r\n" % (name, value)) 85 86 wfile.write("\r\n") 87 try: 88 for chunk in cpg.response.body: 89 wfile.write(chunk) 90 except: 91 wfile.write(">> Error in HTTP server") 92 127 93 def do_GET(self): 128 94 """Serve a GET request.""" 129 95 cpg.request.method = 'GET' 130 _cphttptools.doRequest( 131 self.client_address[0], 132 self.address_string(), 133 self.raw_requestline, 134 self.headers, 135 self.rfile, 136 self.wfile 137 ) 138 96 self.doMethod() 97 139 98 def do_HEAD(self): # Head is not implemented 140 99 """Serve a HEAD request.""" 141 100 cpg.request.method = 'HEAD' 142 _cphttptools.doRequest( 143 self.client_address[0], 144 self.address_string(), 145 self.raw_requestline, 146 self.headers, 147 self.rfile, 148 self.wfile 149 ) 150 101 self.doMethod() 102 151 103 def do_POST(self): 152 104 """Serve a POST request.""" 153 105 cpg.request.method = 'POST' 154 _cphttptools.doRequest( 155 self.client_address[0], 156 self.address_string(), 157 self.raw_requestline, 158 self.headers, 159 self.rfile, 160 self.wfile 161 ) 162 106 self.doMethod() 107 # What does this line do? 163 108 self.connection = self.request 164 109 165 110 def log_message(self, format, *args): 166 111 """ We have to override this to use our own logging mechanism """ … … 169 114 170 115 class CherryHTTPServer(BaseHTTPServer.HTTPServer): 116 171 117 def server_activate(self): 172 118 """Override server_activate to set timeout on our listener socket""" 173 119 self.socket.settimeout(1) 174 120 BaseHTTPServer.HTTPServer.server_activate(self) 175 121 176 122 def server_bind(self): 177 123 # Removed getfqdn call because it was timing out on localhost when calling gethostbyaddr 178 124 self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) 179 125 self.socket.bind(self.server_address) 180 126 181 127 def get_request(self): 182 128 # With Python 2.3 it seems that an accept socket in timeout (nonblocking) mode … … 184 130 # well with makefile() (where wfile and rfile are set in SocketServer.py) we explicitly set 185 131 # the request socket to blocking 186 132 187 133 request, client_address = self.socket.accept() 188 134 request.setblocking(1) 189 135 return request, client_address 190 136 191 137 def handle_request(self): 192 138 """Override handle_request to trap timeout exception.""" … … 197 143 # interrupts on Win32, which don't interrupt accept() by default 198 144 return 1 199 except KeyboardInterrupt:200 _cp LogMessage("<Ctrl-C> hit: shutting down", "HTTP")145 except (KeyboardInterrupt, SystemExit): 146 _cputil.getSpecialFunction('_cpLogMessage')("<Ctrl-C> hit: shutting down", "HTTP") 201 147 self.shutdown() 202 148 203 149 def serve_forever(self): 204 150 """Override serve_forever to handle shutdown.""" … … 206 152 while self.__running: 207 153 self.handle_request() 208 154 start = serve_forever 155 209 156 def shutdown(self): 210 157 self.__running = 0 158 stop = shutdown 159 211 160 212 161 _SHUTDOWNREQUEST = (0,0) 213 162 214 163 class ServerThread(threading.Thread): 215 def __init__(self, RequestHandlerClass, requestQueue, threadIndex): 164 165 def __init__(self, RequestHandlerClass, requestQueue): 216 166 threading.Thread.__init__(self) 217 167 self._RequestHandlerClass = RequestHandlerClass 218 168 self._requestQueue = requestQueue 219 self._threadIndex = threadIndex 220 169 221 170 def run(self): 222 # Call the functions from cpg.server.onStartThreadList223 for func in cpg.server.onStartThreadList:224 func(self._threadIndex)225 171 while 1: 226 172 request, client_address = self._requestQueue.get() 227 173 if (request, client_address) == _SHUTDOWNREQUEST: 228 # Call the functions from cpg.server.onStopThreadList229 for func in cpg.server.onStopThreadList:230 func()231 174 return 232 if self.verify_request(request, client_address): 175 if self.verify_request(request, client_address): 233 176 try: 234 177 self.process_request(request, client_address) … … 238 181 else: 239 182 self.close_request(request) 240 183 241 184 def verify_request(self, request, client_address): 242 185 """ Verify the request. May be overridden. 243 186 Return 1 if we should proceed with this request. """ 244 187 return 1 245 188 246 189 def process_request(self, request, client_address): 247 self._RequestHandlerClass(request, client_address, self) 190 self._RequestHandlerClass(request, client_address, self) 248 191 self.close_request(request) 249 192 250 193 def close_request(self, request): 251 194 """ Called to clean up an individual request. """ 252 195 request.close() 253 196 254 197 def handle_error(self, request, client_address): 255 """ Handle an error gracefully. May be overridden. 256 The default is to print a traceback and continue. 257 """ 258 import traceback, StringIO 259 bodyFile=StringIO.StringIO() 260 traceback.print_exc(file=bodyFile) 261 errorBody=bodyFile.getvalue() 262 bodyFile.close() 198 """Handle an error gracefully. May be overridden.""" 199 errorBody = _cphttptools.formatExc() 263 200 _cputil.getSpecialFunction('_cpLogMessage')(errorBody) 264 201 265 202 266 203 class PooledThreadServer(SocketServer.TCPServer): 267 268 allow_reuse_address = 1269 270 204 """A TCP Server using a pool of worker threads. This is superior to the 271 205 alternatives provided by the Python standard library, which only offer … … 275 209 to the pure async approach used by Twisted because it allows a more 276 210 straightforward and simple programming model in the face of blocking 277 requests (i.e. you don't have to bother with Deferreds).""" 211 requests (i.e. you don't have to bother with Deferreds).""" 212 213 allow_reuse_address = 1 214 278 215 def __init__(self, serverAddress, numThreads, RequestHandlerClass, ThreadClass=ServerThread): 279 216 assert(numThreads > 0) 217 280 218 # I know it says "do not override", but I have to in order to implement SSL support ! 281 219 SocketServer.BaseServer.__init__(self, serverAddress, RequestHandlerClass) … … 283 221 self.server_bind() 284 222 self.server_activate() 285 286 self._numThreads = numThreads 223 224 self._numThreads = numThreads 287 225 self._RequestHandlerClass = RequestHandlerClass 288 226 self._ThreadClass = ThreadClass 289 227 self._requestQueue = Queue.Queue() 290 228 self._workerThreads = [] 291 292 def createThread(self, threadIndex): 293 return self._ThreadClass(self._RequestHandlerClass, self._requestQueue, threadIndex) 294 295 def start(self): 296 if self._workerThreads != []: 297 return 298 for i in xrange(self._numThreads): 299 self._workerThreads.append(self.createThread(i)) 300 for worker in self._workerThreads: 301 worker.start() 302 303 def server_close(self): 304 """Override server_close to shutdown thread pool""" 305 SocketServer.TCPServer.server_close(self) 229 230 def createThread(self): 231 return self._ThreadClass(self._RequestHandlerClass, self._requestQueue) 232 233 def server_activate(self): 234 """Override server_activate to set timeout on our listener socket""" 235 self.socket.settimeout(1) 236 SocketServer.TCPServer.server_activate(self) 237 238 def server_bind(self): 239 self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) 240 self.socket.bind(self.server_address) 241 242 def shutdown(self): 243 """Gracefully shutdown a server that is serve_forever()ing.""" 244 self.__running = 0 245 246 # Must shut down threads here so the code that calls 247 # this method can know when all threads are stopped. 306 248 for worker in self._workerThreads: 307 249 self._requestQueue.put(_SHUTDOWNREQUEST) … … 309 251 worker.join() 310 252 self._workerThreads = [] 311 312 def server_activate(self): 313 """Override server_activate to set timeout on our listener socket""" 314 self.socket.settimeout(1) 315 SocketServer.TCPServer.server_activate(self) 316 317 def server_bind(self): 318 self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) 319 self.socket.bind(self.server_address) 320 321 def shutdown(self): 322 """Gracefully shutdown a server that is serve_forever()ing.""" 323 self.__running = 0 324 253 stop = shutdown 254 325 255 def serve_forever(self): 326 256 """Handle one request at a time until doomsday (or shutdown is called).""" 327 257 if self._workerThreads == []: 328 self.start() 258 for i in xrange(self._numThreads): 259 self._workerThreads.append(self.createThread()) 260 for worker in self._workerThreads: 261 worker.start() 329 262 self.__running = 1 330 263 while self.__running: 331 264 if not self.handle_request(): 332 265 break 333 self.server_close() 334 266 self.server_close() 267 start = serve_forever 268 335 269 def handle_request(self): 336 270 """Override handle_request to enqueue requests rather than handle … … 339 273 try: 340 274 request, client_address = self.get_request() 341 except KeyboardInterrupt:342 _cp LogMessage("<Ctrl-C> hit: shutting down", "HTTP")275 except (KeyboardInterrupt, SystemExit): 276 _cputil.getSpecialFunction('_cpLogMessage')("<Ctrl-C> hit: shutting down", "HTTP") 343 277 return 0 344 278 except socket.error, e: … … 346 280 self._requestQueue.put((request, client_address)) 347 281 return 1 348 282 349 283 def get_request(self): 350 284 # With Python 2.3 it seems that an accept socket in timeout (nonblocking) mode … … 358 292 return request, client_address 359 293 294 295 def embedded_server(handler=None): 296 """Selects and instantiates the appropriate server.""" 297 298 # Set protocol_version 299 CherryHTTPRequestHandler.protocol_version = cpg.config.get('server.protocolVersion') 300 301 # Select the appropriate server based on config options 302 sockFile = cpg.config.get('server.socketFile') 303 threadPool = cpg.config.get('server.threadPool') 304 if sockFile: 305 # AF_UNIX socket 306 # TODO: Handle threading here 307 class ServerClass(CherryHTTPServer): address_family = socket.AF_UNIX 308 309 # So we can reuse the socket 310 try: os.unlink(sockFile) 311 except: pass 312 313 server_address = sockFile 314 else: 315 # AF_INET socket 316 if threadPool > 1: 317 ServerClass = PooledThreadServer 318 else: 319 ServerClass = CherryHTTPServer 320 server_address = (cpg.config.get('server.socketHost'), 321 cpg.config.get('server.socketPort')) 322 323 ServerClass.request_queue_size = cpg.config.get('server.socketQueueSize') 324 325 if handler is None: 326 handler = CherryHTTPRequestHandler 327 328 if threadPool > 1: 329 myCherryHTTPServer = ServerClass(server_address, threadPool, handler) 330 else: 331 myCherryHTTPServer = ServerClass(server_address, handler) 332 333 # So everyone can access the socket 334 if sockFile: 335 try: os.chmod(sockFile, 0777) 336 except: pass 337 338 return myCherryHTTPServer 339 trunk/cherrypy/_cphttptools.py
r213 r229 27 27 """ 28 28 29 import cpg, urllib, sys, time, traceback, types, StringIO, cgi, os30 import mimetypes, sha, random, string, _cputil, cperror, Cookie, urlparse31 from lib.filter import basefilter32 import _cpdefaults33 34 29 """ 35 30 Common Service Code for CherryPy 36 31 """ 37 32 33 import urllib, sys, time, traceback, types, StringIO, cgi 34 import mimetypes, Cookie, urlparse 35 import cpg, _cputil, cperror, _cpdefaults 36 from lib.filter import basefilter 37 38 from BaseHTTPServer import BaseHTTPRequestHandler 39 responseCodes = BaseHTTPRequestHandler.responses 40 38 41 mimetypes.types_map['.dwg']='image/x-dwg' 39 42 mimetypes.types_map['.ico']='image/x-icon' … … 42 45 monthname = [None, 'Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'] 43 46 44 class IndexRedirect(Exception): pass 45 46 def parseFirstLine(data): 47 cpg.request.path = data.split()[1] 48 cpg.request.queryString = "" 49 cpg.request.browserUrl = cpg.request.path 50 cpg.request.paramMap = {} 51 cpg.request.paramList = [] # Only used for Xml-Rpc 52 cpg.request.filenameMap = {} 53 cpg.request.fileTypeMap = {} 54 # From http://www.cherrypy.org/ticket/141/ 55 # find the queryString, or set it to "" if not found 56 if "?" in cpg.request.path: 57 cpg.request.path, cpg.request.queryString = cpg.request.path.split("?",1) 47 48 class Request(object): 49 """Process a request and yield a series of response chunks. 50 51 headers should be a list of (name, value) tuples. 52 """ 53 54 def __init__(self, clientAddress, remoteHost, requestLine, headers, rfile): 55 # When __init__ is finished, cpg.response should have three attributes: 56 # status, e.g. "200 OK" 57 # headers, a list of (name, value) tuples 58 # body, an iterable yielding strings 59 # Consumer code should then access these three attributes 60 # to build the outbound stream. 61 62 self.requestLine = requestLine 63 self.requestHeaders = headers 64 self.rfile = rfile 65 66 # Prepare cpg.request variables 67 cpg.request.remoteAddr = clientAddress 68 cpg.request.remoteHost = remoteHost 69 cpg.request.paramList = [] # Only used for Xml-Rpc 70 cpg.request.filenameMap = {} 71 cpg.request.fileTypeMap = {} 72 cpg.request.headerMap = {} 73 cpg.request.requestLine = requestLine 74 cpg.request.simpleCookie = Cookie.SimpleCookie() 75 cpg.request.isStatic = False 76 cpg.request.processRequestBody = cpg.request.method in ("POST",) 77 cpg.request.rfile = self.rfile 78 79 # Prepare cpg.response variables 80 cpg.response.status = None 81 cpg.response.headers = None 82 cpg.response.body = None 83 84 year, month, day, hh, mm, ss, wd, y, z = time.gmtime(time.time()) 85 date = ("%s, %02d %3s %4d %02d:%02d:%02d GMT" % 86 (weekdayname[wd], day, monthname[month], year, hh, mm, ss)) 87 cpg.response.headerMap = { 88 "Content-Type": "text/html", 89 "Server": "CherryPy/" + cpg.__version__, 90 "Date": date, 91 "Set-Cookie": [], 92 "Content-Length": 0 93 } 94 cpg.response.simpleCookie = Cookie.SimpleCookie() 95 96 self.run() 97 98 def run(self): 99 try: 100 self.processRequestHeaders() 101 102 try: 103 # onStart has to be after processRequestHeaders 104 # so that "path", for example, gets set. 105 applyFilters('onStartResource') 106 107 applyFilters('beforeRequestBody') 108 if cpg.request.processRequestBody: 109 self.processRequestBody() 110 111 applyFilters('beforeMain') 112 if cpg.response.body is None: 113 main() 114 115 applyFilters('beforeFinalize') 116 finalize() 117 finally: 118 applyFilters('onEndResource') 119 except basefilter.RequestHandled: 120 pass 121 except: 122 handleError(sys.exc_info()) 123 124 def processRequestHeaders(self): 125 # Parse first line 126 path = self.requestLine.split()[1] 127 128 # find the queryString, or set it to "" if not found 129 if "?" in path: 130 cpg.request.path, cpg.request.queryString = path.split("?", 1) 131 else: 132 cpg.request.path, cpg.request.queryString = path, "" 133 134 # build a paramMap dictionary from queryString 135 pm = cgi.parse_qs(cpg.request.queryString, keep_blank_values=True) 136 for key, val in pm.items(): 137 if len(val) == 1: 138 pm[key] = val[0] 139 cpg.request.paramMap = pm 140 141 # Process the headers into request.headerMap 142 for name, value in self.requestHeaders: 143 name = name.title() 144 value = value.strip() 145 # Warning: if there is more than one header entry for cookies (AFAIK, 146 # only Konqueror does that), only the last one will remain in headerMap 147 # (but they will be correctly stored in request.simpleCookie). 148 cpg.request.headerMap[name] = value 149 150 # Handle cookies differently because on Konqueror, multiple cookies 151 # come on different lines with the same key 152 if name == 'Cookie': 153 cpg.request.simpleCookie.load(value) 154 155 # Set peer_certificate (in SSL mode) so the 156 # web app can examine the client certificate 157 try: 158 cpg.request.peerCertificate = self.request.get_peer_certificate() 159 except: 160 pass 161 msg = "%s - %s" % (cpg.request.remoteAddr, self.requestLine[:-2]) 162 _cputil.getSpecialFunction('_cpLogMessage')(msg, "HTTP") 163 164 cpg.request.base = "http://" + cpg.request.headerMap.get('Host', '') 165 cpg.request.browserUrl = cpg.request.base + path 166 167 # Change objectPath in filters to change 168 # the object that will get rendered 169 cpg.request.objectPath = None 170 171 # Save original values (in case they get modified by filters) 172 cpg.request.originalPath = cpg.request.path 173 cpg.request.originalParamMap = cpg.request.paramMap 174 cpg.request.originalParamList = cpg.request.paramList 175 176 def processRequestBody(self): 177 # Read request body and put it in data 178 data = "" 179 length = int(cpg.request.headerMap.get("Content-Length", "0")) 180 if length: 181 data = self.rfile.read(length) 182 183 # Put data in a StringIO so FieldStorage can read it 184 newRfile = StringIO.StringIO(data) 185 # Create a copy of headerMap with lowercase keys because 186 # FieldStorage doesn't work otherwise 187 lowerHeaderMap = {} 188 for key, value in cpg.request.headerMap.items(): 189 lowerHeaderMap[key.lower()] = value 190 forms = cgi.FieldStorage(fp = newRfile, headers = lowerHeaderMap, 191 environ = {'REQUEST_METHOD': 'POST'}, 192 keep_blank_values = 1) 193 194 # In case of files being uploaded, save filenames/types in maps. 195 for key in forms.keys(): 196 valueList = forms[key] 197 if isinstance(valueList, list): 198 cpg.request.paramMap[key] = [] 199 cpg.request.filenameMap[key] = [] 200 cpg.request.fileTypeMap[key] = [] 201 for item in valueList: 202 cpg.request.paramMap[key].append(item.value) 203 cpg.request.filenameMap[key].append(item.filename) 204 cpg.request.fileTypeMap[key].append(item.type) 205 else: 206 cpg.request.paramMap[key] = valueList.value 207 cpg.request.filenameMap[key] = valueList.filename 208 cpg.request.fileTypeMap[key] = valueList.type 209 210 211 # Error handling 212 213 dbltrace = """ 214 =====First Error===== 215 216 %s 217 218 =====Second Error===== 219 220 %s 221 222 """ 223 224 def handleError(exc): 225 """Set status, headers, and body when an error occurs.""" 226 try: 227 applyFilters('beforeErrorResponse') 228 229 # _cpOnError will probably change cpg.response.body. 230 # It may also change the headerMap, etc. 231 _cputil.getSpecialFunction('_cpOnError')() 232 233 finalize() 234 235 applyFilters('afterErrorResponse') 236 except: 237 # Failure in _cpOnError, error filter, or finalize. 238 # Bypass them all. 239 body = dbltrace % (formatExc(exc), formatExc()) 240 cpg.response.status, cpg.response.headers, body = bareError(body) 241 cpg.response.body = [body] 242 243 def formatExc(exc=None): 244 """formatExc(exc=None) -> exc (or sys.exc_info), formatted.""" 245 if exc is None: 246 exc = sys.exc_info() 247 return "".join(traceback.format_exception(*exc)) 248 249 def bareError(extrabody=None): 250 """bareError(extrabody=None) -> status, headers, body. 251 252 Returns a triple without calling any other questionable functions, 253 so it should be as error-free as possible. Call it from an HTTP server 254 if you get errors after Request() is done. 255 256 If extrabody is None, a friendly but rather unhelpful error message 257 is set in the body. If extrabody is a string, it will be appended 258 as-is to the body. 259 """ 260 261 body = "Unrecoverable error in the server." 262 if extrabody is not None: 263 body += "\n" + extrabody 264 return ("500 Internal Server Error", 265 [('Content-Type', 'text/plain'), 266 ('Content-Length', str(len(body)))], 267 body) 268 269 270 271 # Response functions 272 273 def main(): 274 """Obtain and set cpg.response.body.""" 275 path = cpg.request.path 276 # Remove leading and trailing slash 277 path = path.strip("/") 278 # Replace quoted chars (eg %20) from url 279 path = urllib.unquote(path) 280 281 try: 282 func, objectPathList, virtualPathList = mapPathToObject() 283 except IndexRedirect, inst: 284 # For an IndexRedirect, we don't go through the regular 285 # mechanism: we return the redirect immediately 286 newUrl = urlparse.urljoin(cpg.request.base, inst.args[0]) 287 cpg.response.status = '302 Found' 288 cpg.response.headerMap['Location'] = newUrl 289 cpg.response.body = [] 290 return 291 292 # Remove "root" from objectPathList and join it to get objectPath 293 cpg.request.objectPath = '/' + '/'.join(objectPathList[1:]) 294 body = func(*(virtualPathList + cpg.request.paramList), 295 **(cpg.request.paramMap)) 296 297 # build a uniform return type (iterable) 298 if isinstance(body, types.FileType): 299 body = fileGenerator(body) 300 elif isinstance(body, types.GeneratorType): 301 body = flattener(body) 302 elif isinstance(body, basestring): 303 body = [body] 304 elif body is None: 305 body = [""] 306 cpg.response.body = body 307 return body 308 309 def checkStatus(): 310 """Test/set cpg.response.status. Provide Reason-phrase if missing.""" 311 if not cpg.response.status: 312 cpg.response.status = "200 OK" 58 313 else: 59 cpg.request.path, cpg.request.queryString = cpg.request.path, "" 60 61 # build a paramMap dictionary from queryString 62 pm = cpg.request.paramMap = cgi.parse_qs(cpg.request.queryString, True) 63 for key, val in pm.items(): 64 if len(val) == 1: 65 pm[key] = val[0] 66 67 def cookHeaders(clientAddress, remoteHost, headers, requestLine): 68 """Process the headers into the request.headerMap""" 69 cpg.request.headerMap = {} 70 cpg.request.requestLine = requestLine 71 cpg.request.simpleCookie = Cookie.SimpleCookie() 72 73 # Build headerMap 74 for item in headers.items(): 75 # Warning: if there is more than one header entry for cookies (AFAIK, only Konqueror does that) 76 # only the last one will remain in headerMap (but they will be correctly stored in request.simpleCookie) 77 insertIntoHeaderMap(item[0],item[1]) 78 79 # Handle cookies differently because on Konqueror, multiple cookies come on different lines with the same key 80 cookieList = headers.getallmatchingheaders('cookie') 81 for cookie in cookieList: 82 cpg.request.simpleCookie.load(cookie) 83 84 cpg.request.remoteAddr = clientAddress 85 cpg.request.remoteHost = remoteHost 86 87 # Set peer_certificate (in SSL mode) so the web app can examinate the client certificate 88 try: cpg.request.peerCertificate = self.request.get_peer_certificate() 89 except: pass 90 91 _cputil.getSpecialFunction('_cpLogMessage')("%s - %s" % (cpg.request.remoteAddr, requestLine[:-2]), "HTTP") 92 93 94 def parsePostData(rfile): 95 # Read request body and put it in data 96 len = int(cpg.request.headerMap.get("Content-Length","0")) 97 if len: data = rfile.read(len) 98 else: data="" 99 100 # Put data in a StringIO so FieldStorage can read it 101 newRfile = StringIO.StringIO(data) 102 # Create a copy of headerMap with lowercase keys because 103 # FieldStorage doesn't work otherwise 104 lowerHeaderMap = {} 105 for key, value in cpg.request.headerMap.items(): 106 lowerHeaderMap[key.lower()] = value 107 forms = cgi.FieldStorage(fp = newRfile, headers = lowerHeaderMap, environ = {'REQUEST_METHOD':'POST'}, keep_blank_values = 1) 108 for key in forms.keys(): 109 # Check if it's a
