Changeset 1047
- Timestamp:
- 04/21/06 15:44:17
- Files:
-
- trunk/cherrypy/__init__.py (modified) (5 diffs)
- trunk/cherrypy/_cpengine.py (modified) (5 diffs)
- trunk/cherrypy/_cperror.py (modified) (1 diff)
- trunk/cherrypy/_cphooks.py (added)
- trunk/cherrypy/_cprequest.py (moved) (moved from trunk/cherrypy/_cphttptools.py) (19 diffs)
- trunk/cherrypy/_cpserver.py (modified) (6 diffs)
- trunk/cherrypy/_cputil.py (modified) (4 diffs)
- trunk/cherrypy/_cpwsgiserver.py (modified) (1 diff)
- trunk/cherrypy/config.py (modified) (8 diffs)
- trunk/cherrypy/filters (deleted)
- trunk/cherrypy/lib/caching.py (added)
- trunk/cherrypy/lib/cptools.py (modified) (6 diffs)
- trunk/cherrypy/lib/encodings.py (added)
- trunk/cherrypy/lib/sessions.py (added)
- trunk/cherrypy/lib/static.py (added)
- trunk/cherrypy/lib/staticdir.py (added)
- trunk/cherrypy/lib/staticfile.py (added)
- trunk/cherrypy/lib/tidy.py (added)
- trunk/cherrypy/lib/wsgiapp.py (added)
- trunk/cherrypy/lib/xmlrpc.py (added)
- trunk/cherrypy/test/benchmark.py (modified) (3 diffs)
- trunk/cherrypy/test/test.py (modified) (1 diff)
- trunk/cherrypy/test/test_baseurl_filter.py (modified) (1 diff)
- trunk/cherrypy/test/test_config.py (modified) (2 diffs)
- trunk/cherrypy/test/test_core.py (modified) (2 diffs)
- trunk/cherrypy/test/test_logdebuginfo_filter.py (deleted)
- trunk/cherrypy/tools.py (added)
- trunk/cherrypy/tutorial/tut09_files.py (modified) (3 diffs)
Legend:
- Unmodified
- Added
- Removed
- Modified
- Copied
- Moved
trunk/cherrypy/__init__.py
r1037 r1047 1 1 """Global module that all modules developing with CherryPy should import.""" 2 2 3 __version__ = ' 2.2.0'3 __version__ = '3.0.0alpha' 4 4 5 5 import datetime … … 9 9 from _cperror import * 10 10 import config 11 import tools 11 12 12 13 import _cptree … … 15 16 # Legacy code may clobber this. 16 17 root = None 17 18 lowercase_api = False19 18 20 19 import _cpserver … … 59 58 # Create thread_data object as a thread-specific all-purpose storage 60 59 thread_data = local() 61 threadData = thread_data # Backward compatibility62 60 63 # Create variables needed for session (see lib/sessionfilter.py for more info)64 from filters import sessionfilter65 session = sessionfilter.SessionWrapper()66 _session_data_holder = {} # Needed for RAM sessions only67 _session_lock_dict = {} # Needed for RAM sessions only68 _session_last_clean_up_time = datetime.datetime.now()61 ### Create variables needed for session (see lib/sessionfilter.py for more info) 62 ##from filters import sessionfilter 63 ##session = sessionfilter.SessionWrapper() 64 ##_session_data_holder = {} # Needed for RAM sessions only 65 ##_session_lock_dict = {} # Needed for RAM sessions only 66 ##_session_last_clean_up_time = datetime.datetime.now() 69 67 70 68 def expose(func=None, alias=None): … … 102 100 103 101 logfunc(msg, context, severity) 102 trunk/cherrypy/_cpengine.py
r989 r1047 8 8 9 9 import cherrypy 10 from cherrypy import _cp httptools, filters10 from cherrypy import _cprequest 11 11 from cherrypy.lib import autoreload, profiler, cptools 12 12 … … 20 20 """The application server engine, connecting HTTP servers to Requests.""" 21 21 22 request_class = _cp httptools.Request23 response_class = _cp httptools.Response22 request_class = _cprequest.Request 23 response_class = _cprequest.Response 24 24 25 25 def __init__(self): … … 74 74 75 75 # Initialize the built in filters 76 filters.init()76 ## filters.init() 77 77 78 78 def start(self): … … 160 160 " receive requests, False otherwise.") 161 161 162 def request(self, clientAddress, remote Host, scheme="http"):162 def request(self, clientAddress, remote_host, scheme="http"): 163 163 """Obtain an HTTP Request object. 164 164 165 165 clientAddress: the (IP address, port) of the client 166 remote Host: the IP address of the client166 remote_host: the IP address of the client 167 167 scheme: either "http" or "https"; defaults to "http" 168 168 """ … … 186 186 187 187 r = self.request_class(clientAddress[0], clientAddress[1], 188 remote Host, scheme)188 remote_host, scheme) 189 189 cherrypy.serving.request = r 190 190 cherrypy.serving.response = self.response_class() trunk/cherrypy/_cperror.py
r972 r1047 41 41 if isinstance(params, basestring): 42 42 request.query_string = params 43 request.queryString = request.query_string # Backward compatibility44 43 pm = cgi.parse_qs(params, keep_blank_values=True) 45 44 for key, val in pm.items(): trunk/cherrypy/_cprequest.py
r1046 r1047 7 7 8 8 import cherrypy 9 from cherrypy import _cputil, _cpcgifs 10 from cherrypy.filters import applyFilters 9 from cherrypy import _cputil, _cpcgifs, tools 11 10 from cherrypy.lib import cptools, httptools 11 12 13 class HookMap(object): 14 15 def __init__(self, points=None, failsafe=None): 16 points = points or [] 17 self.callbacks = dict([(point, []) for point in points]) 18 self.failsafe = failsafe or [] 19 20 def attach(self, point, callback, conf=None): 21 if conf is None: 22 self.callbacks[point].append(callback) 23 else: 24 def wrapper(): 25 callback(**conf) 26 self.callbacks[point].append(wrapper) 27 28 def populate_from_config(self): 29 configs = cherrypy.config.configs 30 collapsed_map = {} 31 32 def collect_tools(section): 33 local_conf = configs.get(section, {}) 34 for k, v in local_conf.iteritems(): 35 atoms = k.split(".") 36 namespace = atoms.pop(0) 37 if namespace == "tools": 38 toolname = atoms.pop(0) 39 bucket = collapsed_map.setdefault(toolname, {}) 40 bucket[".".join(atoms)] = v 41 42 collect_tools("global") 43 path = "" 44 for b in cherrypy.request.object_path.split('/'): 45 path = "/".join((path, b)) 46 collect_tools(path) 47 48 for toolname, conf in collapsed_map.iteritems(): 49 if conf.get("on", False): 50 del conf["on"] 51 getattr(tools, toolname).setup(conf) 52 53 def run(self, point): 54 """Execute all registered callbacks for the given point.""" 55 failsafe = point in self.failsafe 56 for callback in self.callbacks[point]: 57 # Some hookpoints guarantee all callbacks are run even if 58 # others at the same hookpoint fail. We will still log the 59 # failure, but proceed on to the next callback. The only way 60 # to stop all processing from one of these callbacks is 61 # to raise SystemExit and stop the whole server. So, trap 62 # your own errors in these callbacks! 63 if failsafe: 64 try: 65 callback() 66 except (KeyboardInterrupt, SystemExit): 67 raise 68 except: 69 cherrypy.log(traceback=True) 70 else: 71 callback() 72 12 73 13 74 … … 15 76 """An HTTP request.""" 16 77 17 def __init__(self, remote Addr, remotePort, remoteHost, scheme="http"):78 def __init__(self, remote_addr, remote_port, remote_host, scheme="http"): 18 79 """Populate a new Request object. 19 80 20 remote Addr should be the client IP address21 remote Port should be the client Port22 remote Host should be string of the client's IP address.81 remote_addr should be the client IP address 82 remote_port should be the client Port 83 remote_host should be string of the client's IP address. 23 84 scheme should be a string, either "http" or "https". 24 85 """ 25 self.remote_addr = remoteAddr 26 self.remote_port = remotePort 27 self.remote_host = remoteHost 28 # backward compatibility 29 self.remoteAddr = remoteAddr 30 self.remotePort = remotePort 31 self.remoteHost = remoteHost 86 self.remote_addr = remote_addr 87 self.remote_port = remote_port 88 self.remote_host = remote_host 32 89 33 90 self.scheme = scheme 34 91 self.execute_main = True 35 92 self.closed = False 93 94 self.hooks = HookMap(['on_start_resource', 'before_request_body', 95 'before_main', 'before_finalize', 96 'on_end_resource', 'on_end_request', 97 'before_error_response', 'after_error_response']) 98 self.hooks.failsafe = ['on_start_resource', 'on_end_resource', 99 'on_end_request'] 36 100 37 101 def close(self): 38 102 if not self.closed: 39 103 self.closed = True 40 applyFilters('on_end_request', failsafe=True)104 self.hooks.run('on_end_request') 41 105 cherrypy.serving.__dict__.clear() 42 106 … … 63 127 64 128 self.headers = httptools.HeaderMap() 65 self.headerMap = self.headers # Backward compatibility66 129 self.simple_cookie = Cookie.SimpleCookie() 67 self.simpleCookie = self.simple_cookie # Backward compatibility68 130 69 131 if cherrypy.profiler: … … 87 149 # right away. 88 150 self.processRequestLine() 151 self.hooks.populate_from_config() 89 152 90 153 try: 91 applyFilters('on_start_resource', failsafe=True)154 self.hooks.run('on_start_resource') 92 155 93 156 try: 94 157 self.processHeaders() 95 158 96 applyFilters('before_request_body')159 self.hooks.run('before_request_body') 97 160 if self.processRequestBody: 98 161 self.processBody() … … 101 164 while True: 102 165 try: 103 applyFilters('before_main')166 self.hooks.run('before_main') 104 167 if self.execute_main: 105 168 self.main() … … 108 171 self.object_path = ir.path 109 172 110 applyFilters('before_finalize')173 self.hooks.run('before_finalize') 111 174 cherrypy.response.finalize() 112 175 except cherrypy.RequestHandled: … … 117 180 # we return the redirect or error page immediately 118 181 inst.set_response() 119 applyFilters('before_finalize')182 self.hooks.run('before_finalize') 120 183 cherrypy.response.finalize() 121 184 finally: 122 applyFilters('on_end_resource', failsafe=True)185 self.hooks.run('on_end_resource') 123 186 except (KeyboardInterrupt, SystemExit): 124 187 raise … … 139 202 self.path = path 140 203 self.query_string = qs 141 self.queryString = qs # Backward compatibility142 204 self.protocol = proto 143 205 144 # Change object_path in filters to change 145 # the object that will get rendered 206 # Change object_path to change the object that will get rendered 146 207 self.object_path = path 147 208 … … 162 223 # cherrypy.request.version == request.protocol in a Version instance. 163 224 self.version = httptools.Version.from_http(self.protocol) 225 226 # cherrypy.response.version should be used to determine whether or 227 # not to include a given HTTP/1.1 feature in the response content. 164 228 server_v = cherrypy.config.get("server.protocol_version", "HTTP/1.0") 165 229 server_v = httptools.Version.from_http(server_v) 166 167 # cherrypy.response.version should be used to determine whether or168 # not to include a given HTTP/1.1 feature in the response content.169 230 cherrypy.response.version = min(self.version, server_v) 170 231 232 def main(self, path=None): 233 """Obtain and set cherrypy.response.body from a page handler.""" 234 dispatch = cherrypy.config.get("dispatcher") or Dispatcher() 235 handler = dispatch(path) 236 cherrypy.response.body = handler(*self.virtual_path, **self.params) 237 171 238 def processHeaders(self): 172 173 239 self.params = httptools.parseQueryString(self.query_string) 174 self.paramMap = self.params # Backward compatibility175 240 176 241 # Process the headers into self.headers … … 186 251 if name.title() == 'Cookie': 187 252 self.simple_cookie.load(value) 188 189 # Save original values (in case they get modified by filters)190 # This feature is deprecated in 2.2 and will be removed in 2.3.191 self._original_params = self.params.copy()192 253 193 254 if self.version >= "1.1": … … 200 261 self.base = "%s://%s" % (self.scheme, self.headers.get('Host', '')) 201 262 202 def _get_original_params(self):203 # This feature is deprecated in 2.2 and will be removed in 2.3.204 return self._original_params205 original_params = property(_get_original_params,206 doc="Deprecated. A copy of the original params.")207 208 263 def _get_browser_url(self): 209 264 url = self.base + self.path … … 213 268 browser_url = property(_get_browser_url, 214 269 doc="The URL as entered in a browser (read-only).") 215 browserUrl = browser_url # Backward compatibility216 270 217 271 def processBody(self): … … 238 292 else: 239 293 self.params.update(httptools.paramsFromCGIForm(forms)) 240 241 def main(self, path=None): 242 """Obtain and set cherrypy.response.body from a page handler.""" 294 295 296 class Dispatcher(object): 297 298 def __call__(self, path=None): 299 """Find the appropriate page handler.""" 300 request = cherrypy.request 243 301 if path is None: 244 path = self.object_path 245 246 page_handler, object_path, virtual_path = self.mapPathToObject(path) 302 path = request.object_path 303 304 handler, opath, vpath = self.find(request.browser_url, path) 305 306 # Remove "root" from opath and join it to get object_path 307 request.object_path = '/' + '/'.join(opath[1:]) 247 308 248 309 # Decode any leftover %2F in the virtual_path atoms. 249 virtual_path = [x.replace("%2F", "/") for x in virtual_path] 250 251 # Remove "root" from object_path and join it to get object_path 252 self.object_path = '/' + '/'.join(object_path[1:]) 253 try: 254 body = page_handler(*virtual_path, **self.params) 255 except Exception, x: 256 if hasattr(x, "args"): 257 x.args = x.args + (page_handler,) 258 raise 259 cherrypy.response.body = body 260 261 def mapPathToObject(self, objectpath): 262 """For path, return the corresponding exposed callable (or raise NotFound). 263 264 path should be a "relative" URL path, like "/app/a/b/c". Leading and 265 trailing slashes are ignored. 266 267 Traverse path: 268 for /a/b?arg=val, we'll try: 269 root.a.b.index -> redirect to /a/b/?arg=val 270 root.a.b.default(arg='val') -> redirect to /a/b/?arg=val 271 root.a.b(arg='val') 272 root.a.default('b', arg='val') 273 root.default('a', 'b', arg='val') 274 275 The target method must have an ".exposed = True" attribute. 276 """ 277 310 request.virtual_path = [x.replace("%2F", "/") for x in vpath] 311 312 return handler 313 314 def find(self, browser_url, objectpath): 278 315 objectTrail = _cputil.get_object_trail(objectpath) 279 316 names = [name for name, candidate in objectTrail] … … 299 336 # had a trailing slash (otherwise, do a redirect). 300 337 if not objectpath.endswith('/'): 301 atoms = self.browser_url.split("?", 1)338 atoms = browser_url.split("?", 1) 302 339 newUrl = atoms.pop(0) + '/' 303 340 if atoms: … … 360 397 361 398 self.headers = httptools.HeaderMap() 362 self.headerMap = self.headers # Backward compatibility363 399 content_type = cherrypy.config.get('server.default_content_type', 'text/html') 364 400 self.headers.update({ … … 370 406 }) 371 407 self.simple_cookie = Cookie.SimpleCookie() 372 self.simpleCookie = self.simple_cookie # Backward compatibility373 408 374 409 def collapse_body(self): … … 419 454 """Set status, headers, and body when an unanticipated error occurs.""" 420 455 try: 421 applyFilters('before_error_response') 422 423 # _cp_on_error will probably change self.body. 424 # It may also change the headers, etc. 425 _cputil.get_special_attribute('_cp_on_error', '_cpOnError')() 426 456 cherrypy.request.hooks.run('before_error_response') 457 458 self.error_response() 427 459 self.finalize() 428 460 429 applyFilters('after_error_response')461 cherrypy.request.hooks.run('after_error_response') 430 462 return 431 463 except cherrypy.HTTPRedirect, inst: … … 445 477 pass 446 478 447 # Failure in _cp_on_error, error filter, or finalize.479 # Failure in self.error_response, error hooks, or finalize. 448 480 # Bypass them all. 449 481 if cherrypy.config.get('server.show_tracebacks', False): … … 454 486 self.setBareError(body) 455 487 488 def error_response(self): 489 # Allow logging of only *unexpected* HTTPError's. 490 if (not cherrypy.config.get('server.log_tracebacks', True) 491 and cherrypy.config.get('server.log_unhandled_tracebacks', True)): 492 cherrypy.log(traceback=True) 493 cherrypy.HTTPError(500).set_response() 494 456 495 def setBareError(self, body=None): 457 496 self.status, self.header_list, self.body = _cputil.bareError(body) trunk/cherrypy/_cpserver.py
r1004 r1047 5 5 6 6 import cherrypy 7 from cherrypy import _cphttptools8 7 from cherrypy.lib import cptools 9 10 8 from cherrypy._cpengine import Engine, STOPPED, STARTING, STARTED 11 9 … … 21 19 22 20 self.httpserver = None 23 # Starting in 2.2, the "httpserverclass" attr is essentially dead;24 # no CP code uses it. Inspect "httpserver" instead.25 self.httpserverclass = None26 27 # Backward compatibility:28 self.onStopServerList = self.on_stop_server_list29 self.onStartThreadList = self.on_start_thread_list30 self.onStartServerList = self.on_start_server_list31 self.onStopThreadList = self.on_stop_thread_list32 21 33 22 def start(self, init_only=False, server_class=_missing, server=None, **kwargs): … … 37 26 Set serverClass and server to None to skip starting any HTTP server. 38 27 """ 39 40 # Read old variable names for backward compatibility41 if 'initOnly' in kwargs:42 init_only = kwargs['initOnly']43 if 'serverClass' in kwargs:44 server_class = kwargs['serverClass']45 28 46 29 conf = cherrypy.config.get … … 60 43 # Dynamically load the class from the given string 61 44 server_class = cptools.attributes(server_class) 62 self.httpserverclass = server_class63 45 if server_class is not None: 64 46 self.httpserver = server_class() … … 66 48 if isinstance(server, basestring): 67 49 server = cptools.attributes(server) 68 self.httpserverclass = server.__class__69 50 self.httpserver = server 70 51 … … 182 163 """Start, then callback the given func in a new thread.""" 183 164 184 # Read old name for backward compatibility185 if serverClass is not None:186 server_class = None187 188 165 if args is None: 189 166 args = () trunk/cherrypy/_cputil.py
r1030 r1047 107 107 108 108 def _cp_log_access(): 109 """ Default method for logging access"""109 """Default method for logging access""" 110 110 111 111 tmpl = '%(h)s %(l)s %(u)s [%(t)s] "%(r)s" %(s)s %(b)s "%(f)s" "%(a)s"' 112 s = tmpl % {'h': cherrypy.request.remote Host,112 s = tmpl % {'h': cherrypy.request.remote_host, 113 113 'l': '-', 114 114 'u': getattr(cherrypy.request, "login", None) or "-", … … 230 230 231 231 def _cp_on_http_error(status, message): 232 """ Default _cp_on_http_error method.232 """Default _cp_on_http_error method. 233 233 234 234 status should be an int. … … 362 362 [body]) 363 363 364 def _cp_on_error():365 """ Default _cp_on_error method """366 # Allow logging of only *unexpected* HTTPError's.367 if (not cherrypy.config.get('server.log_tracebacks', True)368 and cherrypy.config.get('server.log_unhandled_tracebacks', True)):369 cherrypy.log(traceback=True)370 371 cherrypy.HTTPError(500).set_response()372 373 364 def headers(headers): 374 365 """ Provides a simple way to add specific headers to page handler … … 387 378 return wrapper 388 379 389 _cp_filters = []390 trunk/cherrypy/_cpwsgiserver.py
r1037 r1047 218 218 class CherryPyWSGIServer(object): 219 219 220 version = "CherryPy/ 2.2.0"220 version = "CherryPy/3.0.0alpha" 221 221 ready = False 222 222 interrupt = None trunk/cherrypy/config.py
r1004 r1047 3 3 import ConfigParser 4 4 import os 5 _favicon_path = os.path.join(os.path.dirname(__file__), "favicon.ico") 5 6 6 7 import cherrypy … … 12 13 # Keys are URL paths, and values are dicts. 13 14 configs = {} 14 configMap = configs # Backward compatibility15 15 16 16 default_global = { … … 27 27 'server.environment': "development", 28 28 29 '/favicon.ico': { 30 'static_filter.on': True, 31 'static_filter.file': os.path.join(os.path.dirname(__file__), "favicon.ico"),} 29 '/favicon.ico': {'hooks.static.on': True, 30 'hooks.static.file': _favicon_path}, 32 31 } 33 32 … … 35 34 "development": { 36 35 'autoreload.on': True, 37 'log_debug_info_filter.on': True,38 36 'server.log_file_not_found': True, 39 37 'server.show_tracebacks': True, … … 42 40 "staging": { 43 41 'autoreload.on': False, 44 'log_debug_info_filter.on': False,45 42 'server.log_file_not_found': False, 46 43 'server.show_tracebacks': False, … … 49 46 "production": { 50 47 'autoreload.on': False, 51 'log_debug_info_filter.on': False,52 48 'server.log_file_not_found': False, 53 49 'server.show_tracebacks': False, … … 122 118 path = "/" 123 119 124 if cherrypy.lowercase_api is False: 125 # We don't know for sure if user uses the new lowercase API 126 try: 127 result = configs[path][_cputil.lower_to_camel(key)] 128 break 129 except KeyError: 130 try: 131 result = configs[path][key] 132 break 133 except KeyError: 134 pass 135 pass 136 137 try: 138 # Check for a server.environment entry at this node. 139 env = configs[path]["server.environment"] 140 # For backward compatibility, check for camelCase key first 141 result = environments[env][_cputil.lower_to_camel(key)] 142 break 143 except KeyError: 144 try: 145 env = configs[path]["server.environment"] 146 result = environments[env][key] 147 break 148 except KeyError: 149 pass 150 pass 151 else: 152 # We know for sure that user uses the new lowercase api 153 try: 154 result = configs[path][key] 155 break 156 except KeyError: 157 pass 158 159 try: 160 env = configs[path]["server.environment"] 161 result = environments[env][key] 162 break 163 except KeyError: 164 pass 120 try: 121 result = configs[path][key] 122 break 123 except KeyError: 165 124 pass 125 126 try: 127 env = configs[path]["server.environment"] 128 result = environments[env][key] 129 break 130 except KeyError: 131 pass 132 pass 166 133 167 134 if path == "global": … … 183 150 else: 184 151 return result 185 186 def getAll(key):187 """Lookup key in the current node and all of its parent nodes188 as a list of path, value pairs.189 """190 # Needed by the session filter191 192 try:193 results = [('global', configs['global'][key])]194 except KeyError:195 results = []196 197 try:198 path = cherrypy.request.object_path199 except AttributeError:200 return results201 202 pathList = path.split('/')203 204 for n in xrange(1, len(pathList)):205 path = '/' + '/'.join(pathList[0:n+1])206 try:207 results.append((path, configs[path][key]))208 except KeyError:209 pass210 211 return results212 152 213 153 trunk/cherrypy/lib/cptools.py
r1045 r1047 2 2 3 3 import inspect 4 import mimetools5 import mimetypes6 mimetypes.init()7 mimetypes.types_map['.dwg']='image/x-dwg'8 mimetypes.types_map['.ico']='image/x-icon'9 10 4 import os 11 5 import sys … … 13 7 14 8 import cherrypy 15 import httptools 16 17 from cherrypy.filters.wsgiappfilter import WSGIAppFilter 9 18 10 19 11 … … 64 56 return self.items[key] 65 57 66 def modified_since(path, stat=None):67 """Check whether a file has been modified since the date68 provided in 'If-Modified-Since'69 It doesn't check if the file exists or not70 Return True if has been modified, False otherwise71 """72 # serveFile already creates a stat object so let's not73 # waste our energy to do it again74 if not stat:75 try:76 stat = os.stat(path)77 except OSError:78 if cherrypy.config.get('server.log_file_not_found', False):79 cherrypy.log(" NOT FOUND file: %s" % path, "DEBUG")80 raise cherrypy.NotFound()81 82 response = cherrypy.response83 strModifTime = httptools.HTTPDate(time.gmtime(stat.st_mtime))84 if cherrypy.request.headers.has_key('If-Modified-Since'):85 if cherrypy.request.headers['If-Modified-Since'] == strModifTime:86 response.status = "304 Not Modified"87 response.body = None88 if getattr(cherrypy, "debug", None):89 cherrypy.log(" Found file (304 Not Modified): %s" % path, "DEBUG")90 return False91 response.headers['Last-Modified'] = strModifTime92 return True93 94 def serveFile(path, contentType=None, disposition=None, name=None):95 """Set status, headers, and body in order to serve the given file.96 97 The Content-Type header will be set to the contentType arg, if provided.98 If not provided, the Content-Type will be guessed by its extension.99 100 If disposition is not None, the Content-Disposition header will be set101 to "<disposition>; filename=<name>". If name is None, it will be set102 to the basename of path. If disposition is None, no Content-Disposition103 header will be written.104 """105 106 response = cherrypy.response107 108 # If path is relative, users should fix it by making path absolute.109 # That is, CherryPy should not guess where the application root is.110 # It certainly should *not* use cwd (since CP may be invoked from a111 # variety of paths). If using static_filter, you can make your relative112 # paths become absolute by supplying a value for "static_filter.root".113 if not os.path.isabs(path):114 raise ValueError("'%s' is not an absolute path." % path)115 116 try:117 stat = os.stat(path)118 except OSError:119 if cherrypy.config.get('server.log_file_not_found', False):120 cherrypy.log(" NOT FOUND file: %s" % path, "DEBUG")121 raise cherrypy.NotFound()122 123 if os.path.isdir(path):124 # Let the caller deal with it as they like.125 raise cherrypy.NotFound()126 127 if contentType is None:128 # Set content-type based on filename extension129 ext = ""130 i = path.rfind('.')131 if i != -1:132 ext = path[i:].lower()133 contentType = mimetypes.types_map.get(ext, "text/plain")134 response.headers['Content-Type'] = contentType135 136 if not modified_since(path, stat):137 return []138 139 if disposition is not None:140 if name is None:141 name = os.path.basename(path)142 cd = "%s; filename=%s" % (disposition, name)143 response.headers["Content-Disposition"] = cd144 145 # Set Content-Length and use an iterable (file object)146 # this way CP won't load the whole file in memory147 c_len = stat.st_size148 bodyfile = open(path, 'rb')149 if getattr(cherrypy, "debug", None):150 cherrypy.log(" Found file: %s" % path, "DEBUG")151 152 # HTTP/1.0 didn't have Range/Accept-Ranges headers, or the 206 code153 if cherrypy.response.version >= "1.1":154 response.headers["Accept-Ranges"] = "bytes"155 r = httptools.getRanges(cherrypy.request.headers.get('Range'), c_len)156 if r == []:157 response.headers['Content-Range'] = "bytes */%s" % c_len158 message = "Invalid Range (first-byte-pos greater than Content-Length)"159 raise cherrypy.HTTPError(416, message)160 if r:161 if len(r) == 1:162 # Return a single-part response.163 start, stop = r[0]164 r_len = stop - start165 response.status = "206 Partial Content"166 response.headers['Content-Range'] = ("bytes %s-%s/%s" %167 (start, stop - 1, c_len))168 response.headers['Content-Length'] = r_len169 bodyfile.seek(start)170 response.body = bodyfile.read(r_len)171 else:172 # Return a multipart/byteranges response.173 response.status = "206 Partial Content"174 boundary = mimetools.choose_boundary()175 ct = "multipart/byteranges; boundary=%s" % boundary176 response.headers['Content-Type'] = ct177 ## del response.headers['Content-Length']178 179 def fileRanges():180 for start, stop in r:181 yield "--" + boundary182 yield "\nContent-type: %s" % contentType183 yield ("\nContent-range: bytes %s-%s/%s\n\n"184 % (start, stop - 1, c_len))185 bodyfile.seek(start)186 yield bodyfile.read((stop + 1) - start)187 yield "\n"188 # Final boundary189 yield "--" + boundary190 response.body = fileRanges()191 else:192 response.headers['Content-Length'] = c_len193 response.body = bodyfile194 else:195 response.headers['Content-Length'] = c_len196 response.body = bodyfile197 return response.body198 58 199 59 def fileGenerator(input, chunkSize=65536): … … 204 64 chunk = input.read(chunkSize) 205 65 input.close() 66 206 67 207 68 def modules(modulePath): … … 236 97 237 98 238 class WSGIApp(object):239 """a convenience class that uses the WSGIAppFilter240 241 to easily add a WSGI application to the CP object tree.242 243 example:244 cherrypy.tree.mount(SomeRoot(), '/')245 cherrypy.tree.mount(WSGIApp(other_wsgi_app), '/ext_app')246 """247 def __init__(self, app, env_update=None):248 self._cpFilterList = [WSGIAppFilter(app, env_update)]249 250 251 99 # public domain "unrepr" implementation, found on the web and then improved. 252 100 import compiler … … 329 177 return Builder().build(getObj(s)) 330 178 179 180 # Old filter code 181 182 def base_url(base=None, use_x_forwarded_host=True): 183 """Change the base URL. 184 185 Useful when running a CP server behind Apache. 186 """ 187 188 request = cherrypy.request 189 190 if base is None: 191 port = str(cherrypy.config.get('server.socket_port', '80')) 192 if port == "80": 193 base = 'http://localhost' 194 else: 195 base = 'http://localhost:%s' % port <
