diff plugins/cherrypy/_cphttpserver.py @ 0:4385a7d0efd1 grumpy-goblin

Deleted and repushed it with the 'grumpy-goblin' branch. I forgot a y
author sirebral
date Tue, 14 Jul 2009 16:41:58 -0500
parents
children
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/plugins/cherrypy/_cphttpserver.py	Tue Jul 14 16:41:58 2009 -0500
@@ -0,0 +1,350 @@
+"""
+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.
+"""
+
+import cpg, sys, threading, SocketServer, _cphttptools
+import BaseHTTPServer, socket, Queue, _cputil
+
+def stop():
+    cpg._server.shutdown()
+
+def start():
+    """ Prepare the HTTP server and then run it """
+
+    # If sessions are stored in files and we
+    # use threading, we need a lock on the file
+    if (cpg.configOption.threadPool > 1) and \
+            cpg.configOption.sessionStorageType == 'file':
+        cpg._sessionFileLock = threading.RLock()
+
+
+    if cpg.configOption.socketFile:
+        # AF_UNIX socket
+        # TODO: Handle threading here
+        class MyCherryHTTPServer(CherryHTTPServer): address_family = socket.AF_UNIX
+    else:
+        # AF_INET socket
+        if cpg.configOption.threadPool > 1:
+            MyCherryHTTPServer = PooledThreadServer
+        else:
+            MyCherryHTTPServer = CherryHTTPServer
+
+    MyCherryHTTPServer.request_queue_size = cpg.configOption.socketQueueSize
+
+    # Set protocol_version
+    CherryHTTPRequestHandler.protocol_version = cpg.configOption.protocolVersion
+
+    run_server(CherryHTTPRequestHandler, MyCherryHTTPServer, \
+        (cpg.configOption.socketHost, cpg.configOption.socketPort), \
+        cpg.configOption.socketFile)
+
+def run_server(HandlerClass, ServerClass, server_address, socketFile):
+    """Run the HTTP request handler class."""
+
+    if cpg.configOption.socketFile:
+        try: os.unlink(cpg.configOption.socketFile) # So we can reuse the socket
+        except: pass
+        server_address = cpg.configOption.socketFile
+    if cpg.configOption.threadPool > 1:
+        myCherryHTTPServer = ServerClass(server_address, cpg.configOption.threadPool, HandlerClass)
+    else:
+        myCherryHTTPServer = ServerClass(server_address, HandlerClass)
+    cpg._server = myCherryHTTPServer
+    if cpg.configOption.socketFile:
+        try: os.chmod(socketFile, 0777) # So everyone can access the socket
+        except: pass
+    global _cpLogMessage
+    _cpLogMessage = _cputil.getSpecialFunction('_cpLogMessage')
+
+    servingWhat = "HTTP"
+    if cpg.configOption.socketPort: onWhat = "socket: ('%s', %s)" % (cpg.configOption.socketHost, cpg.configOption.socketPort)
+    else: onWhat = "socket file: %s" % cpg.configOption.socketFile
+    _cpLogMessage("Serving %s on %s" % (servingWhat, onWhat), 'HTTP')
+
+    try:
+        # Call the functions from cpg.server.onStartServerList
+        for func in cpg.server.onStartServerList:
+            func()
+        myCherryHTTPServer.serve_forever()
+    except KeyboardInterrupt:
+        _cpLogMessage("<Ctrl-C> hit: shutting down", "HTTP")
+        myCherryHTTPServer.server_close()
+    # Call the functions from cpg.server.onStartServerList
+    for func in cpg.server.onStopServerList:
+        func()
+
+class CherryHTTPRequestHandler(BaseHTTPServer.BaseHTTPRequestHandler):
+
+    """CherryPy HTTP request handler with the following commands:
+
+        o  GET
+        o  HEAD
+        o  POST
+        o  HOTRELOAD
+
+    """
+
+    def address_string(self):
+        """ Try to do a reverse DNS based on [server]reverseDNS in the config file """
+        if cpg.configOption.reverseDNS:
+            return BaseHTTPServer.BaseHTTPRequestHandler.address_string(self)
+        else:
+            return self.client_address[0]
+
+    def do_GET(self):
+        """Serve a GET request."""
+        cpg.request.method = 'GET'
+        _cphttptools.doRequest(
+            self.client_address[0],
+            self.address_string(),
+            self.raw_requestline,
+            self.headers,
+            self.rfile,
+            self.wfile
+        )
+
+    def do_HEAD(self): # Head is not implemented
+        """Serve a HEAD request."""
+        cpg.request.method = 'HEAD'
+        _cphttptools.doRequest(
+            self.client_address[0],
+            self.address_string(),
+            self.raw_requestline,
+            self.headers,
+            self.rfile,
+            self.wfile
+        )
+
+    def do_POST(self):
+        """Serve a POST request."""
+        cpg.request.method = 'POST'
+        _cphttptools.doRequest(
+            self.client_address[0],
+            self.address_string(),
+            self.raw_requestline,
+            self.headers,
+            self.rfile,
+            self.wfile
+        )
+
+        self.connection = self.request
+
+    def log_message(self, format, *args):
+        """ We have to override this to use our own logging mechanism """
+        _cputil.getSpecialFunction('_cpLogMessage')(format % args, "HTTP")
+
+
+class CherryHTTPServer(BaseHTTPServer.HTTPServer):
+    def server_activate(self):
+        """Override server_activate to set timeout on our listener socket"""
+        self.socket.settimeout(1)
+        BaseHTTPServer.HTTPServer.server_activate(self)
+
+    def server_bind(self):
+        # Removed getfqdn call because it was timing out on localhost when calling gethostbyaddr
+        self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
+        self.socket.bind(self.server_address)
+
+    def get_request(self):
+        # With Python 2.3 it seems that an accept socket in timeout (nonblocking) mode
+        #  results in request sockets that are also set in nonblocking mode. Since that doesn't play
+        #  well with makefile() (where wfile and rfile are set in SocketServer.py) we explicitly set
+        #  the request socket to blocking
+
+        request, client_address = self.socket.accept()
+        request.setblocking(1)
+        return request, client_address
+
+    def handle_request(self):
+        """Override handle_request to trap timeout exception."""
+        try:
+            BaseHTTPServer.HTTPServer.handle_request(self)
+        except socket.timeout:
+            # The only reason for the timeout is so we can notice keyboard
+            # interrupts on Win32, which don't interrupt accept() by default
+            return 1
+        except KeyboardInterrupt:
+            _cpLogMessage("<Ctrl-C> hit: shutting down", "HTTP")
+            self.shutdown()
+
+    def serve_forever(self):
+        """Override serve_forever to handle shutdown."""
+        self.__running = 1
+        while self.__running:
+            self.handle_request()
+
+    def shutdown(self):
+        self.__running = 0
+
+_SHUTDOWNREQUEST = (0,0)
+
+class ServerThread(threading.Thread):
+    def __init__(self, RequestHandlerClass, requestQueue, threadIndex):
+        threading.Thread.__init__(self)
+        self._RequestHandlerClass = RequestHandlerClass
+        self._requestQueue = requestQueue
+        self._threadIndex = threadIndex
+        
+    def run(self):
+        # Call the functions from cpg.server.onStartThreadList
+        for func in cpg.server.onStartThreadList:
+            func(self._threadIndex)
+        while 1:
+            request, client_address = self._requestQueue.get()
+            if (request, client_address) == _SHUTDOWNREQUEST:
+                # Call the functions from cpg.server.onStopThreadList
+                for func in cpg.server.onStopThreadList:
+                    func()
+                return
+            if self.verify_request(request, client_address):            
+                try:
+                    self.process_request(request, client_address)
+                except:
+                    self.handle_error(request, client_address)
+                    self.close_request(request)
+            else:
+                self.close_request(request)
+
+    def verify_request(self, request, client_address):
+        """ Verify the request.  May be overridden.
+            Return 1 if we should proceed with this request. """
+        return 1
+
+    def process_request(self, request, client_address):
+        self._RequestHandlerClass(request, client_address, self)        
+        self.close_request(request)
+
+    def close_request(self, request):
+        """ Called to clean up an individual request. """
+        request.close()
+
+    def handle_error(self, request, client_address):
+        """ Handle an error gracefully.  May be overridden.
+            The default is to print a traceback and continue.
+        """
+        import traceback, StringIO
+        bodyFile=StringIO.StringIO()
+        traceback.print_exc(file=bodyFile)
+        errorBody=bodyFile.getvalue()
+        bodyFile.close()
+        _cputil.getSpecialFunction('_cpLogMessage')(errorBody)
+        
+
+class PooledThreadServer(SocketServer.TCPServer):
+
+    allow_reuse_address = 1
+
+    """A TCP Server using a pool of worker threads. This is superior to the
+       alternatives provided by the Python standard library, which only offer
+       (1) handling a single request at a time, (2) handling each request in
+       a separate thread (via ThreadingMixIn), or (3) handling each request in
+       a separate process (via ForkingMixIn). It's also superior in some ways
+       to the pure async approach used by Twisted because it allows a more
+       straightforward and simple programming model in the face of blocking
+       requests (i.e. you don't have to bother with Deferreds).""" 
+    def __init__(self, serverAddress, numThreads, RequestHandlerClass, ThreadClass=ServerThread):
+        assert(numThreads > 0)
+        # I know it says "do not override", but I have to in order to implement SSL support !
+        SocketServer.BaseServer.__init__(self, serverAddress, RequestHandlerClass)
+        self.socket=socket.socket(self.address_family, self.socket_type)
+        self.server_bind()
+        self.server_activate()
+
+        self._numThreads = numThreads        
+        self._RequestHandlerClass = RequestHandlerClass
+        self._ThreadClass = ThreadClass
+        self._requestQueue = Queue.Queue()
+        self._workerThreads = []
+
+    def createThread(self, threadIndex):
+        return self._ThreadClass(self._RequestHandlerClass, self._requestQueue, threadIndex)
+            
+    def start(self):
+        if self._workerThreads != []:
+            return
+        for i in xrange(self._numThreads):
+            self._workerThreads.append(self.createThread(i))        
+        for worker in self._workerThreads:
+            worker.start()
+            
+    def server_close(self):
+        """Override server_close to shutdown thread pool"""
+        SocketServer.TCPServer.server_close(self)
+        for worker in self._workerThreads:
+            self._requestQueue.put(_SHUTDOWNREQUEST)
+        for worker in self._workerThreads:
+            worker.join()
+        self._workerThreads = []
+
+    def server_activate(self):
+        """Override server_activate to set timeout on our listener socket"""
+        self.socket.settimeout(1)
+        SocketServer.TCPServer.server_activate(self)
+
+    def server_bind(self):
+        self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
+        self.socket.bind(self.server_address)
+
+    def shutdown(self):
+        """Gracefully shutdown a server that is serve_forever()ing."""
+        self.__running = 0
+
+    def serve_forever(self):
+        """Handle one request at a time until doomsday (or shutdown is called)."""
+        if self._workerThreads == []:
+            self.start()
+        self.__running = 1
+        while self.__running:
+            if not self.handle_request():
+                break
+        self.server_close()            
+        
+    def handle_request(self):
+        """Override handle_request to enqueue requests rather than handle
+           them synchronously. Return 1 by default, 0 to shutdown the
+           server."""
+        try:
+            request, client_address = self.get_request()
+        except KeyboardInterrupt:
+            _cpLogMessage("<Ctrl-C> hit: shutting down", "HTTP")
+            return 0
+        except socket.error, e:
+            return 1
+        self._requestQueue.put((request, client_address))
+        return 1
+
+    def get_request(self):
+        # With Python 2.3 it seems that an accept socket in timeout (nonblocking) mode
+        #  results in request sockets that are also set in nonblocking mode. Since that doesn't play
+        #  well with makefile() (where wfile and rfile are set in SocketServer.py) we explicitly set
+        #  the request socket to blocking
+
+        request, client_address = self.socket.accept()
+        if hasattr(request,'setblocking'):
+            request.setblocking(1)
+        return request, client_address
+