advanced.py 7.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196
  1. # This extension demonstrates some advanced features of the Python ISAPI
  2. # framework.
  3. # We demonstrate:
  4. # * Reloading your Python module without shutting down IIS (eg, when your
  5. # .py implementation file changes.)
  6. # * Custom command-line handling - both additional options and commands.
  7. # * Using a query string - any part of the URL after a '?' is assumed to
  8. # be "variable names" separated by '&' - we will print the values of
  9. # these server variables.
  10. # * If the tail portion of the URL is "ReportUnhealthy", IIS will be
  11. # notified we are unhealthy via a HSE_REQ_REPORT_UNHEALTHY request.
  12. # Whether this is acted upon depends on if the IIS health-checking
  13. # tools are installed, but you should always see the reason written
  14. # to the Windows event log - see the IIS documentation for more.
  15. from isapi import isapicon
  16. from isapi.simple import SimpleExtension
  17. import sys, os, stat
  18. if hasattr(sys, "isapidllhandle"):
  19. import win32traceutil
  20. # Notes on reloading
  21. # If your HttpFilterProc or HttpExtensionProc functions raises
  22. # 'isapi.InternalReloadException', the framework will not treat it
  23. # as an error but instead will terminate your extension, reload your
  24. # extension module, re-initialize the instance, and re-issue the request.
  25. # The Initialize functions are called with None as their param. The
  26. # return code from the terminate function is ignored.
  27. #
  28. # This is all the framework does to help you. It is up to your code
  29. # when you raise this exception. This sample uses a Win32 "find
  30. # notification". Whenever windows tells us one of the files in the
  31. # directory has changed, we check if the time of our source-file has
  32. # changed, and set a flag. Next imcoming request, we check the flag and
  33. # raise the special exception if set.
  34. #
  35. # The end result is that the module is automatically reloaded whenever
  36. # the source-file changes - you need take no further action to see your
  37. # changes reflected in the running server.
  38. # The framework only reloads your module - if you have libraries you
  39. # depend on and also want reloaded, you must arrange for this yourself.
  40. # One way of doing this would be to special case the import of these
  41. # modules. Eg:
  42. # --
  43. # try:
  44. # my_module = reload(my_module) # module already imported - reload it
  45. # except NameError:
  46. # import my_module # first time around - import it.
  47. # --
  48. # When your module is imported for the first time, the NameError will
  49. # be raised, and the module imported. When the ISAPI framework reloads
  50. # your module, the existing module will avoid the NameError, and allow
  51. # you to reload that module.
  52. from isapi import InternalReloadException
  53. import win32event, win32file, winerror, win32con, threading
  54. try:
  55. reload_counter += 1
  56. except NameError:
  57. reload_counter = 0
  58. # A watcher thread that checks for __file__ changing.
  59. # When it detects it, it simply sets "change_detected" to true.
  60. class ReloadWatcherThread(threading.Thread):
  61. def __init__(self):
  62. self.change_detected = False
  63. self.filename = __file__
  64. if self.filename.endswith("c") or self.filename.endswith("o"):
  65. self.filename = self.filename[:-1]
  66. self.handle = win32file.FindFirstChangeNotification(
  67. os.path.dirname(self.filename),
  68. False, # watch tree?
  69. win32con.FILE_NOTIFY_CHANGE_LAST_WRITE)
  70. threading.Thread.__init__(self)
  71. def run(self):
  72. last_time = os.stat(self.filename)[stat.ST_MTIME]
  73. while 1:
  74. try:
  75. rc = win32event.WaitForSingleObject(self.handle,
  76. win32event.INFINITE)
  77. win32file.FindNextChangeNotification(self.handle)
  78. except win32event.error as details:
  79. # handle closed - thread should terminate.
  80. if details.winerror != winerror.ERROR_INVALID_HANDLE:
  81. raise
  82. break
  83. this_time = os.stat(self.filename)[stat.ST_MTIME]
  84. if this_time != last_time:
  85. print("Detected file change - flagging for reload.")
  86. self.change_detected = True
  87. last_time = this_time
  88. def stop(self):
  89. win32file.FindCloseChangeNotification(self.handle)
  90. # The ISAPI extension - handles requests in our virtual dir, and sends the
  91. # response to the client.
  92. class Extension(SimpleExtension):
  93. "Python advanced sample Extension"
  94. def __init__(self):
  95. self.reload_watcher = ReloadWatcherThread()
  96. self.reload_watcher.start()
  97. def HttpExtensionProc(self, ecb):
  98. # NOTE: If you use a ThreadPoolExtension, you must still perform
  99. # this check in HttpExtensionProc - raising the exception from
  100. # The "Dispatch" method will just cause the exception to be
  101. # rendered to the browser.
  102. if self.reload_watcher.change_detected:
  103. print("Doing reload")
  104. raise InternalReloadException
  105. url = ecb.GetServerVariable("UNICODE_URL")
  106. if url.endswith("ReportUnhealthy"):
  107. ecb.ReportUnhealthy("I'm a little sick")
  108. ecb.SendResponseHeaders("200 OK", "Content-Type: text/html\r\n\r\n", 0)
  109. print("<HTML><BODY>", file=ecb)
  110. qs = ecb.GetServerVariable("QUERY_STRING")
  111. if qs:
  112. queries = qs.split("&")
  113. print("<PRE>", file=ecb)
  114. for q in queries:
  115. val = ecb.GetServerVariable(q, '&lt;no such variable&gt;')
  116. print("%s=%r" % (q, val), file=ecb)
  117. print("</PRE><P/>", file=ecb)
  118. print("This module has been imported", file=ecb)
  119. print("%d times" % (reload_counter,), file=ecb)
  120. print("</BODY></HTML>", file=ecb)
  121. ecb.close()
  122. return isapicon.HSE_STATUS_SUCCESS
  123. def TerminateExtension(self, status):
  124. self.reload_watcher.stop()
  125. # The entry points for the ISAPI extension.
  126. def __ExtensionFactory__():
  127. return Extension()
  128. # Our special command line customization.
  129. # Pre-install hook for our virtual directory.
  130. def PreInstallDirectory(params, options):
  131. # If the user used our special '--description' option,
  132. # then we override our default.
  133. if options.description:
  134. params.Description = options.description
  135. # Post install hook for our entire script
  136. def PostInstall(params, options):
  137. print()
  138. print("The sample has been installed.")
  139. print("Point your browser to /AdvancedPythonSample")
  140. print("If you modify the source file and reload the page,")
  141. print("you should see the reload counter increment")
  142. # Handler for our custom 'status' argument.
  143. def status_handler(options, log, arg):
  144. "Query the status of something"
  145. print("Everything seems to be fine!")
  146. custom_arg_handlers = {"status": status_handler}
  147. if __name__=='__main__':
  148. # If run from the command-line, install ourselves.
  149. from isapi.install import *
  150. params = ISAPIParameters(PostInstall = PostInstall)
  151. # Setup the virtual directories - this is a list of directories our
  152. # extension uses - in this case only 1.
  153. # Each extension has a "script map" - this is the mapping of ISAPI
  154. # extensions.
  155. sm = [
  156. ScriptMapParams(Extension="*", Flags=0)
  157. ]
  158. vd = VirtualDirParameters(Name="AdvancedPythonSample",
  159. Description = Extension.__doc__,
  160. ScriptMaps = sm,
  161. ScriptMapUpdate = "replace",
  162. # specify the pre-install hook.
  163. PreInstall = PreInstallDirectory
  164. )
  165. params.VirtualDirs = [vd]
  166. # Setup our custom option parser.
  167. from optparse import OptionParser
  168. parser = OptionParser('') # blank usage, so isapi sets it.
  169. parser.add_option("", "--description",
  170. action="store",
  171. help="custom description to use for the virtual directory")
  172. HandleCommandLine(params, opt_parser=parser,
  173. custom_arg_handlers = custom_arg_handlers)