196 lines
6.2 KiB
Python
196 lines
6.2 KiB
Python
# 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
|
|
)
|