#!/usr/bin/env python """DebugAppServer `DebugAppServer` executes all requests within the main thread, allowing servlets to be easily debugged using any Python debugger. The drawback is that only one request can be processed at a time using this approach. Also, exceptions are not caught and gracefully handled with an HTML error page. Instead, the exceptions rise uncaught which allows the debugger to trap them so you can inspect the program. To use, simply run "python Launch.py DebugAppServer" using whatever debugging environment you prefer. Caveats: This app server supports the AutoReload feature but not for Python's standard modules or WebKit or WebKit's dependencies (MiscUtils, WebUtils and TaskKit). Even with that limitation, auto-reload is still useful because if you modify any module in your site, the auto-reload will happen. Note that the app server *will* restart when the aformentioned modules are modified, but the modules won't actually be reloaded. Currently the session sweeper is still run within a separate thread, and a "close thread" is started up by the AppServer base class, but neither of these two extra threads should pose any problems debugging servlets. Tested on: - WingIDE on Windows, http://wingware.com/ - PythonWin - JEdit with the JPyDbg plugin, on Windows """ import sys import ThreadedAppServer, Profiler # We are going to replace ThreadedAppServer with our own class, # so we need to save a reference to the original class. OriginalThreadedAppServer = ThreadedAppServer.ThreadedAppServer # We want the Python debugger to trap the exceptions, not WebKit ThreadedAppServer.doesRunHandleExceptions = False class DebugAppServer(OriginalThreadedAppServer): """Single-threaded AppServer for debugging purposes. We are piggybacking on 99% of the code in ThreadedAppServer. Our trick is to replace the request queue with a dummy object that executes requests immediately instead of pushing them onto a queue to be handled by other threads. """ ## Init ## _excludePrefixes = 'WebKit MiscUtils WebUtils TaskKit'.split() def __init__(self, path=None): """Initialize DebugAppServer.""" # Initialize the base class OriginalThreadedAppServer.__init__(self, path) # Replace the request queue with a dummy object that merely # runs request handlers as soon as they are "pushed" self._requestQueue = DummyRequestQueue() print 'You are running the debugging app server.' def config(self): """Return default configuration.""" # Force ThreadedAppServer to create an empty thread pool by hacking # the settings to zero. This is not strictly necessary to do. if self._config is None: OriginalThreadedAppServer.config(self) self.setSetting('StartServerThreads', 0) self.setSetting('MaxServerThreads', 0) self.setSetting('MinServerThreads', 0) return self._config ## Overridden methods ## def mainloop(self): """Main loop for Windows. This is needed for COM support on Windows, because special thread initialization is required on any thread that runs servlets, in this case the main thread itself. """ self.initThread() try: OriginalThreadedAppServer.mainloop(self) finally: self.delThread() def createApplication(self): """Create and return an application object. Invoked by __init__.""" return DebugApplication(server=self) def restart(self): # The normal restart technique is to exit the application # with a special exit code and let an exta-process script # start the app server up again. That works poorly for a # debugging environment which is attached to a particular process. Profiler.reset() self.initiateShutdown() self._closeThread.join() sys.stdout.flush() sys.stderr.flush() self._imp.delModules(includePythonModules=False, excludePrefixes=self._excludePrefixes) raise ThreadedAppServer.RestartAppServerError from Application import Application class DebugApplication(Application): """This is a modified Application class for debugging.""" ## Overridden methods ## # Don't handle exceptions gracefully because we want # them to rise uncaught so the debugger will kick in. # @@ 2005-07-15 CE: This works well for exceptions within responding to # a request, but for problems during importing a servlet, the exception # gets printed to console and the debugger does not kick in. def handleException(self): """Handle exception. This should only be used in cases where there is no transaction object, for example if an exception occurs when attempting to save a session to disk. """ raise def handleExceptionInTransaction(self, excInfo, transaction): """Handle exception with info. Raises exception `excInfo` (as returned by ``sys.exc_info()``) that was generated by `transaction`. """ raise class DummyRequestQueue(object): """This is a dummy replacement for the request queue. It merely executes handlers as soon as they are "pushed". """ ## Overridden methods ## @staticmethod def put(handler): handler.handleRequest() handler.close() ## Globals ## # Replace ThreadedAppServer class in the ThreadedAppServer module with our # DebugAppServer. This seems like an awful hack, but it works and # requires less code duplication than other approaches I could think of, and # required a very minimal amount of modification to ThreadedAppServer.py. ThreadedAppServer.ThreadedAppServer = DebugAppServer # Grab the main function from ThreadedAppServer -- it has been "tricked" # into using DebugAppServer instead. main = ThreadedAppServer.main # Tweak ThreadedAppServer so that it never runs the main loop in a thread: def runMainLoopInThread(): return 0 ThreadedAppServer.runMainLoopInThread = runMainLoopInThread