1 # This extension is used mainly for testing purposes - it is not 2 # designed to be a simple sample, but instead is a hotch-potch of things 3 # that attempts to exercise the framework. 4 5 import os 6 import stat 7 import sys 8 9 from isapi import isapicon 10 from isapi.simple import SimpleExtension 11 12 if hasattr(sys, "isapidllhandle"): 13 import win32traceutil 14 15 # We use the same reload support as 'advanced.py' demonstrates. 16 import threading 17 18 import win32con 19 import win32event 20 import win32file 21 import winerror 22 23 from isapi import InternalReloadException 24 25 26 # A watcher thread that checks for __file__ changing. 27 # When it detects it, it simply sets "change_detected" to true. 28 class ReloadWatcherThread(threading.Thread): 29 def __init__(self): 30 self.change_detected = False 31 self.filename = __file__ 32 if self.filename.endswith("c") or self.filename.endswith("o"): 33 self.filename = self.filename[:-1] 34 self.handle = win32file.FindFirstChangeNotification( 35 os.path.dirname(self.filename), 36 False, # watch tree? 37 win32con.FILE_NOTIFY_CHANGE_LAST_WRITE, 38 ) 39 threading.Thread.__init__(self) 40 41 def run(self): 42 last_time = os.stat(self.filename)[stat.ST_MTIME] 43 while 1: 44 try: 45 rc = win32event.WaitForSingleObject(self.handle, win32event.INFINITE) 46 win32file.FindNextChangeNotification(self.handle) 47 except win32event.error as details: 48 # handle closed - thread should terminate. 49 if details.winerror != winerror.ERROR_INVALID_HANDLE: 50 raise 51 break 52 this_time = os.stat(self.filename)[stat.ST_MTIME] 53 if this_time != last_time: 54 print("Detected file change - flagging for reload.") 55 self.change_detected = True 56 last_time = this_time 57 58 def stop(self): 59 win32file.FindCloseChangeNotification(self.handle) 60 61 62 def TransmitFileCallback(ecb, hFile, cbIO, errCode): 63 print("Transmit complete!") 64 ecb.close() 65 66 67 # The ISAPI extension - handles requests in our virtual dir, and sends the 68 # response to the client. 69 class Extension(SimpleExtension): 70 "Python test Extension" 71 72 def __init__(self): 73 self.reload_watcher = ReloadWatcherThread() 74 self.reload_watcher.start() 75 76 def HttpExtensionProc(self, ecb): 77 # NOTE: If you use a ThreadPoolExtension, you must still perform 78 # this check in HttpExtensionProc - raising the exception from 79 # The "Dispatch" method will just cause the exception to be 80 # rendered to the browser. 81 if self.reload_watcher.change_detected: 82 print("Doing reload") 83 raise InternalReloadException 84 85 if ecb.GetServerVariable("UNICODE_URL").endswith("test.py"): 86 file_flags = ( 87 win32con.FILE_FLAG_SEQUENTIAL_SCAN | win32con.FILE_FLAG_OVERLAPPED 88 ) 89 hfile = win32file.CreateFile( 90 __file__, 91 win32con.GENERIC_READ, 92 0, 93 None, 94 win32con.OPEN_EXISTING, 95 file_flags, 96 None, 97 ) 98 flags = ( 99 isapicon.HSE_IO_ASYNC 100 | isapicon.HSE_IO_DISCONNECT_AFTER_SEND 101 | isapicon.HSE_IO_SEND_HEADERS 102 ) 103 # We pass hFile to the callback simply as a way of keeping it alive 104 # for the duration of the transmission 105 try: 106 ecb.TransmitFile( 107 TransmitFileCallback, 108 hfile, 109 int(hfile), 110 "200 OK", 111 0, 112 0, 113 None, 114 None, 115 flags, 116 ) 117 except: 118 # Errors keep this source file open! 119 hfile.Close() 120 raise 121 else: 122 # default response 123 ecb.SendResponseHeaders("200 OK", "Content-Type: text/html\r\n\r\n", 0) 124 print("<HTML><BODY>", file=ecb) 125 print("The root of this site is at", ecb.MapURLToPath("/"), file=ecb) 126 print("</BODY></HTML>", file=ecb) 127 ecb.close() 128 return isapicon.HSE_STATUS_SUCCESS 129 130 def TerminateExtension(self, status): 131 self.reload_watcher.stop() 132 133 134 # The entry points for the ISAPI extension. 135 def __ExtensionFactory__(): 136 return Extension() 137 138 139 # Our special command line customization. 140 # Pre-install hook for our virtual directory. 141 def PreInstallDirectory(params, options): 142 # If the user used our special '--description' option, 143 # then we override our default. 144 if options.description: 145 params.Description = options.description 146 147 148 # Post install hook for our entire script 149 def PostInstall(params, options): 150 print() 151 print("The sample has been installed.") 152 print("Point your browser to /PyISAPITest") 153 154 155 # Handler for our custom 'status' argument. 156 def status_handler(options, log, arg): 157 "Query the status of something" 158 print("Everything seems to be fine!") 159 160 161 custom_arg_handlers = {"status": status_handler} 162 163 if __name__ == "__main__": 164 # If run from the command-line, install ourselves. 165 from isapi.install import * 166 167 params = ISAPIParameters(PostInstall=PostInstall) 168 # Setup the virtual directories - this is a list of directories our 169 # extension uses - in this case only 1. 170 # Each extension has a "script map" - this is the mapping of ISAPI 171 # extensions. 172 sm = [ScriptMapParams(Extension="*", Flags=0)] 173 vd = VirtualDirParameters( 174 Name="PyISAPITest", 175 Description=Extension.__doc__, 176 ScriptMaps=sm, 177 ScriptMapUpdate="replace", 178 # specify the pre-install hook. 179 PreInstall=PreInstallDirectory, 180 ) 181 params.VirtualDirs = [vd] 182 # Setup our custom option parser. 183 from optparse import OptionParser 184 185 parser = OptionParser("") # blank usage, so isapi sets it. 186 parser.add_option( 187 "", 188 "--description", 189 action="store", 190 help="custom description to use for the virtual directory", 191 ) 192 193 HandleCommandLine( 194 params, opt_parser=parser, custom_arg_handlers=custom_arg_handlers 195 ) 196