Changeset 967
- Timestamp:
- 02/14/06 15:33:41
- Files:
-
- trunk/cherrypy/_cpengine.py (added)
- trunk/cherrypy/_cphttpserver.py (modified) (8 diffs)
- trunk/cherrypy/_cpserver.py (modified) (8 diffs)
- trunk/cherrypy/_cpwsgiserver.py (modified) (1 diff)
- trunk/cherrypy/test/test_core.py (modified) (3 diffs)
- trunk/cherrypy/test/test_logdebuginfo_filter.py (modified) (2 diffs)
- trunk/cherrypy/test/test_states.py (modified) (4 diffs)
Legend:
- Unmodified
- Added
- Removed
- Modified
- Copied
- Moved
trunk/cherrypy/_cphttpserver.py
r959 r967 186 186 187 187 self.request_queue_size = cherrypy.config.get('server.socket_queue_size') 188 189 def server_bind(self): 190 """Called by constructor to bind the socket.""" 188 191 189 192 # Select the appropriate server based on config options … … 203 206 self.server_address = sockFile 204 207 self.socket = socket.socket(self.address_family, self.socket_type) 205 self. server_bind()208 self._server_bind() 206 209 else: 207 210 # AF_INET or AF_INET6 socket … … 217 220 # Must...refuse...temptation..to..guess... 218 221 self.socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 219 self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) 220 self.socket.bind(self.server_address) 222 self._server_bind() 221 223 else: 222 224 # Get the correct address family for our host (allows IPv6 addresses) … … 227 229 try: 228 230 self.socket = socket.socket(af, socktype, proto) 229 self. server_bind()231 self._server_bind() 230 232 except socket.error, msg: 231 233 if self.socket: … … 236 238 if not self.socket: 237 239 raise socket.error, msg 238 239 self.server_activate() 240 241 def _server_bind(self): 242 if self.allow_reuse_address: 243 self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) 244 self.socket.bind(self.server_address) 240 245 241 246 def server_activate(self): … … 243 248 self.socket.settimeout(1) 244 249 self.socket.listen(self.request_queue_size) 245 246 def server_bind(self):247 """Called by constructor to bind the socket."""248 if self.allow_reuse_address:249 self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)250 self.socket.bind(self.server_address)251 250 252 251 def close_request(self, request): … … 295 294 self.handle_request() 296 295 self.server_close() 297 start = serve_forever 296 297 def start(self): 298 self.server_bind() 299 self.server_activate() 300 self.serve_forever() 298 301 299 302 def server_close(self): … … 346 349 347 350 CherryHTTPServer.serve_forever(self) 348 start = serve_forever349 351 350 352 def server_close(self): trunk/cherrypy/_cpserver.py
r966 r967 1 1 """Create and manage the CherryPy server.""" 2 2 3 import cgi4 import sys5 3 import threading 6 4 import time 7 import warnings8 5 9 6 import cherrypy 10 from cherrypy import _cphttptools, filters, _cpwsgi 11 from cherrypy.lib import autoreload, profiler, cptools 12 13 # Use a flag to indicate the state of the application server. 14 STOPPED = 0 15 STARTING = None 16 STARTED = 1 7 from cherrypy import _cphttptools 8 from cherrypy.lib import cptools 9 10 from cherrypy._cpengine import Engine, STOPPED, STARTING, STARTED 17 11 18 12 _missing = object() 19 13 20 14 21 class Server(object): 22 23 request_class = _cphttptools.Request 15 class Server(Engine): 24 16 25 17 def __init__(self): 26 self.state = STOPPED 27 self.seen_threads = {} 18 Engine.__init__(self) 19 self._is_setup = False 20 self.blocking = True 28 21 29 22 self.httpserver = None 23 # Starting in 2.2, the "httpserverclass" attr is essentially dead; 24 # no CP code uses it. Inspect "httpserver" instead. 30 25 self.httpserverclass = None 31 self.interrupt = None 32 33 # Set some special attributes for adding hooks 34 self.on_start_server_list = [] 35 self.on_start_thread_list = [] 36 self.on_stop_server_list = [] 37 self.on_stop_thread_list = [] 38 26 39 27 # Backward compatibility: 40 self.onStopServerList = [] 41 self.onStartThreadList = [] 42 self.onStartServerList = [] 43 self.onStopThreadList = [] 44 45 self.wsgi_app = _cpwsgi.wsgiApp 46 47 def start(self, init_only = False, server_class = _missing, **kwargs): 28 self.onStopServerList = self.on_stop_server_list 29 self.onStartThreadList = self.on_start_thread_list 30 self.onStartServerList = self.on_start_server_list 31 self.onStopThreadList = self.on_stop_thread_list 32 33 def start(self, init_only=False, server_class=_missing, server=None, **kwargs): 48 34 """Main function. MUST be called from the main thread. 49 35 50 36 Set initOnly to True to keep this function from blocking. 51 Set serverClass to None to skip starting any HTTP server.37 Set serverClass and server to None to skip starting any HTTP server. 52 38 """ 53 39 … … 58 44 server_class = kwargs['serverClass'] 59 45 46 conf = cherrypy.config.get 47 if server is None: 48 if server_class is _missing: 49 server_class = conf("server.class", _missing) 50 if server_class is _missing: 51 import _cpwsgi 52 server_class = _cpwsgi.WSGIServer 53 elif server_class and isinstance(server_class, basestring): 54 # Dynamically load the class from the given string 55 server_class = cptools.attributes(server_class) 56 self.httpserverclass = server_class 57 if server_class is not None: 58 self.httpserver = server_class() 59 else: 60 self.httpserverclass = server.__class__ 61 self.httpserver = server 62 63 self.blocking = not init_only 64 Engine.start(self) 65 66 def _start(self): 67 if not self._is_setup: 68 self.setup() 69 self._is_setup = True 70 Engine._start(self) 71 self.start_http_server() 72 if self.blocking: 73 self.block() 74 75 def restart(self): 76 """Restart the application server engine.""" 77 self.stop() 60 78 self.state = STARTING 61 79 self.interrupt = None 62 63 conf = cherrypy.config.get64 65 if server_class is _missing:66 server_class = conf("server.class", _missing)67 if server_class is _missing:68 import _cpwsgi69 server_class = _cpwsgi.WSGIServer70 elif server_class and isinstance(server_class, basestring):71 # Dynamically load the class from the given string72 server_class = cptools.attributes(server_class)73 74 self.blocking = not init_only75 self.httpserverclass = server_class76 77 # Hmmm...we *could* check config in _start instead, but I think78 # most people would like CP to fail before autoreload kicks in.79 check_config()80 81 # Autoreload, but check server_class. If None, we're not starting82 # our own webserver, and therefore could do Very Bad Things when83 # autoreload calls sys.exit.84 if server_class is not None:85 if conf('autoreload.on', False):86 try:87 freq = conf('autoreload.frequency', 1)88 autoreload.main(self._start, freq=freq)89 except KeyboardInterrupt:90 cherrypy.log("<Ctrl-C> hit: shutting down autoreloader", "HTTP")91 self.stop()92 except SystemExit:93 cherrypy.log("SystemExit raised: shutting down autoreloader", "HTTP")94 self.stop()95 # We must raise here: if this is a process spawned by96 # autoreload, then it must return its error code to97 # the parent.98 raise99 return100 101 80 self._start() 102 103 def _start(self):104 # Output config options to log105 if cherrypy.config.get("server.log_config_options", True):106 cherrypy.config.outputConfigMap()107 108 try:109 configure()110 111 for func in cherrypy.server.on_start_server_list + cherrypy.server.onStartServerList:112 func()113 self.start_http_server()114 self.state = STARTED115 116 if self.blocking:117 # Block forever (wait for KeyboardInterrupt or SystemExit).118 while True:119 time.sleep(.1)120 if self.interrupt:121 raise self.interrupt122 except KeyboardInterrupt:123 cherrypy.log("<Ctrl-C> hit: shutting down server", "HTTP")124 self.stop()125 except SystemExit:126 cherrypy.log("SystemExit raised: shutting down server", "HTTP")127 self.stop()128 except:129 # Don't bother logging, since we're going to re-raise.130 self.interrupt = sys.exc_info()[1]131 self.stop()132 raise133 81 134 82 def start_http_server(self, blocking=True): 135 83 """Start the requested HTTP server.""" 136 if self.httpserver is not None: 137 msg = ("You seem to have an HTTP server still running." 138 "Please call server.stop_http_server() " 139 "before continuing.") 140 warnings.warn(msg) 141 142 if self.httpserverclass is None: 84 if not self.httpserver: 143 85 return 144 86 … … 155 97 on_what = "socket file: %s" % cherrypy.config.get('server.socket_file') 156 98 157 # Instantiate the server.158 self.httpserver = self.httpserverclass()159 160 99 # HTTP servers MUST be started in a new thread, so that the 161 # main thread persists to receive KeyboardInterrupt's. This 162 # wrapper traps an interrupt in the http server's main thread 163 # and shutdowns CherryPy. 100 # main thread persists to receive KeyboardInterrupt's. If an 101 # exception is raised in the http server's main thread then it's 102 # trapped here, and the CherryPy app server is shut down (via 103 # self.interrupt). 164 104 def _start_http(): 165 105 try: … … 167 107 except (KeyboardInterrupt, SystemExit), exc: 168 108 self.interrupt = exc 109 self.stop() 169 110 threading.Thread(target=_start_http).start() 170 111 … … 174 115 cherrypy.log("Serving HTTP on %s" % on_what, 'HTTP') 175 116 117 def wait(self): 118 """Block the caller until ready to receive requests (or error).""" 119 Engine.wait(self) 120 self.wait_for_http_ready() 121 176 122 def wait_for_http_ready(self): 177 if self.httpserver class is not None:178 while not getattr(self.httpserver, "ready", True) :123 if self.httpserver: 124 while not getattr(self.httpserver, "ready", True) and not self.interrupt: 179 125 time.sleep(.1) 180 126 … … 183 129 host = cherrypy.config.get('server.socket_host') 184 130 port = cherrypy.config.get('server.socket_port') 185 wait_for_occupied_port(host, port) 186 187 def request(self, clientAddress, remoteHost, scheme="http"): 188 """Obtain an HTTP Request object. 189 190 clientAddress: the (IP address, port) of the client 191 remoteHost: the IP address of the client 192 scheme: either "http" or "https"; defaults to "http" 193 """ 194 if self.state == STOPPED: 195 raise cherrypy.NotReady("The CherryPy server has stopped.") 196 elif self.state == STARTING: 197 raise cherrypy.NotReady("The CherryPy server could not start.") 198 199 threadID = threading._get_ident() 200 if threadID not in self.seen_threads: 201 202 if cherrypy.codecoverage: 203 from cherrypy.lib import covercp 204 covercp.start() 205 206 i = len(self.seen_threads) + 1 207 self.seen_threads[threadID] = i 208 209 for func in self.on_start_thread_list + self.onStartThreadList: 210 func(i) 211 212 r = self.request_class(clientAddress[0], clientAddress[1], 213 remoteHost, scheme) 214 cherrypy.serving.request = r 215 cherrypy.serving.response = _cphttptools.Response() 216 return r 131 if not host: 132 host = 'localhost' 133 134 for trial in xrange(50): 135 if self.interrupt: 136 break 137 try: 138 check_port(host, port) 139 except IOError: 140 break 141 else: 142 time.sleep(.1) 143 else: 144 cherrypy.log("Port %s not bound on %s" % 145 (repr(port), repr(host)), 'HTTP') 146 raise cherrypy.NotReady("Port not bound.") 217 147 218 148 def stop(self): 219 149 """Stop, including any HTTP servers.""" 220 150 self.stop_http_server() 221 222 for thread_ident, i in self.seen_threads.iteritems(): 223 for func in self.on_stop_thread_list + self.onStopThreadList: 224 func(i) 225 self.seen_threads.clear() 226 227 for func in self.on_stop_server_list + self.onStopServerList: 228 func() 229 230 self.state = STOPPED 231 cherrypy.log("CherryPy shut down", "HTTP") 151 Engine.stop(self) 232 152 233 153 def stop_http_server(self): … … 241 161 httpstop() 242 162 cherrypy.log("HTTP Server shut down", "HTTP") 243 244 self.httpserver = None245 246 def restart(self):247 """Restart, including any HTTP servers."""248 self.stop()249 for func in self.on_start_server_list + self.onStartServerList:250 func()251 self.start_http_server()252 self.state = STARTED253 254 def wait(self):255 """Block the caller until ready to receive requests (or error)."""256 while not self.ready:257 time.sleep(.1)258 if self.interrupt:259 # Something went wrong in server.start,260 # possibly in another thread. Stop this thread.261 raise cherrypy.NotReady("The CherryPy server errored", "HTTP")262 263 def _is_ready(self):264 return bool(self.state == STARTED)265 ready = property(_is_ready, doc="Return True if the server is ready to"266 " receive requests, False otherwise.")267 163 268 164 def start_with_callback(self, func, args=None, kwargs=None, 269 165 server_class = _missing, serverClass = None): 270 166 """Start, then callback the given func in a new thread.""" 271 167 272 168 # Read old name for backward compatibility 273 169 if serverClass is not None: 274 170 server_class = None 275 171 276 172 if args is None: 277 173 args = () … … 286 182 287 183 self.start(server_class = server_class) 288 289 290 def check_config():291 err = cherrypy.WrongConfigValue292 for name, section in cherrypy.config.configs.iteritems():293 for k, v in section.iteritems():294 if k == "server.environment":295 if v and v not in cherrypy.config.environments:296 raise err("'%s' is not a registered environment." % v)297 298 299 def configure():300 """Perform one-time actions to prepare the CherryPy core."""301 if cherrypy.codecoverage:302 from cherrypy.lib import covercp303 covercp.start()304 305 conf = cherrypy.config.get306 # TODO: config.checkConfigOptions()307 308 # If sessions are stored in files and we309 # use threading, we need a lock on the file310 if (conf('server.thread_pool') > 1311 and conf('session.storage_type') == 'file'):312 cherrypy._sessionFileLock = threading.RLock()313 314 # set cgi.maxlen which will limit the size of POST request bodies315 cgi.maxlen = conf('server.max_request_size')316 317 # Set up the profiler if requested.318 if conf("profiling.on", False):319 ppath = conf("profiling.path", "")320 cherrypy.profiler = profiler.Profiler(ppath)321 else:322 cherrypy.profiler = None323 324 # Initialize the built in filters325 filters.init()326 184 327 185 trunk/cherrypy/_cpwsgiserver.py
r965 r967 67 67 68 68 for mount_point, wsgi_app in self.server.mount_points: 69 if path == "*": 70 # This means, of course, that the first wsgi_app will 71 # always handle a URI of "*". 72 self.environ["SCRIPT_NAME"] = "" 73 self.environ["PATH_INFO"] = "*" 74 self.wsgi_app = wsgi_app 75 break 69 76 # The mount_points list should be sorted by length, descending. 70 77 if path.startswith(mount_point): trunk/cherrypy/test/test_core.py
r942 r967 632 632 633 633 # Test server.throw_errors (ticket #186). 634 httpcls = cherrypy.server.httpserverclass635 if httpcls:634 s = cherrypy.server.httpserver 635 if s: 636 636 self.getPage("/error/rethrow") 637 637 self.assertBody("THROWN ERROR: ValueError") … … 837 837 self.assertBody("OK") 838 838 839 httpcls = cherrypy.server.httpserverclass840 if httpcls:839 s = cherrypy.server.httpserver 840 if s: 841 841 cherrypy.config.update({'server.max_request_header_size': 10}) 842 842 self.getPage("/maxrequestsize/index") … … 866 866 self.assertBody('Size: 5') 867 867 868 httpcls = cherrypy.server.httpserverclass869 if httpcls:868 s = cherrypy.server.httpserver 869 if s: 870 870 cherrypy.config.update({ 871 871 '%s/maxrequestsize' % self.prefix(): {'server.max_request_body_size': 3}}) trunk/cherrypy/test/test_logdebuginfo_filter.py
r910 r967 35 35 36 36 def testBug326(self): 37 httpcls = cherrypy.server.httpserverclass 38 if httpcls and httpcls.__name__ == "WSGIServer": 37 from cherrypy import _cpwsgi 38 s = cherrypy.server.httpserver 39 if s and isinstance(s, _cpwsgi.WSGIServer): 39 40 h = [("Content-type", "multipart/form-data; boundary=x"), 40 41 ("Content-Length", "110")] … … 47 48 """ 48 49 cherrypy.config.update({ 49 ('%s/bug326' % self.prefix ): {50 ('%s/bug326' % self.prefix()): { 50 51 'server.max_request_body_size': 3, 51 52 'server.environment': 'development', trunk/cherrypy/test/test_states.py
r943 r967 55 55 56 56 def test_0_NormalStateFlow(self): 57 # Without having called "cherrypy.server.start()", we should 58 # get a NotReady error 59 self.assertRaises(cherrypy.NotReady, self.getPage, "/") 57 if not self.server_class: 58 # Without having called "cherrypy.server.start()", we should 59 # get a NotReady error 60 self.assertRaises(cherrypy.NotReady, self.getPage, "/") 60 61 61 62 # And our db_connection should not be running … … 86 87 self.assertEqual(cherrypy.server.state, 0) 87 88 88 # Once the server has stopped, we should get a NotReady error again.89 self.assertRaises(cherrypy.NotReady, self.getPage, "/")90 91 89 # Verify that the on_stop_server function was called 92 90 self.assertEqual(db_connection.running, False) 93 91 self.assertEqual(len(db_connection.threads), 0) 92 93 if not self.server_class: 94 # Once the server has stopped, we should get a NotReady 95 # error again. (If we were running an HTTP server, 96 # then the connection should not even be processed). 97 self.assertRaises(cherrypy.NotReady, self.getPage, "/") 94 98 95 99 def test_1_Restart(self): … … 140 144 cherrypy.server.start(False, self.server_class) 141 145 # Time passes... 142 self.assertEqual(cherrypy.server.httpserver, None)143 146 self.assertEqual(cherrypy.server.state, 0) 144 self.assertRaises(cherrypy.NotReady, self.getPage, "/")145 147 self.assertEqual(db_connection.running, False) 146 148 self.assertEqual(len(db_connection.threads), 0) … … 158 160 cherrypy.server.start(False, self.server_class) 159 161 # Time passes... 160 self.assertEqual(cherrypy.server.httpserver, None)161 162 self.assertEqual(cherrypy.server.state, 0) 162 self.assertRaises(cherrypy.NotReady, self.getPage, "/")163 163 self.assertEqual(db_connection.running, False) 164 164 self.assertEqual(len(db_connection.threads), 0) 165 166 def test_3_ConfigErrors(self):167 cherrypy.config.update({'server.environment': 'destruction'})168 169 try:170 self.assertRaises(cherrypy.WrongConfigValue,171 cherrypy.server.start, True, self.server_class)172 finally:173 cherrypy.server.stop()174 165 175 166

