""" Copyright (c) 2004, CherryPy Team (team@cherrypy.org) All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. * Neither the name of the CherryPy Team nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. """ """ Main CherryPy module: - Creates a server """ import cgi import threading import time import warnings import cherrypy from cherrypy import _cphttptools from cherrypy.lib import autoreload, profiler # Use a flag to indicate the state of the application server. STOPPED = 0 STARTING = None STARTED = 1 seen_threads = {} _missing = object() class Server(object): def __init__(self): self.state = STOPPED self.httpserver = None self.httpserverclass = None self.interrupt = None # Set some special attributes for adding hooks self.onStartServerList = [] self.onStartThreadList = [] self.onStopServerList = [] self.onStopThreadList = [] def start(self, initOnly=False, serverClass=_missing): """Main function. MUST be called from the main thread. Set initOnly to True to keep this function from blocking. Set serverClass to None to skip starting any HTTP server. """ self.state = STARTING self.interrupt = None conf = cherrypy.config.get if serverClass is _missing: serverClass = conf("server.class", _missing) if serverClass is _missing: import _cpwsgi serverClass = _cpwsgi.WSGIServer elif serverClass and isinstance(serverClass, basestring): # Dynamically load the class from the given string serverClass = cherrypy._cputil.attributes(serverClass) self.blocking = not initOnly self.httpserverclass = serverClass # Autoreload, but check serverClass. If None, we're not starting # our own webserver, and therefore could do Very Bad Things when # autoreload calls sys.exit. if serverClass is not None: defaultOn = (conf("server.environment") == "development") if conf('autoreload.on', defaultOn): try: autoreload.main(self._start) except KeyboardInterrupt: cherrypy.log(" hit: shutting down autoreloader", "HTTP") self.stop() except SystemExit: cherrypy.log("SystemExit raised: shutting down autoreloader", "HTTP") self.stop() # We must raise here: if this is a process spawned by # autoreload, then it must return its error code to # the parent. raise return self._start() def _start(self): # Output config options to log if cherrypy.config.get("server.logConfigOptions", True): cherrypy.config.outputConfigMap() try: configure() for func in cherrypy.server.onStartServerList: func() self.start_http_server() self.state = STARTED if self.blocking: # Block forever (wait for KeyboardInterrupt or SystemExit). while True: time.sleep(.1) if self.interrupt: raise self.interrupt except KeyboardInterrupt: cherrypy.log(" hit: shutting down server", "HTTP") self.stop() except SystemExit: cherrypy.log("SystemExit raised: shutting down server", "HTTP") self.stop() def start_http_server(self, blocking=True): """Start the requested HTTP server.""" if self.httpserver is not None: msg = ("You seem to have an HTTP server still running." "Please call server.stop_http_server() " "before continuing.") warnings.warn(msg) if self.httpserverclass is None: return if cherrypy.config.get('server.socketPort'): host = cherrypy.config.get('server.socketHost') port = cherrypy.config.get('server.socketPort') wait_for_free_port(host, port) if not host: host = 'localhost' onWhat = "http://%s:%s/" % (host, port) else: onWhat = "socket file: %s" % cherrypy.config.get('server.socketFile') # Instantiate the server. self.httpserver = self.httpserverclass() # HTTP servers MUST be started in a new thread, so that the # main thread persists to receive KeyboardInterrupt's. This # wrapper traps an interrupt in the http server's main thread # and shutdowns CherryPy. def _start_http(): try: self.httpserver.start() except (KeyboardInterrupt, SystemExit), exc: self.interrupt = exc threading.Thread(target=_start_http).start() if blocking: self.wait_for_http_ready() cherrypy.log("Serving HTTP on %s" % onWhat, 'HTTP') def wait_for_http_ready(self): if self.httpserverclass is not None: while not getattr(self.httpserver, "ready", True): time.sleep(.1) # Wait for port to be occupied if cherrypy.config.get('server.socketPort'): host = cherrypy.config.get('server.socketHost') port = cherrypy.config.get('server.socketPort') wait_for_occupied_port(host, port) def request(self, clientAddress, remoteHost, requestLine, headers, rfile, scheme="http"): """request(clientAddress, remoteHost, requestLine, headers, rfile, scheme="http") clientAddress: the (IP address, port) of the client remoteHost: the IP address of the client requestLine: " HTTP/", e.g. "GET /main?abc=123 HTTP/1.1" headers: a list of (key, value) tuples rfile: a file-like object from which to read the HTTP request body scheme: either "http" or "https"; defaults to "http" """ if self.state == STOPPED: raise cherrypy.NotReady("The CherryPy server has stopped.") elif self.state == STARTING: raise cherrypy.NotReady("The CherryPy server could not start.") threadID = threading._get_ident() if threadID not in seen_threads: if cherrypy.codecoverage: from cherrypy.lib import covercp covercp.start() i = len(seen_threads) + 1 seen_threads[threadID] = i for func in self.onStartThreadList: func(i) if cherrypy.profiler: cherrypy.profiler.run(_cphttptools.Request, clientAddress, remoteHost, requestLine, headers, rfile, scheme) else: _cphttptools.Request(clientAddress, remoteHost, requestLine, headers, rfile, scheme) def stop(self): """Stop, including any HTTP servers.""" self.stop_http_server() for thread_ident, i in seen_threads.iteritems(): for func in self.onStopThreadList: func(i) seen_threads.clear() for func in self.onStopServerList: func() self.state = STOPPED cherrypy.log("CherryPy shut down", "HTTP") def stop_http_server(self): """Stop the HTTP server.""" try: httpstop = self.httpserver.stop except AttributeError: pass else: # httpstop() MUST block until the server is *truly* stopped. httpstop() cherrypy.log("HTTP Server shut down", "HTTP") self.httpserver = None def restart(self): """Restart, including any HTTP servers.""" self.stop() for func in self.onStartServerList: func() self.start_http_server() self.state = STARTED def wait(self): """Block the caller until ready to receive requests.""" while not self.ready: time.sleep(.1) def _is_ready(self): return bool(self.state == STARTED) ready = property(_is_ready, doc="Return True if the server is ready to receive requests, False otherwise.") def start_with_callback(self, func, args=None, kwargs=None, serverClass=_missing): """Start, then callback the given func in a new thread.""" if args is None: args = () if kwargs is None: kwargs = {} args = (func,) + args def _callback(func, *args, **kwargs): self.wait() func(*args, **kwargs) threading.Thread(target=_callback, args=args, kwargs=kwargs).start() self.start(serverClass=serverClass) def configure(): """Perform one-time actions to prepare the CherryPy core.""" if cherrypy.codecoverage: from cherrypy.lib import covercp covercp.start() conf = cherrypy.config.get # TODO: config.checkConfigOptions() # If sessions are stored in files and we # use threading, we need a lock on the file if (conf('server.threadPool') > 1 and conf('session.storageType') == 'file'): cherrypy._sessionFileLock = threading.RLock() # set cgi.maxlen which will limit the size of POST request bodies cgi.maxlen = conf('server.maxRequestSize') # Set up the profiler if requested. if conf("profiling.on", False): ppath = conf("profiling.path", "") cherrypy.profiler = profiler.Profiler(ppath) else: cherrypy.profiler = None # Initialize the built in filters cherrypy._cputil._cpInitDefaultFilters() cherrypy._cputil._cpInitUserDefinedFilters() def check_port(host, port): """Raise an error if the given port is not free on the given host.""" if not host: host = 'localhost' import socket try: s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) s.connect((host, int(port))) s.close() raise IOError("Port %s is in use on %s; perhaps the previous " "server did not shut down properly." % (repr(port), repr(host))) except socket.error: pass def wait_for_free_port(host, port): """Wait for the specified port to become free (drop requests).""" if not host: host = 'localhost' for trial in xrange(50): try: check_port(host, port) except IOError: # Give the old server thread time to free the port. time.sleep(.1) else: return cherrypy.log("Port %s not free on %s" % (repr(port), repr(host)), 'HTTP') raise cherrypy.NotReady("Port not free.") def wait_for_occupied_port(host, port): """Wait for the specified port to become active (receive requests).""" if not host: host = 'localhost' for trial in xrange(50): try: check_port(host, port) except IOError: return else: time.sleep(.1) cherrypy.log("Port %s not bound on %s" % (repr(port), repr(host)), 'HTTP') raise cherrypy.NotReady("Port not bound.")