Changeset 1822
- Timestamp:
- 11/10/07 19:37:41
- Files:
-
- branches/simpleserver/cherrypy/__init__.py (modified) (3 diffs)
- branches/simpleserver/cherrypy/_cpserver.py (modified) (5 diffs)
- branches/simpleserver/cherrypy/restsrv/plugins.py (modified) (5 diffs)
- branches/simpleserver/cherrypy/restsrv/servers.py (modified) (4 diffs)
- branches/simpleserver/cherrypy/restsrv/win32.py (modified) (3 diffs)
- branches/simpleserver/cherrypy/restsrv/wspbus.py (modified) (1 diff)
- branches/simpleserver/cherrypy/test/helper.py (modified) (2 diffs)
- branches/simpleserver/cherrypy/test/test_conn.py (modified) (1 diff)
- branches/simpleserver/cherrypy/test/test_states.py (modified) (5 diffs)
- branches/simpleserver/cherrypy/test/test_states_demo.py (modified) (3 diffs)
- branches/simpleserver/cherrypy/test/test_tools.py (modified) (1 diff)
- branches/simpleserver/cherrypy/test/test_wsgi_ns.py (modified) (1 diff)
Legend:
- Unmodified
- Added
- Removed
- Modified
- Copied
- Moved
branches/simpleserver/cherrypy/__init__.py
r1793 r1822 213 213 engine.autoreload = restsrv.plugins.Autoreloader(engine) 214 214 engine.autoreload.subscribe() 215 215 216 restsrv.plugins.ThreadManager(engine).subscribe() 217 218 signal_handler = restsrv.plugins.SignalHandler(engine) 216 219 217 220 from cherrypy import _cpserver 218 221 server = _cpserver.Server() 222 server.subscribe() 219 223 220 224 … … 240 244 tree.mount(root, script_name, config) 241 245 242 engine.subscribe('start', server.quickstart) 243 restsrv.plugins.SignalHandler(engine).subscribe() 246 signal_handler.subscribe() 244 247 engine.start() 245 248 engine.block() … … 386 389 # Using an access file makes CP about 10% slower. Leave off by default. 387 390 log.access_file = '' 388 engine.subscribe('log', lambda msg: log.error(msg, 'ENGINE')) 391 392 def _buslog(msg): 393 log.error(msg, 'ENGINE') 394 engine.subscribe('log', _buslog) 389 395 390 396 # Helper functions for CP apps # branches/simpleserver/cherrypy/_cpserver.py
r1767 r1822 5 5 import cherrypy 6 6 from cherrypy.lib import attributes 7 8 # We import * because we want to export check_port 9 # et al as attributes of this module. 7 10 from cherrypy.restsrv.servers import * 8 11 9 12 10 class Server( object):11 """ Manager for a set of HTTP servers.13 class Server(ServerAdapter): 14 """An adapter for an HTTP server. 12 15 13 This is both a container and controller for "HTTP server" objects, 14 which are kept in Server.httpservers, a dictionary of the form: 15 {httpserver: bind_addr} where 'bind_addr' is usually a (host, port) 16 tuple. 17 18 Most often, you will only be starting a single HTTP server. In this 19 common case, you can set attributes (like socket_host and socket_port) 16 You can set attributes (like socket_host and socket_port) 20 17 on *this* object (which is probably cherrypy.server), and call 21 18 quickstart. For example: … … 29 26 s = MyCustomWSGIServer(wsgiapp, port=8080) 30 27 cherrypy.server.quickstart(s) 31 32 But if you need to start more than one HTTP server (to serve on multiple33 ports, or protocols, etc.), you can manually register each one and then34 control them all:35 36 s1 = MyWSGIServer(host='0.0.0.0', port=80)37 s2 = another.HTTPServer(host='127.0.0.1', SSL=True)38 cherrypy.server.httpservers = {s1: ('0.0.0.0', 80),39 s2: ('127.0.0.1', 443)}40 # Note we do not use quickstart when we define our own httpservers41 cherrypy.server.start()42 43 Whether you use quickstart(), or define your own httpserver entries and44 use start(), you'll find that the start, wait, restart, and stop methods45 work the same way, controlling all registered httpserver objects at once.46 28 """ 47 29 … … 81 63 82 64 def __init__(self): 83 self.mgr = ServerManager(cherrypy.engine) 84 85 def _get_httpservers(self): 86 return self.mgr.httpservers 87 def _set_httpservers(self, value): 88 self.mgr.httpservers = value 89 httpservers = property(_get_httpservers, _set_httpservers) 65 ServerAdapter.__init__(self, cherrypy.engine) 90 66 91 67 def quickstart(self, server=None): … … 96 72 and attributes of self. 97 73 """ 98 httpserver, bind_addr = self.httpserver_from_self(server) 99 self.mgr.httpservers[httpserver] = bind_addr 100 self.mgr.start() 101 cherrypy.engine.subscribe('stop', self.mgr.stop) 74 self.httpserver, self.bind_addr = self.httpserver_from_self(server) 75 self.start() 102 76 103 77 def httpserver_from_self(self, httpserver=None): … … 119 93 120 94 def start(self): 121 """Start all registered HTTP servers.""" 122 self.mgr.start() 123 124 def wait(self, httpserver=None): 125 """Wait until the HTTP server is ready to receive requests. 126 127 If no httpserver is specified, wait for all registered httpservers. 128 """ 129 self.mgr.wait(httpserver) 130 131 def stop(self): 132 """Stop all HTTP servers.""" 133 self.mgr.stop() 134 135 def restart(self): 136 """Restart all HTTP servers.""" 137 self.mgr.restart() 95 """Start the HTTP server.""" 96 if not self.httpserver: 97 self.httpserver, self.bind_addr = self.httpserver_from_self() 98 ServerAdapter.start(self) 99 start.priority = 75 138 100 139 101 def base(self): 140 """Return the base (scheme://host) for this server manager."""102 """Return the base (scheme://host) for this server.""" 141 103 if self.socket_file: 142 104 return self.socket_file branches/simpleserver/cherrypy/restsrv/plugins.py
r1816 r1822 17 17 18 18 def subscribe(self): 19 """Register this monitoras a (multi-channel) listener on the bus."""19 """Register this object as a (multi-channel) listener on the bus.""" 20 20 for channel in self.bus.listeners: 21 21 method = getattr(self, channel, None) … … 24 24 25 25 def unsubscribe(self): 26 """Unregister this monitoras a listener on the bus."""26 """Unregister this object as a listener on the bus.""" 27 27 for channel in self.bus.listeners: 28 28 method = getattr(self, channel, None) … … 189 189 # forking has issues with threads: 190 190 # http://www.opengroup.org/onlinepubs/000095399/functions/fork.html 191 # " ... The general problem with making fork() work in a multi-threaded world192 # is what to do with all of the threads. ..."191 # "The general problem with making fork() work in a multi-threaded 192 # world is what to do with all of the threads..." 193 193 # So we check for active threads: 194 194 if threading.activeCount() != 1: 195 self.bus.log('There are more than one active threads. Daemonizing now may cause strange failures.') 195 self.bus.log('There are more than one active threads. ' 196 'Daemonizing now may cause strange failures.') 196 197 self.bus.log(str(threading.enumerate())) 197 198 198 199 # See http://www.erlenstar.demon.co.uk/unix/faq_2.html#SEC16 199 200 # (or http://www.faqs.org/faqs/unix-faq/programmer/faq/ section 1.7) … … 302 303 """Start our callback in its own perpetual timer thread.""" 303 304 if self.frequency > 0: 304 threadname = "restsrv %s" % self.__class__.__name__ 305 self.thread = PerpetualTimer(self.frequency, self.callback) 306 self.thread.setName(threadname) 307 self.thread.start() 308 self.bus.log("Started thread %r." % threadname) 305 if self.thread is None: 306 threadname = "restsrv %s" % self.__class__.__name__ 307 self.thread = PerpetualTimer(self.frequency, self.callback) 308 self.thread.setName(threadname) 309 self.thread.start() 310 self.bus.log("Started thread %r." % threadname) 311 else: 312 self.bus.log("Thread %r already started." % threadname) 309 313 start.priority = 70 310 314 311 315 def stop(self): 312 316 """Stop our callback's perpetual timer thread.""" 313 if self.thread: 317 if self.thread is None: 318 self.bus.log("No thread running for %s." % self.__class__.__name__) 319 else: 314 320 if self.thread is not threading.currentThread(): 315 321 self.thread.cancel() … … 338 344 def start(self): 339 345 """Start our own perpetual timer thread for self.run.""" 340 self.mtimes = {} 346 if self.thread is None: 347 self.mtimes = {} 341 348 Monitor.start(self) 342 349 start.priority = 70 branches/simpleserver/cherrypy/restsrv/servers.py
r1818 r1822 1 """ Manage a set of HTTP servers."""1 """Adapt an HTTP server.""" 2 2 3 3 import socket … … 6 6 7 7 8 class ServerManager(object): 9 """Manager for a set of HTTP servers. 10 11 This is both a container and controller for HTTP servers and gateways, 12 which are kept in Server.httpservers, a dictionary of the form: 13 {httpserver: bind_addr} where 'bind_addr' is usually a (host, port) 14 tuple. 8 class ServerAdapter(object): 9 """Adapter for an HTTP server. 15 10 16 11 If you need to start more than one HTTP server (to serve on multiple 17 12 ports, or protocols, etc.), you can manually register each one and then 18 control them all:13 start them all with bus.start: 19 14 20 s1 = MyWSGIServer(host='0.0.0.0', port=80) 21 s2 = another.HTTPServer(host='127.0.0.1', SSL=True) 22 server.httpservers = {s1: ('0.0.0.0', 80), 23 s2: ('127.0.0.1', 443)} 24 server.start() 25 26 The start, wait, restart, and stop methods control all registered 27 httpserver objects at once. 15 s1 = ServerAdapter(bus, MyWSGIServer(host='0.0.0.0', port=80)) 16 s2 = ServerAdapter(bus, another.HTTPServer(host='127.0.0.1', SSL=True)) 17 s1.subscribe() 18 s2.subscribe() 19 bus.start() 28 20 """ 29 21 30 31 def __init__(self, bus): 22 def __init__(self, bus, httpserver=None, bind_addr=None): 32 23 self.bus = bus 33 self.httpservers = {} 24 self.httpserver = httpserver 25 self.bind_addr = bind_addr 34 26 self.interrupt = None 27 self.running = False 35 28 36 29 def subscribe(self): … … 38 31 self.bus.subscribe('stop', self.stop) 39 32 33 def unsubscribe(self): 34 self.bus.unsubscribe('start', self.start) 35 self.bus.unsubscribe('stop', self.stop) 36 40 37 def start(self): 41 """Start all registered HTTP servers.""" 42 self.interrupt = None 43 if not self.httpservers: 44 raise ValueError("No HTTP servers have been created.") 45 for httpserver in self.httpservers: 46 self._start_http(httpserver) 47 start.priority = 75 48 49 def _start_http(self, httpserver): 50 """Start the given httpserver in a new thread.""" 51 bind_addr = self.httpservers[httpserver] 52 if isinstance(bind_addr, tuple): 53 wait_for_free_port(*bind_addr) 54 host, port = bind_addr 38 """Start the HTTP server.""" 39 if isinstance(self.bind_addr, tuple): 40 host, port = self.bind_addr 55 41 on_what = "%s:%s" % (host, port) 56 42 else: 57 on_what = "socket file: %s" % bind_addr43 on_what = "socket file: %s" % self.bind_addr 58 44 59 t = threading.Thread(target=self._start_http_thread, args=(httpserver,)) 45 if self.running: 46 self.bus.log("Already serving on %s" % on_what) 47 return 48 49 self.interrupt = None 50 if not self.httpserver: 51 raise ValueError("No HTTP server has been created.") 52 53 # Start the httpserver in a new thread. 54 if isinstance(self.bind_addr, tuple): 55 wait_for_free_port(*self.bind_addr) 56 57 t = threading.Thread(target=self._start_http_thread) 60 58 t.setName("HTTPServer " + t.getName()) 61 59 t.start() 62 60 63 self.wait(httpserver) 61 self.wait() 62 self.running = True 64 63 self.bus.log("Serving on %s" % on_what) 64 start.priority = 75 65 65 66 def _start_http_thread(self , httpserver):67 """HTTP servers MUST be startedin new threads, so that the66 def _start_http_thread(self): 67 """HTTP servers MUST be running in new threads, so that the 68 68 main thread persists to receive KeyboardInterrupt's. If an 69 69 exception is raised in the httpserver's thread then it's 70 trapped here, and the bus (and therefore our httpserver s)70 trapped here, and the bus (and therefore our httpserver) 71 71 are shut down. 72 72 """ 73 73 try: 74 httpserver.start()74 self.httpserver.start() 75 75 except KeyboardInterrupt, exc: 76 self.bus.log("<Ctrl-C> hit: shutting down HTTP server s")76 self.bus.log("<Ctrl-C> hit: shutting down HTTP server") 77 77 self.interrupt = exc 78 78 self.bus.stop() 79 79 except SystemExit, exc: 80 self.bus.log("SystemExit raised: shutting down HTTP server s")80 self.bus.log("SystemExit raised: shutting down HTTP server") 81 81 self.interrupt = exc 82 82 self.bus.stop() … … 86 86 self.interrupt = sys.exc_info()[1] 87 87 self.bus.log("Error in HTTP server: shutting down", 88 traceback=True)88 traceback=True) 89 89 self.bus.stop() 90 90 raise 91 91 92 def wait(self, httpserver=None): 93 """Wait until the HTTP server is ready to receive requests. 92 def wait(self): 93 """Wait until the HTTP server is ready to receive requests.""" 94 while not getattr(self.httpserver, "ready", False): 95 if self.interrupt: 96 raise self.interrupt 97 time.sleep(.1) 94 98 95 If no httpserver is specified, wait for all registered httpservers. 96 """ 97 if httpserver is None: 98 httpservers = self.httpservers.items() 99 else: 100 httpservers = [(httpserver, self.httpservers[httpserver])] 101 102 for httpserver, bind_addr in httpservers: 103 while not getattr(httpserver, "ready", False): 104 if self.interrupt: 105 raise self.interrupt 106 time.sleep(.1) 107 108 # Wait for port to be occupied 109 if isinstance(bind_addr, tuple): 110 host, port = bind_addr 111 wait_for_occupied_port(host, port) 99 # Wait for port to be occupied 100 if isinstance(self.bind_addr, tuple): 101 host, port = self.bind_addr 102 wait_for_occupied_port(host, port) 112 103 113 104 def stop(self): 114 """Stop all HTTP servers."""115 for httpserver, bind_addr in self.httpservers.items():116 # httpstop() MUST block until the server is *truly* stopped.117 httpserver.stop()105 """Stop the HTTP server.""" 106 if self.running: 107 # stop() MUST block until the server is *truly* stopped. 108 self.httpserver.stop() 118 109 # Wait for the socket to be truly freed. 119 if isinstance(bind_addr, tuple): 120 wait_for_free_port(*bind_addr) 121 self.bus.log("HTTP Server %s shut down" % httpserver) 110 if isinstance(self.bind_addr, tuple): 111 wait_for_free_port(*self.bind_addr) 112 self.running = False 113 self.bus.log("HTTP Server %s shut down" % self.httpserver) 114 else: 115 self.bus.log("HTTP Server %s already shut down" % self.httpserver) 116 stop.priority = 25 122 117 123 118 def restart(self): 124 """Restart all HTTP servers."""119 """Restart the HTTP server.""" 125 120 self.stop() 126 121 self.start() branches/simpleserver/cherrypy/restsrv/win32.py
r1757 r1822 1 1 """Windows service for restsrv. Requires pywin32.""" 2 2 3 import os 3 4 import thread 4 5 import win32api … … 52 53 return self.events[state] 53 54 except KeyError: 54 event = win32event.CreateEvent(None, 0, 0, None) 55 event = win32event.CreateEvent(None, 0, 0, 56 u"WSPBus %s Event (pid=%r)" % 57 (state.name, os.getpid())) 55 58 self.events[state] = event 56 59 return event … … 70 73 argument is ignored. 71 74 """ 75 # Don't wait for an event that beat us to the punch ;) 76 if self.state == state: 77 return 78 72 79 event = self._get_state_event(state) 73 80 try: branches/simpleserver/cherrypy/restsrv/wspbus.py
r1820 r1822 75 75 class _StateEnum(object): 76 76 class State(object): 77 pass 77 name = None 78 def __repr__(self): 79 return "states.%s" % self.name 80 81 def __setattr__(self, key, value): 82 if isinstance(value, self.State): 83 value.name = key 84 object.__setattr__(self, key, value) 78 85 states = _StateEnum() 79 86 states.STOPPED = states.State() branches/simpleserver/cherrypy/test/helper.py
r1760 r1822 110 110 cherrypy.config.reset() 111 111 setConfig(conf) 112 cherrypy.s erver.quickstart(server)112 cherrypy.signal_handler.subscribe() 113 113 # The Pybots automatic testing system needs the suite to exit 114 114 # with a non-zero value if there were any problems. … … 147 147 apps.sort() 148 148 apps.reverse() 149 for s in cherrypy.server.httpservers: 150 s.wsgi_app.apps = apps 149 cherrypy.server.httpserver.wsgi_app.apps = apps 151 150 152 151 def _run_test_suite_thread(moduleNames, conf): branches/simpleserver/cherrypy/test/test_conn.py
r1786 r1822 202 202 old_timeout = None 203 203 try: 204 httpserver = cherrypy.server.httpserver s.keys()[0]204 httpserver = cherrypy.server.httpserver 205 205 old_timeout = httpserver.timeout 206 206 except (AttributeError, IndexError): branches/simpleserver/cherrypy/test/test_states.py
r1815 r1822 197 197 self.assertNoHeader("Connection") 198 198 199 cherrypy.server.httpserver s.keys()[0].interrupt = KeyboardInterrupt199 cherrypy.server.httpserver.interrupt = KeyboardInterrupt 200 200 engine.block() 201 201 … … 328 328 self.fail("Process failed to return nonzero exit code.") 329 329 330 def test_6_Start_Error_With_Daemonize(self): 330 331 class DaemonizeTests(helper.CPWebCase): 332 333 def test_1_Daemonize(self): 331 334 if not self.server_class: 332 335 print "skipped (no server) ", 336 return 337 if os.name not in ['posix']: 338 print "skipped (not on posix) ", 339 return 340 341 # Start the demo script in a new process 342 demoscript = os.path.join(os.getcwd(), os.path.dirname(__file__), 343 "test_states_demo.py") 344 host = cherrypy.server.socket_host 345 port = cherrypy.server.socket_port 346 cherrypy._cpserver.wait_for_free_port(host, port) 347 348 args = [sys.executable, demoscript, host, str(port), '-daemonize'] 349 if self.scheme == "https": 350 args.append('-ssl') 351 # Spawn the process and wait, when this returns, the original process 352 # is finished. If it daemonized properly, we should still be able 353 # to access pages. 354 exit_code = os.spawnl(os.P_WAIT, sys.executable, *args) 355 cherrypy._cpserver.wait_for_occupied_port(host, port) 356 357 # Give the server some time to start up 358 time.sleep(2) 359 360 # Get the PID from the file. 361 pid = int(open(PID_file_path).read()) 362 try: 363 # Just get the pid of the daemonization process. 364 self.getPage("/pid") 365 self.assertStatus(200) 366 page_pid = int(self.body) 367 self.assertEqual(page_pid, pid) 368 finally: 369 # Shut down the spawned process 370 self.getPage("/stop") 371 372 try: 373 print os.waitpid(pid, 0) 374 except OSError, x: 375 if x.args != (10, 'No child processes'): 376 raise 377 378 # Wait until here to test the exit code because we want to ensure 379 # that we wait for the daemon to finish running before we fail. 380 if exit_code != 0: 381 self.fail("Daemonized process failed to exit cleanly") 382 383 def test_2_Start_Error_With_Daemonize(self): 384 if not self.server_class: 385 print "skipped (no server) ", 386 return 387 if os.name not in ['posix']: 388 print "skipped (not on posix) ", 333 389 return 334 390 … … 351 407 352 408 353 class DaemonizeTest(helper.CPWebCase):354 355 def test_Daemonize(self):356 if not self.server_class:357 print "skipped (no server) ",358 return359 if os.name not in ['posix']:360 print "skipped (not on posix) ",361 return362 363 # Start the demo script in a new process364 demoscript = os.path.join(os.getcwd(), os.path.dirname(__file__),365 "test_states_demo.py")366 host = cherrypy.server.socket_host367 port = cherrypy.server.socket_port368 cherrypy._cpserver.wait_for_free_port(host, port)369 370 args = [sys.executable, demoscript, host, str(port), '-daemonize']371 if self.scheme == "https":372 args.append('-ssl')373 # Spawn the process and wait, when this returns, the original process374 # is finished. If it daemonized properly, we should still be able375 # to access pages.376 exit_code = os.spawnl(os.P_WAIT, sys.executable, *args)377 cherrypy._cpserver.wait_for_occupied_port(host, port)378 379 # Give the server some time to start up380 time.sleep(2)381 382 # Get the PID from the file.383 pid = int(open(PID_file_path).read())384 try:385 # Just get the pid of the daemonization process.386 self.getPage("/pid")387 self.assertStatus(200)388 page_pid = int(self.body)389 self.assertEqual(page_pid, pid)390 finally:391 # Shut down the spawned process392 self.getPage("/stop")393 394 try:395 print os.waitpid(pid, 0)396 except OSError, x:397 if x.args != (10, 'No child processes'):398 raise399 400 # Wait until here to test the exit code because we want to ensure401 # that we wait for the daemon to finish running before we fail.402 if exit_code != 0:403 self.fail("Daemonized process failed to exit cleanly")404 405 406 409 def run(server, conf): 407 410 helper.setConfig(conf) 408 411 ServerStateTests.server_class = server 409 DaemonizeTest .server_class = server412 DaemonizeTests.server_class = server 410 413 suite = helper.CPTestLoader.loadTestsFromTestCase(ServerStateTests) 411 daemon_suite = helper.CPTestLoader.loadTestsFromTestCase(DaemonizeTest )414 daemon_suite = helper.CPTestLoader.loadTestsFromTestCase(DaemonizeTests) 412 415 try: 413 416 try: … … 438 441 439 442 if host: 440 DaemonizeTest .HOST = ServerStateTests.HOST = host443 DaemonizeTests.HOST = ServerStateTests.HOST = host 441 444 442 445 if port: 443 DaemonizeTest .PORT = ServerStateTests.PORT = port446 DaemonizeTests.PORT = ServerStateTests.PORT = port 444 447 445 448 if ssl: … … 448 451 conf['server.ssl_certificate'] = serverpem 449 452 conf['server.ssl_private_key'] = serverpem 450 DaemonizeTest .scheme = ServerStateTests.scheme = "https"451 DaemonizeTest .HTTP_CONN = ServerStateTests.HTTP_CONN = httplib.HTTPSConnection453 DaemonizeTests.scheme = ServerStateTests.scheme = "https" 454 DaemonizeTests.HTTP_CONN = ServerStateTests.HTTP_CONN = httplib.HTTPSConnection 452 455 453 456 def _run(server): branches/simpleserver/cherrypy/test/test_states_demo.py
r1815 r1822 29 29 30 30 def stop(self): 31 # This handler might be called before the engine is STARTED if an 32 # HTTP worker thread handles it before the HTTP server returns 33 # control to engine.start. We avoid that race condition here 34 # by waiting for the Bus to be STARTED. 35 cherrypy.engine.block(state=cherrypy.engine.states.STARTED) 31 36 cherrypy.engine.stop() 32 37 stop.exposed = True … … 38 43 "log.screen": False, 39 44 "log.error_file": os.path.join(thisdir, 'test_states_demo.error.log'), 45 "log.access_file": os.path.join(thisdir, 'test_states_demo.access.log'), 40 46 } 41 47 … … 62 68 plugins.PIDFile(cherrypy.engine, PID_file_path).subscribe() 63 69 64 cherrypy.engine.subscribe('start', cherrypy.server.quickstart, priority=75)65 66 70 if '-starterror' in sys.argv[3:]: 67 71 cherrypy.engine.subscribe('start', lambda: 1/0, priority=6) branches/simpleserver/cherrypy/test/test_tools.py
r1661 r1822 259 259 old_timeout = None 260 260 try: 261 httpserver = cherrypy.server.httpserver s.keys()[0]261 httpserver = cherrypy.server.httpserver 262 262 old_timeout = httpserver.timeout 263 263 except (AttributeError, IndexError): branches/simpleserver/cherrypy/test/test_wsgi_ns.py
r1804 r1822 78 78 79 79 def test_pipeline(self): 80 if not cherrypy.server.httpserver s:80 if not cherrypy.server.httpserver: 81 81 print "skipped ", 82 82 return

