Spaces:
Running
Running
# This extension is used mainly for testing purposes - it is not | |
# designed to be a simple sample, but instead is a hotch-potch of things | |
# that attempts to exercise the framework. | |
import os | |
import stat | |
import sys | |
from isapi import isapicon | |
from isapi.simple import SimpleExtension | |
if hasattr(sys, "isapidllhandle"): | |
import win32traceutil | |
# We use the same reload support as 'advanced.py' demonstrates. | |
import threading | |
import win32con | |
import win32event | |
import win32file | |
import winerror | |
from isapi import InternalReloadException | |
# 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) | |
def TransmitFileCallback(ecb, hFile, cbIO, errCode): | |
print("Transmit complete!") | |
ecb.close() | |
# The ISAPI extension - handles requests in our virtual dir, and sends the | |
# response to the client. | |
class Extension(SimpleExtension): | |
"Python test 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 | |
if ecb.GetServerVariable("UNICODE_URL").endswith("test.py"): | |
file_flags = ( | |
win32con.FILE_FLAG_SEQUENTIAL_SCAN | win32con.FILE_FLAG_OVERLAPPED | |
) | |
hfile = win32file.CreateFile( | |
__file__, | |
win32con.GENERIC_READ, | |
0, | |
None, | |
win32con.OPEN_EXISTING, | |
file_flags, | |
None, | |
) | |
flags = ( | |
isapicon.HSE_IO_ASYNC | |
| isapicon.HSE_IO_DISCONNECT_AFTER_SEND | |
| isapicon.HSE_IO_SEND_HEADERS | |
) | |
# We pass hFile to the callback simply as a way of keeping it alive | |
# for the duration of the transmission | |
try: | |
ecb.TransmitFile( | |
TransmitFileCallback, | |
hfile, | |
int(hfile), | |
"200 OK", | |
0, | |
0, | |
None, | |
None, | |
flags, | |
) | |
except: | |
# Errors keep this source file open! | |
hfile.Close() | |
raise | |
else: | |
# default response | |
ecb.SendResponseHeaders("200 OK", "Content-Type: text/html\r\n\r\n", 0) | |
print("<HTML><BODY>", file=ecb) | |
print("The root of this site is at", ecb.MapURLToPath("/"), 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 /PyISAPITest") | |
# 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="PyISAPITest", | |
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 | |
) | |