123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218 |
- # 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.
-
- import os
- import stat
- import sys
-
- from isapi import isapicon
- from isapi.simple import SimpleExtension
-
- 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.
-
- import threading
-
- import win32con
- import win32event
- import win32file
- import winerror
-
- from isapi import InternalReloadException
-
- 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("<HTML><BODY>", file=ecb)
-
- qs = ecb.GetServerVariable("QUERY_STRING")
- if qs:
- queries = qs.split("&")
- print("<PRE>", file=ecb)
- for q in queries:
- val = ecb.GetServerVariable(q, "<no such variable>")
- print("%s=%r" % (q, val), file=ecb)
- print("</PRE><P/>", file=ecb)
-
- print("This module has been imported", file=ecb)
- print("%d times" % (reload_counter,), file=ecb)
- print("</BODY></HTML>", 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
- )
|