# This extension demonstrates some advanced features of the Python ISAPI # framework. # We demonstrate: # * Reloading your Python module without shutting down IIS (eg, when your # .py implementation file changes.) # * Custom command-line handling - both additional options and commands. # * Using a query string - any part of the URL after a '?' is assumed to # be "variable names" separated by '&' - we will print the values of # these server variables. # * If the tail portion of the URL is "ReportUnhealthy", IIS will be # notified we are unhealthy via a HSE_REQ_REPORT_UNHEALTHY request. # Whether this is acted upon depends on if the IIS health-checking # tools are installed, but you should always see the reason written # to the Windows event log - see the IIS documentation for more. from isapi import isapicon from isapi.simple import SimpleExtension import sys, os, stat if hasattr(sys, "isapidllhandle"): import win32traceutil # Notes on reloading # If your HttpFilterProc or HttpExtensionProc functions raises # 'isapi.InternalReloadException', the framework will not treat it # as an error but instead will terminate your extension, reload your # extension module, re-initialize the instance, and re-issue the request. # The Initialize functions are called with None as their param. The # return code from the terminate function is ignored. # # This is all the framework does to help you. It is up to your code # when you raise this exception. This sample uses a Win32 "find # notification". Whenever windows tells us one of the files in the # directory has changed, we check if the time of our source-file has # changed, and set a flag. Next imcoming request, we check the flag and # raise the special exception if set. # # The end result is that the module is automatically reloaded whenever # the source-file changes - you need take no further action to see your # changes reflected in the running server. # The framework only reloads your module - if you have libraries you # depend on and also want reloaded, you must arrange for this yourself. # One way of doing this would be to special case the import of these # modules. Eg: # -- # try: # my_module = reload(my_module) # module already imported - reload it # except NameError: # import my_module # first time around - import it. # -- # When your module is imported for the first time, the NameError will # be raised, and the module imported. When the ISAPI framework reloads # your module, the existing module will avoid the NameError, and allow # you to reload that module. from isapi import InternalReloadException import win32event, win32file, winerror, win32con, threading try: reload_counter += 1 except NameError: reload_counter = 0 # A watcher thread that checks for __file__ changing. # When it detects it, it simply sets "change_detected" to true. class ReloadWatcherThread(threading.Thread): def __init__(self): self.change_detected = False self.filename = __file__ if self.filename.endswith("c") or self.filename.endswith("o"): self.filename = self.filename[:-1] self.handle = win32file.FindFirstChangeNotification( os.path.dirname(self.filename), False, # watch tree? win32con.FILE_NOTIFY_CHANGE_LAST_WRITE) threading.Thread.__init__(self) def run(self): last_time = os.stat(self.filename)[stat.ST_MTIME] while 1: try: rc = win32event.WaitForSingleObject(self.handle, win32event.INFINITE) win32file.FindNextChangeNotification(self.handle) except win32event.error as details: # handle closed - thread should terminate. if details.winerror != winerror.ERROR_INVALID_HANDLE: raise break this_time = os.stat(self.filename)[stat.ST_MTIME] if this_time != last_time: print("Detected file change - flagging for reload.") self.change_detected = True last_time = this_time def stop(self): win32file.FindCloseChangeNotification(self.handle) # The ISAPI extension - handles requests in our virtual dir, and sends the # response to the client. class Extension(SimpleExtension): "Python advanced sample Extension" def __init__(self): self.reload_watcher = ReloadWatcherThread() self.reload_watcher.start() def HttpExtensionProc(self, ecb): # NOTE: If you use a ThreadPoolExtension, you must still perform # this check in HttpExtensionProc - raising the exception from # The "Dispatch" method will just cause the exception to be # rendered to the browser. if self.reload_watcher.change_detected: print("Doing reload") raise InternalReloadException url = ecb.GetServerVariable("UNICODE_URL") if url.endswith("ReportUnhealthy"): ecb.ReportUnhealthy("I'm a little sick") ecb.SendResponseHeaders("200 OK", "Content-Type: text/html\r\n\r\n", 0) print("", file=ecb) qs = ecb.GetServerVariable("QUERY_STRING") if qs: queries = qs.split("&") print("
", file=ecb)
            for q in queries:
                val = ecb.GetServerVariable(q, '<no such variable>')
                print("%s=%r" % (q, val), file=ecb)
            print("

", file=ecb) print("This module has been imported", file=ecb) print("%d times" % (reload_counter,), file=ecb) print("", file=ecb) ecb.close() return isapicon.HSE_STATUS_SUCCESS def TerminateExtension(self, status): self.reload_watcher.stop() # The entry points for the ISAPI extension. def __ExtensionFactory__(): return Extension() # Our special command line customization. # Pre-install hook for our virtual directory. def PreInstallDirectory(params, options): # If the user used our special '--description' option, # then we override our default. if options.description: params.Description = options.description # Post install hook for our entire script def PostInstall(params, options): print() print("The sample has been installed.") print("Point your browser to /AdvancedPythonSample") print("If you modify the source file and reload the page,") print("you should see the reload counter increment") # Handler for our custom 'status' argument. def status_handler(options, log, arg): "Query the status of something" print("Everything seems to be fine!") custom_arg_handlers = {"status": status_handler} if __name__=='__main__': # If run from the command-line, install ourselves. from isapi.install import * params = ISAPIParameters(PostInstall = PostInstall) # Setup the virtual directories - this is a list of directories our # extension uses - in this case only 1. # Each extension has a "script map" - this is the mapping of ISAPI # extensions. sm = [ ScriptMapParams(Extension="*", Flags=0) ] vd = VirtualDirParameters(Name="AdvancedPythonSample", Description = Extension.__doc__, ScriptMaps = sm, ScriptMapUpdate = "replace", # specify the pre-install hook. PreInstall = PreInstallDirectory ) params.VirtualDirs = [vd] # Setup our custom option parser. from optparse import OptionParser parser = OptionParser('') # blank usage, so isapi sets it. parser.add_option("", "--description", action="store", help="custom description to use for the virtual directory") HandleCommandLine(params, opt_parser=parser, custom_arg_handlers = custom_arg_handlers)