Changeset 615
- Timestamp:
- 09/08/05 10:40:38
- Files:
-
- branches/mikerobi-experimental (modified) (1 prop)
- branches/mikerobi-experimental/CHANGELOG.txt (modified) (1 diff)
- branches/mikerobi-experimental/CHERRYPYTEAM.txt (modified) (1 diff)
- branches/mikerobi-experimental/MANIFEST.in (modified) (1 diff)
- branches/mikerobi-experimental/cherrypy (modified) (1 prop)
- branches/mikerobi-experimental/cherrypy/__init__.py (modified) (3 diffs)
- branches/mikerobi-experimental/cherrypy/_cpcgifs.py (modified) (1 diff)
- branches/mikerobi-experimental/cherrypy/_cperror.py (modified) (6 diffs)
- branches/mikerobi-experimental/cherrypy/_cphttpserver.py (modified) (7 diffs)
- branches/mikerobi-experimental/cherrypy/_cphttptools.py (modified) (21 diffs)
- branches/mikerobi-experimental/cherrypy/_cputil.py (modified) (10 diffs)
- branches/mikerobi-experimental/cherrypy/_cpwsgi.py (modified) (7 diffs)
- branches/mikerobi-experimental/cherrypy/_cpwsgiserver.py (modified) (5 diffs)
- branches/mikerobi-experimental/cherrypy/config.py (modified) (9 diffs)
- branches/mikerobi-experimental/cherrypy/lib (modified) (1 prop)
- branches/mikerobi-experimental/cherrypy/lib/autoreload.py (modified) (4 diffs)
- branches/mikerobi-experimental/cherrypy/lib/covercp.py (modified) (5 diffs)
- branches/mikerobi-experimental/cherrypy/lib/cptools.py (modified) (3 diffs)
- branches/mikerobi-experimental/cherrypy/lib/csauthenticate.py (modified) (1 diff)
- branches/mikerobi-experimental/cherrypy/lib/defaultformmask.py (modified) (1 diff)
- branches/mikerobi-experimental/cherrypy/lib/filter (modified) (1 prop)
- branches/mikerobi-experimental/cherrypy/lib/filter/baseurlfilter.py (modified) (1 diff)
- branches/mikerobi-experimental/cherrypy/lib/filter/cachefilter.py (modified) (2 diffs)
- branches/mikerobi-experimental/cherrypy/lib/filter/sessionauthenticatefilter.py (modified) (4 diffs)
- branches/mikerobi-experimental/cherrypy/lib/filter/sessionfilter (deleted)
- branches/mikerobi-experimental/cherrypy/lib/filter/sessionfilter.py (copied) (copied from trunk/cherrypy/lib/filter/sessionfilter.py)
- branches/mikerobi-experimental/cherrypy/lib/filter/staticfilter.py (modified) (2 diffs)
- branches/mikerobi-experimental/cherrypy/lib/filter/virtualhostfilter.py (modified) (2 diffs)
- branches/mikerobi-experimental/cherrypy/lib/filter/xmlrpcfilter.py (modified) (3 diffs)
- branches/mikerobi-experimental/cherrypy/lib/httperrors.py (copied) (copied from trunk/cherrypy/lib/httperrors.py)
- branches/mikerobi-experimental/cherrypy/lib/httptools.py (modified) (2 diffs)
- branches/mikerobi-experimental/cherrypy/lib/profiler.py (modified) (1 diff)
- branches/mikerobi-experimental/cherrypy/server.py (modified) (9 diffs)
- branches/mikerobi-experimental/cherrypy/test (modified) (1 prop)
- branches/mikerobi-experimental/cherrypy/test/helper.py (modified) (3 diffs)
- branches/mikerobi-experimental/cherrypy/test/static/has space.html (copied) (copied from trunk/cherrypy/test/static/has space.html)
- branches/mikerobi-experimental/cherrypy/test/test.py (modified) (1 diff)
- branches/mikerobi-experimental/cherrypy/test/test_baseurl_filter.py (modified) (1 diff)
- branches/mikerobi-experimental/cherrypy/test/test_cache_filter.py (modified) (1 diff)
- branches/mikerobi-experimental/cherrypy/test/test_combinedfilters.py (modified) (2 diffs)
- branches/mikerobi-experimental/cherrypy/test/test_core.py (modified) (11 diffs)
- branches/mikerobi-experimental/cherrypy/test/test_decodingencoding_filter.py (modified) (1 diff)
- branches/mikerobi-experimental/cherrypy/test/test_gzip_filter.py (modified) (3 diffs)
- branches/mikerobi-experimental/cherrypy/test/test_logdebuginfo_filter.py (modified) (2 diffs)
- branches/mikerobi-experimental/cherrypy/test/test_noserver.py (modified) (2 diffs)
- branches/mikerobi-experimental/cherrypy/test/test_objectmapping.py (modified) (2 diffs)
- branches/mikerobi-experimental/cherrypy/test/test_session_filter.py (modified) (1 diff)
- branches/mikerobi-experimental/cherrypy/test/test_static_filter.py (modified) (2 diffs)
- branches/mikerobi-experimental/cherrypy/test/test_tutorials.py (modified) (4 diffs)
- branches/mikerobi-experimental/cherrypy/test/test_virtualhost_filter.py (modified) (1 diff)
- branches/mikerobi-experimental/cherrypy/test/webtest.py (copied) (copied from trunk/cherrypy/test/webtest.py)
- branches/mikerobi-experimental/cherrypy/tutorial (modified) (1 prop)
- branches/mikerobi-experimental/cherrypy/tutorial/tut07_sessions.py (modified) (2 diffs)
- branches/mikerobi-experimental/cherrypy/tutorial/tut08_advanced_sessions.py (deleted)
- branches/mikerobi-experimental/cherrypy/tutorial/tut08_generators_and_yield.py (copied) (copied from trunk/cherrypy/tutorial/tut08_generators_and_yield.py)
- branches/mikerobi-experimental/cherrypy/tutorial/tut09_file_upload.py (copied) (copied from trunk/cherrypy/tutorial/tut09_file_upload.py)
- branches/mikerobi-experimental/cherrypy/tutorial/tut09_generators_and_yield.py (deleted)
- branches/mikerobi-experimental/cherrypy/tutorial/tut10_file_upload.py (deleted)
- branches/mikerobi-experimental/cherrypy/tutorial/tut10_http_errors.py (copied) (copied from trunk/cherrypy/tutorial/tut10_http_errors.py)
- branches/mikerobi-experimental/cherrypy/tutorial/tutorial.conf (modified) (1 diff)
- branches/mikerobi-experimental/docs/book/build.sh (modified) (1 diff)
- branches/mikerobi-experimental/docs/book/build4xslt.bat (copied) (copied from trunk/docs/book/build4xslt.bat)
- branches/mikerobi-experimental/docs/book/build4xslt.sh (copied) (copied from trunk/docs/book/build4xslt.sh)
- branches/mikerobi-experimental/docs/book/xml/apireference.xml (modified) (4 diffs)
- branches/mikerobi-experimental/docs/book/xml/appdeveloperreference.xml (modified) (1 diff)
- branches/mikerobi-experimental/docs/book/xml/buildingblog.xml (modified) (3 diffs)
- branches/mikerobi-experimental/docs/book/xml/configsystemoverview.xml (modified) (1 diff)
- branches/mikerobi-experimental/docs/book/xml/fileuploadbehavior.xml (copied) (copied from trunk/docs/book/xml/fileuploadbehavior.xml)
- branches/mikerobi-experimental/docs/book/xml/filtersexplained.xml (modified) (1 diff)
- branches/mikerobi-experimental/docs/book/xml/globaloverviewcherrypy.xml (modified) (1 diff)
- branches/mikerobi-experimental/docs/book/xml/sessions.xml (modified) (1 diff)
- branches/mikerobi-experimental/docs/book/xml/staticcontenthandling.xml (copied) (copied from trunk/docs/book/xml/staticcontenthandling.xml)
- branches/mikerobi-experimental/docs/book/xml/templateindependant.xml (modified) (1 diff)
- branches/mikerobi-experimental/docs/book/xsl/chunked.xsl (modified) (1 diff)
- branches/mikerobi-experimental/docs/book/xsl/html.xsl (modified) (1 diff)
- branches/mikerobi-experimental/setup.py (modified) (1 diff)
Legend:
- Unmodified
- Added
- Removed
- Modified
- Copied
- Moved
branches/mikerobi-experimental
- Property svn:ignore set to build
branches/mikerobi-experimental/CHANGELOG.txt
r383 r615 1 2005-08-17: 2 * The session filter has been simplified, the mechanism for creating multiple sessions has been removed. However .. (mikerobi) 3 * The session filter can placed inside a _cpFilterList to provide an alternative to the default session. (mikerobi) 4 5 2005-08-09: 6 * The config section [global] is now distinct from, and the parent of, [/]. (fumanchu) 7 * cherrypy.request.path is now parsed and set before onStartResource filter methods are called. (fumanchu) 8 * Absolute Request-URI's are now acceptable, and are converted to relative paths. (fumanchu) 9 * A Request-URI of "*" now maps to the [global] config section, and to cherrypy.root._global. (fumanchu) 10 11 2005-07-12: 12 * CherryPy-2.1.0-beta released. Check http://www.cherrypy.org/wiki/WhatsNewIn21 for instructions on how to upgrade from 2.0. (remi) 13 14 2005-07-09: 15 * Request entities that are not form params now get stuck in cherrypy.request.body (as a temp file object). (fumanchu) 16 * cherrypy.config.update can be called with an the keyword override=False to prevent values from being overwritten. 17 2005-07-06: 18 * Added code-coverage tools. (fumanchu) 19 1 20 2005-06-25: 2 21 * BACKWARD INCOMPATIBILITY: Removed cpg module, renamed _cpserver to server, _cpconfig to config, cperror to _cperror. See http://www.cherrypy.org/wiki/PackageLayout21 (fumanchu) 3 22 4 23 2005-06-21: 5 * New generic HTTPRedirect exception for 3xx responses (fumanchu)24 * New generic HTTPRedirect exception for 3xx responses. (fumanchu) 6 25 7 26 2005-06-17: 8 * Handle all HTTP methods (fumanchu)27 * Handle all HTTP methods. (fumanchu) 9 28 10 29 2005-06-16: branches/mikerobi-experimental/CHERRYPYTEAM.txt
r278 r615 1 See http:// trac.cherrypy.org/wiki/CherryPyTeam1 See http://www.cherrypy.org/wiki/CherryPyTeam branches/mikerobi-experimental/MANIFEST.in
r290 r615 1 1 include CHANGELOG.txt 2 2 include CHERRYPYTEAM.txt 3 include cherrypy/favicon.ico 3 4 include cherrypy/tutorial/*.conf 4 5 include cherrypy/tutorial/README.txt branches/mikerobi-experimental/cherrypy
- Property svn:ignore set to *.pyc .*.swp
branches/mikerobi-experimental/cherrypy/__init__.py
r418 r615 31 31 """ 32 32 33 __version__ = '2.1.0 alpha' 33 __version__ = '2.1.0-beta' 34 35 import datetime 34 36 35 37 from _cperror import * 36 37 38 import config 38 39 import server … … 60 61 threadData = local() 61 62 63 # Create variables needed for session (see lib/sessionfilter.py for more info) 64 from lib.filter import sessionfilter 65 session = sessionfilter.SessionWrapper() 66 _sessionDataHolder = {} # Needed for RAM sessions only 67 _sessionLockDict = {} # Needed for RAM sessions only 68 _sessionLastCleanUpTime = datetime.datetime.now() 69 62 70 # decorator function for exposing methods 63 71 def expose(func): … … 66 74 67 75 def log(msg, context='', severity=0): 68 """Syntactic sugar for writing to the log.""" 76 """Syntactic sugar for writing to the (error) log.""" 77 # Load _cputil lazily to avoid circular references, and 78 # to allow profiler and coverage tools to work on it. 69 79 import _cputil 70 80 logfunc = _cputil.getSpecialAttribute('_cpLogMessage') branches/mikerobi-experimental/cherrypy/_cpcgifs.py
r321 r615 1 1 import cgi 2 import cherrypy 2 3 4 try: 5 from threading import local 6 except ImportError: 7 from cherrypy._cpthreadinglocal import local 8 9 class LocalInt: 10 def __init__(self, value): 11 self.__local = local() 12 self.__local.value = value 13 14 def setValue(self, value): 15 self.__local.value = value 16 17 def __int__(self): 18 return self.__local.value 19 20 def __nonzero__(self): 21 return bool(self.__local.value) 22 23 def __str__(self): 24 return str(self.__local.value) 25 26 cgi.maxlen = LocalInt(0) 3 27 4 28 class FieldStorage(cgi.FieldStorage): 5 29 def __init__(self, *args, **kwds): 30 maxlen = cherrypy.config.get('server.maxRequestSize') 31 cgi.maxlen.setValue(maxlen) 32 try: 33 cgi.FieldStorage.__init__(self, *args, **kwds) 34 except ValueError: 35 raise cherrypy.HTTPStatusError(status=413) 36 6 37 def read_lines_to_eof(self): 7 38 """Internal: read lines until EOF.""" branches/mikerobi-experimental/cherrypy/_cperror.py
r405 r615 41 41 pass 42 42 43 class NotFound(Error):44 """ Happens when a URL couldn't be mapped to any class.method """45 pass46 47 43 class WrongResponseType(Error): 48 44 """ Happens when the cherrypy.response.body is not a string """ … … 76 72 import cgi 77 73 74 # Set a 'path' member attribute so that code which traps this 75 # error can have access to it. 78 76 self.path = path 77 79 78 if params is not None: 80 79 if isinstance(params, basestring): … … 86 85 cherrypy.request.paramMap = pm 87 86 else: 87 cherrypy.request.queryString = urllib.urlencode(params) 88 88 cherrypy.request.paramMap = params.copy() 89 cherrypy.request.queryString = urllib.urlencode(params)89 90 90 cherrypy.request.browserUrl = cherrypy.request.base + path 91 91 … … 96 96 97 97 The new URL must be passed as the first argument to the Exception, e.g., 98 cperror.HTTPRedirect(newUrl). Multiple URLs are allowed. 98 cperror.HTTPRedirect(newUrl). Multiple URLs are allowed. If a URL 99 is absolute, it will be used as-is. If it is relative, it is assumed 100 to be relative to the current cherrypy.request.path. 99 101 """ 100 102 … … 108 110 abs_urls = [] 109 111 for url in urls: 110 if url.startswith("/"): 111 url = urlparse.urljoin(cherrypy.request.base, url) 112 # Note that urljoin will "do the right thing" whether url is: 113 # 1. a complete URL with host (e.g. "http://www.dummy.biz/test") 114 # 2. a URL relative to root (e.g. "/dummy") 115 # 3. a URL relative to the current path 116 url = urlparse.urljoin(cherrypy.request.browserUrl, url) 112 117 abs_urls.append(url) 113 118 self.urls = abs_urls 114 119 115 120 # RFC 2616 indicates a 301 response code fits our goal; however, 116 # browser support for 301 is quite messy. Do 302 instead. 121 # browser support for 301 is quite messy. Do 302 instead. See 117 122 # http://ppewww.ph.gla.ac.uk/~flavell/www/post-redirect.html 118 123 if status is None: 119 if cherrypy.request. protocol == "HTTP/1.1":124 if cherrypy.request.version >= "1.1": 120 125 status = 303 121 126 else: … … 164 169 else: 165 170 raise ValueError("The %s status code is unknown." % status) 171 172 173 _missing = object() 174 175 class HTTPStatusError(Error): 176 """Exception raised when the client has made an error in its request.""" 177 178 def __init__(self, status=400, message=_missing): 179 self.status = status = int(status) 180 if status < 400 or status > 599: 181 raise ValueError("status must be between 400 and 599.") 182 183 # these 4 lines might dissapear 184 import cherrypy 185 cherrypy.response.status = status 186 if message is not _missing: 187 cherrypy.response.body=message 188 189 self.message = message 190 191 def getArgs(self): 192 return (self.status, self.message) 193 194 195 class NotFound(HTTPStatusError): 196 """ Happens when a URL couldn't be mapped to any class.method """ 197 198 def __init__(self, path): 199 self.args = (path,) 200 HTTPStatusError.__init__(self, 404) branches/mikerobi-experimental/cherrypy/_cphttpserver.py
r388 r615 27 27 """ 28 28 29 import threading, SocketServer, BaseHTTPServer, socket, Queue 29 import threading, os, socket 30 import SocketServer, BaseHTTPServer, Queue 30 31 import cherrypy 31 32 from cherrypy import _cputil, _cphttptools … … 83 84 cherrypy.request.multithread = cherrypy.config.get("server.threadPool") > 1 84 85 cherrypy.request.multiprocess = False 85 cherrypy.server.request(self.client_address [0],86 cherrypy.server.request(self.client_address, 86 87 self.address_string(), 87 88 self.raw_requestline, … … 91 92 wfile.write("%s %s\r\n" % (self.protocol_version, cherrypy.response.status)) 92 93 94 has_close_conn = False 93 95 for name, value in cherrypy.response.headers: 94 96 wfile.write("%s: %s\r\n" % (name, value)) 97 if name.lower == 'connection' and value.lower == 'close': 98 has_close_conn = True 99 if not has_close_conn: 100 # This server doesn't support persistent connections yet, so we 101 # must add a "Connection: close" header to tell the client that 102 # we will close the connection when we're done sending output. 103 # 104 # From RFC 2616 sec 14.10: 105 # HTTP/1.1 defines the "close" connection option for the sender 106 # to signal that the connection will be closed after completion 107 # of the response. For example, 108 # 109 # Connection: close 110 # 111 # in either the request or the response header fields indicates 112 # that the connection SHOULD NOT be considered `persistent' 113 # (section 8.1) after the current request/response is complete. 114 # 115 # HTTP/1.1 applications that do not support persistent connections 116 # MUST include the "close" connection option in every message. 117 wfile.write("Connection: close\r\n") 95 118 96 119 wfile.write("\r\n") … … 105 128 if self.command == "POST": 106 129 self.connection = self.request 130 131 # Close the conn, since we do not yet support persistent connections. 132 self.close_connection = 1 107 133 108 134 def log_message(self, format, *args): … … 112 138 113 139 class CherryHTTPServer(BaseHTTPServer.HTTPServer): 140 141 def __init__(self): 142 # Set protocol_version 143 proto = cherrypy.config.get('server.protocolVersion') or "HTTP/1.0" 144 CherryHTTPRequestHandler.protocol_version = proto 145 146 # Select the appropriate server based on config options 147 sockFile = cherrypy.config.get('server.socketFile') 148 if sockFile: 149 # AF_UNIX socket 150 self.address_family = socket.AF_UNIX 151 152 # So we can reuse the socket 153 try: os.unlink(sockFile) 154 except: pass 155 156 # So everyone can access the socket 157 try: os.chmod(sockFile, 0777) 158 except: pass 159 160 server_address = sockFile 161 else: 162 # AF_INET socket 163 server_address = (cherrypy.config.get('server.socketHost'), 164 cherrypy.config.get('server.socketPort')) 165 166 self.request_queue_size = cherrypy.config.get('server.socketQueueSize') 167 168 BaseHTTPServer.HTTPServer.__init__(self, server_address, CherryHTTPRequestHandler) 114 169 115 170 def server_activate(self): … … 211 266 allow_reuse_address = 1 212 267 213 def __init__(self, serverAddress, numThreads, RequestHandlerClass, ThreadClass=ServerThread): 214 assert(numThreads > 0) 268 def __init__(self): 269 # Set protocol_version 270 proto = cherrypy.config.get('server.protocolVersion') or "HTTP/1.0" 271 CherryHTTPRequestHandler.protocol_version = proto 272 273 # Select the appropriate server based on config options 274 threadPool = cherrypy.config.get('server.threadPool') 275 server_address = (cherrypy.config.get('server.socketHost'), 276 cherrypy.config.get('server.socketPort')) 277 self.request_queue_size = cherrypy.config.get('server.socketQueueSize') 215 278 216 279 # I know it says "do not override", but I have to in order to implement SSL support ! 217 SocketServer.BaseServer.__init__(self, server Address, RequestHandlerClass)218 self.socket =socket.socket(self.address_family, self.socket_type)280 SocketServer.BaseServer.__init__(self, server_address, CherryHTTPRequestHandler) 281 self.socket = socket.socket(self.address_family, self.socket_type) 219 282 self.server_bind() 220 283 self.server_activate() 221 284 222 self._numThreads = numThreads223 self._RequestHandlerClass = RequestHandlerClass224 self._ThreadClass = ThreadClass285 self._numThreads = threadPool 286 self._RequestHandlerClass = CherryHTTPRequestHandler 287 self._ThreadClass = ServerThread 225 288 self._requestQueue = Queue.Queue() 226 289 self._workerThreads = [] … … 296 359 """Selects and instantiates the appropriate server.""" 297 360 298 # Set protocol_version299 proto = cherrypy.config.get('server.protocolVersion')300 if not proto:301 proto = "HTTP/1.0"302 CherryHTTPRequestHandler.protocol_version = proto303 304 361 # Select the appropriate server based on config options 305 362 sockFile = cherrypy.config.get('server.socketFile') 306 363 threadPool = cherrypy.config.get('server.threadPool') 307 if sockFile: 308 # AF_UNIX socket 309 # TODO: Handle threading here 310 class ServerClass(CherryHTTPServer): address_family = socket.AF_UNIX 311 312 # So we can reuse the socket 313 try: os.unlink(sockFile) 314 except: pass 315 316 server_address = sockFile 364 if threadPool > 1 and not sockFile: 365 ServerClass = PooledThreadServer 317 366 else: 318 # AF_INET socket 319 if threadPool > 1: 320 ServerClass = PooledThreadServer 321 else: 322 ServerClass = CherryHTTPServer 323 server_address = (cherrypy.config.get('server.socketHost'), 324 cherrypy.config.get('server.socketPort')) 325 326 ServerClass.request_queue_size = cherrypy.config.get('server.socketQueueSize') 327 328 if handler is None: 329 handler = CherryHTTPRequestHandler 330 331 if threadPool > 1: 332 myCherryHTTPServer = ServerClass(server_address, threadPool, handler) 333 else: 334 myCherryHTTPServer = ServerClass(server_address, handler) 335 336 # So everyone can access the socket 337 if sockFile: 338 try: os.chmod(sockFile, 0777) 339 except: pass 340 341 return myCherryHTTPServer 342 367 ServerClass = CherryHTTPServer 368 return ServerClass() 369 branches/mikerobi-experimental/cherrypy/_cphttptools.py
r422 r615 31 31 """ 32 32 33 import urllib, os, sys, time, types, cgi 34 import mimetypes, Cookie, urlparse 33 import cgi 34 35 from BaseHTTPServer import BaseHTTPRequestHandler 36 responseCodes = BaseHTTPRequestHandler.responses 37 38 import Cookie 39 import os 40 import re 41 import sys 42 import types 43 import urllib 44 from urlparse import urlparse 35 45 36 46 import cherrypy 37 47 from cherrypy import _cputil, _cpcgifs 38 39 # Can't use cStringIO; doesn't support unicode strings 40 # See http://www.python.org/sf/216388 41 import StringIO 42 43 from BaseHTTPServer import BaseHTTPRequestHandler 44 responseCodes = BaseHTTPRequestHandler.responses 45 46 mimetypes.types_map['.dwg']='image/x-dwg' 47 mimetypes.types_map['.ico']='image/x-icon' 48 49 weekdayname = ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'] 50 monthname = [None, 'Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'] 48 from cherrypy.lib import cptools 49 50 51 class Version(object): 52 53 """A version, such as "2.1 beta 3", which can be compared atom-by-atom. 54 55 If a string is provided to the constructor, it will be split on word 56 boundaries; that is, "1.4.13 beta 9" -> ["1", "4", "13", "beta", "9"]. 57 58 Comparisons are performed atom-by-atom, numerically if both atoms are 59 numeric. Therefore, "2.12" is greater than "2.4", and "3.0 beta" is 60 greater than "3.0 alpha" (only because "b" > "a"). If an atom is 61 provided in one Version and not another, the longer Version is 62 greater than the shorter, that is: "4.8 alpha" > "4.8". 63 """ 64 65 def __init__(self, atoms): 66 """A Version object. A str argument will be split on word boundaries.""" 67 if isinstance(atoms, basestring): 68 self.atoms = re.split(r'\W', atoms) 69 else: 70 self.atoms = [str(x) for x in atoms] 71 72 def from_http(cls, version_str): 73 """Return a Version object from the given 'HTTP/x.y' string.""" 74 return cls(version_str[5:]) 75 from_http = classmethod(from_http) 76 77 def to_http(self): 78 """Return a 'HTTP/x.y' string for this Version object.""" 79 return "HTTP/%s.%s" % tuple(self.atoms[:2]) 80 81 def __str__(self): 82 return ".".join([str(x) for x in self.atoms]) 83 84 def __cmp__(self, other): 85 cls = self.__class__ 86 if not isinstance(other, cls): 87 # Try to coerce other to a Version instance. 88 other = cls(other) 89 90 index = 0 91 while index < len(self.atoms) and index < len(other.atoms): 92 mine, theirs = self.atoms[index], other.atoms[index] 93 if mine.isdigit() and theirs.isdigit(): 94 mine, theirs = int(mine), int(theirs) 95 if mine < theirs: 96 return -1 97 if mine > theirs: 98 return 1 99 index += 1 100 if index < len(other.atoms): 101 return -1 102 if index < len(self.atoms): 103 return 1 104 return 0 51 105 52 106 53 107 class KeyTitlingDict(dict): 54 """dict subclass which changes each key to str(key).title() 55 56 This should allow response headers to be case-insensitive and 108 109 """A dict subclass which changes each key to str(key).title() 110 111 This allows response headers to be case-insensitive and 57 112 avoid duplicates. 113 58 114 """ 59 115 … … 100 156 101 157 class Request(object): 102 """Process a request and yield a series of response chunks. 103 104 headers should be a list of (name, value) tuples. 105 """ 158 159 """Process an HTTP request and set cherrypy.response attributes.""" 106 160 107 161 def __init__(self, clientAddress, remoteHost, requestLine, headers, 108 162 rfile, scheme="http"): 109 # When __init__ is finished, cherrypy.response should have three attributes: 110 # status, e.g. "200 OK" 111 # headers, a list of (name, value) tuples 112 # body, an iterable yielding strings 113 # Consumer code should then access these three attributes 114 # to build the outbound stream. 115 116 self.requestLine = requestLine 163 """Populate a new Request object. 164 165 clientAddress should be a tuple of client IP address, client Port 166 remoteHost should be string of the client's IP address. 167 requestLine should be of the form "GET /path HTTP/1.0". 168 headers should be a list of (name, value) tuples. 169 rfile should be a file-like object containing the HTTP request 170 entity. 171 scheme should be a string, either "http" or "https". 172 173 When __init__ is done, cherrypy.response should have 3 attributes: 174 status, e.g. "200 OK" 175 headers, a list of (name, value) tuples 176 body, an iterable yielding strings 177 178 Consumer code (HTTP servers) should then access these response 179 attributes to build the outbound stream. 180 181 """ 182 183 cherrypy.request.method = "" 184 cherrypy.request.requestLine = requestLine.strip() 185 self.parseFirstLine() 186 117 187 self.requestHeaders = headers 118 188 119 189 # Prepare cherrypy.request variables 120 cherrypy.request.remoteAddr = clientAddress 190 cherrypy.request.remoteAddr = clientAddress[0] 191 cherrypy.request.remotePort = clientAddress[1] 121 192 cherrypy.request.remoteHost = remoteHost 122 193 cherrypy.request.paramList = [] # Only used for Xml-Rpc 123 194 cherrypy.request.headerMap = {} 124 cherrypy.request.requestLine = requestLine125 195 cherrypy.request.simpleCookie = Cookie.SimpleCookie() 126 196 cherrypy.request.rfile = rfile … … 132 202 cherrypy.response.body = None 133 203 134 year, month, day, hh, mm, ss, wd, y, z = time.gmtime()135 date = ("%s, %02d %3s %4d %02d:%02d:%02d GMT" %136 (weekdayname[wd], day, monthname[month], year, hh, mm, ss))137 204 cherrypy.response.headerMap = KeyTitlingDict() 138 205 cherrypy.response.headerMap.update({ 139 206 "Content-Type": "text/html", 140 207 "Server": "CherryPy/" + cherrypy.__version__, 141 "Date": date,208 "Date": cptools.HTTPDate(), 142 209 "Set-Cookie": [], 143 "Content-Length": 0210 "Content-Length": None 144 211 }) 145 212 cherrypy.response.simpleCookie = Cookie.SimpleCookie() … … 150 217 # HEAD requests MUST NOT return a message-body in the response. 151 218 cherrypy.response.body = [] 219 220 _cputil.getSpecialAttribute("_cpLogAccess")() 221 222 def parseFirstLine(self): 223 # This has to be done very early in the request process, 224 # because request.path is used for config lookups right away. 225 request = cherrypy.request 226 227 # Parse first line 228 request.method, path, request.protocol = request.requestLine.split() 229 request.processRequestBody = request.method in ("POST", "PUT") 230 231 # separate the queryString, or set it to "" if not found 232 if "?" in path: 233 path, request.queryString = path.split("?", 1) 234 else: 235 path, request.queryString = path, "" 236 237 # Unquote the path (e.g. "/this%20path" -> "this path"). 238 # http://www.w3.org/Protocols/rfc2616/rfc2616-sec5.html#sec5.1.2 239 # Note that cgi.parse_qs will decode the querystring for us. 240 path = urllib.unquote(path) 241 242 if path == "*": 243 # "...the request does not apply to a particular resource, 244 # but to the server itself". See 245 # http://www.w3.org/Protocols/rfc2616/rfc2616-sec5.html#sec5.1.2 246 path = "global" 247 elif not path.startswith("/"): 248 # path is an absolute path (including "http://host.domain.tld"); 249 # convert it to a relative path, so configMap lookups work. This 250 # default method assumes all hosts are valid for this server. 251 scheme, location, p, pm, q, f = urlparse(path) 252 path = path[len(scheme + "://" + location):] 253 254 # Save original value (in case it gets modified by filters) 255 request.path = request.originalPath = path 256 257 # Change objectPath in filters to change 258 # the object that will get rendered 259 request.objectPath = None 152 260 153 261 def run(self): 262 """Process the Request.""" 154 263 try: 155 264 try: … … 179 288 applyFilters('onEndResource') 180 289 except: 290 # This includes HTTPStatusError and NotFound 181 291 handleError(sys.exc_info()) 182 292 183 293 def processRequestHeaders(self): 184 req = cherrypy.request 185 186 # Parse first line 187 req.method, path, req.protocol = self.requestLine.split() 188 req.processRequestBody = req.method in ("POST", "PUT") 189 190 # find the queryString, or set it to "" if not found 191 if "?" in path: 192 req.path, req.queryString = path.split("?", 1) 193 else: 194 req.path, req.queryString = path, "" 294 request = cherrypy.request 295 296 # Compare request and server HTTP versions, in case our server does 297 # not support the requested version. We can't tell the server what 298 # version number to write in the response, so we limit our output 299 # to min(req, server). We want the following output: 300 # request version server version response version features 301 # a 1.0 1.0 1.0 1.0 302 # b 1.0 1.1 1.1 1.0 303 # c 1.1 1.0 1.0 1.0 304 # d 1.1 1.1 1.1 1.1 305 # Notice that, in (b), the response will be "HTTP/1.1" even though 306 # the client only understands 1.0. RFC 2616 10.5.6 says we should 307 # only return 505 if the _major_ version is different. 308 request_v = Version.from_http(request.protocol) 309 server_v = cherrypy.config.get("server.protocolVersion", "HTTP/1.0") 310 server_v = Version.from_http(server_v) 311 cherrypy.request.version = min(request_v, server_v) 195 312 196 313 # build a paramMap dictionary from queryString 197 pm = cgi.parse_qs(req.queryString, keep_blank_values=True) 198 for key, val in pm.items(): 199 if len(val) == 1: 200 pm[key] = val[0] 201 req.paramMap = pm 314 if re.match(r"[0-9]+,[0-9]+", request.queryString): 315 # Server-side image map. Map the coords to 'x' and 'y' 316 # (like CGI::Request does). 317 pm = request.queryString.split(",") 318 pm = {'x': int(pm[0]), 'y': int(pm[1])} 319 else: 320 pm = cgi.parse_qs(request.queryString, keep_blank_values=True) 321 for key, val in pm.items(): 322 if len(val) == 1: 323 pm[key] = val[0] 324 request.paramMap = pm 202 325 203 326 # Process the headers into request.headerMap … … 208 331 # only Konqueror does that), only the last one will remain in headerMap 209 332 # (but they will be correctly stored in request.simpleCookie). 210 req .headerMap[name] = value333 request.headerMap[name] = value 211 334 212 # Handle cookies differently because on Konqueror, multiple cookies213 # co me on different lines with the same key335 # Handle cookies differently because on Konqueror, multiple 336 # cookies come on different lines with the same key 214 337 if name == 'Cookie': 215 req.simpleCookie.load(value) 216 217 msg = "%s - %s" % (req.remoteAddr, self.requestLine.strip()) 218 cherrypy.log(msg, "HTTP") 219 220 req.base = "%s://%s" % (req.scheme, req.headerMap.get('Host', '')) 221 req.browserUrl = req.base + path 222 223 # Change objectPath in filters to change 224 # the object that will get rendered 225 req.objectPath = None 338 request.simpleCookie.load(value) 339 340 # Write a message to the error.log only if there is no access.log. 341 # This is only here for backwards-compatibility (with the time 342 # before the access.log existed), and should be removed in CP 2.2. 343 fname = cherrypy.config.get('server.logAccessFile', '') 344 if not fname: 345 msg = "%s - %s" % (request.remoteAddr, request.requestLine) 346 cherrypy.log(msg, "HTTP") 226 347 227 348 # Save original values (in case they get modified by filters) 228 req.originalPath = req.path 229 req.originalParamMap = req.paramMap 230 req.originalParamList = req.paramList 349 request.originalParamMap = request.paramMap 350 request.originalParamList = request.paramList 351 352 if cherrypy.request.version >= "1.1": 353 # All Internet-based HTTP/1.1 servers MUST respond with a 400 354 # (Bad Request) status code to any HTTP/1.1 request message 355 # which lacks a Host header field. 356 if not request.headerMap.has_key("Host"): 357 cherrypy.response.status = 400 358 cherrypy.response.body = ["HTTP/1.1 requires a 'Host' request header."] 359 finalize() 360 raise cherrypy.RequestHandled() 361 request.base = "%s://%s" % (request.scheme, request.headerMap.get('Host', '')) 362 request.browserUrl = request.base + request.path 231 363 232 364 def processRequestBody(self): 233 req = cherrypy.request365 request = cherrypy.request 234 366 235 367 # Create a copy of headerMap with lowercase keys because 236 368 # FieldStorage doesn't work otherwise 237 369 lowerHeaderMap = {} 238 for key, value in req .headerMap.items():370 for key, value in request.headerMap.items(): 239 371 lowerHeaderMap[key.lower()] = value 240 372 241 methenv = {'REQUEST_METHOD': req.method} 242 forms = _cpcgifs.FieldStorage(fp=req.rfile, 373 # FieldStorage only recognizes POST, so fake it. 374 methenv = {'REQUEST_METHOD': "POST"} 375 forms =
