Skip to content

Instantly share code, notes, and snippets.

@xchellx
Created June 6, 2022 08:14
Show Gist options
  • Save xchellx/a5251600263c32904ae5b8c07dee0e6e to your computer and use it in GitHub Desktop.
Save xchellx/a5251600263c32904ae5b8c07dee0e6e to your computer and use it in GitHub Desktop.
CORS-supported alternative to `py -3 -m http.server 8000`
#!/usr/bin/env python3
# encoding: utf-8
# Absolute TRUE process exit handling (atexit is psuedo-atexit, this is TRUE atexit!)
__all__ = ['register','unregister']
import sys
import os
from pathlib import Path
# Ensure current path is on the system path in case local relative import of libraries fail
CWD: str
CWD = None
try:
CWD = __file__
except NameError:
CWD = sys.argv[0]
CWD = os.fspath(Path(CWD).resolve().parent)
if (CWD not in sys.path):
sys.path.append(CWD)
import atexit
from signal import signal, getsignal, SIGINT, SIGTERM
if sys.platform.startswith('win'):
import win32api
import win32con
else:
from signal import SIGTSTP, SIGHUP
global __winsigs__checklist__
__winsigs__checklist__ = []
global __og_sig_handlers__
__og_sig_handlers__ = {
'SIGINT': getsignal(SIGINT),
'SIGTERM': getsignal(SIGTERM),
'SIGTSTP': getsignal(SIGTSTP) if not sys.platform.startswith('win') else None,
'SIGHUP': getsignal(SIGHUP) if not sys.platform.startswith('win') else None
}
def handle_signal(sig, frame):
sys.exit(sig)
def handle_signal_windows(sig):
global __winsigs__checklist__
__winsigs__checklist__ = [] if __winsigs__checklist__ is None else __winsigs__checklist__
if sig in __winsigs__checklist__:
sys.exit(sig)
def signal_windows(handler, handle_interrupt, remove):
if (handler is None):
raise ValueError('Parameter "handler" is required')
handle_interrupt = False if handle_interrupt is None else handle_interrupt
remove = False if remove is None else remove
global __winsigs__checklist__
__winsigs__checklist__ = [] if __winsigs__checklist__ is None else __winsigs__checklist__
if sys.platform.startswith('win'):
__winsigs__checklist__ = [win32con.CTRL_LOGOFF_EVENT, win32con.CTRL_SHUTDOWN_EVENT, win32con.CTRL_CLOSE_EVENT]
if handle_interrupt:
__winsigs__checklist__.append(win32con.CTRL_BREAK_EVENT)
__winsigs__checklist__.append(win32con.CTRL_C_EVENT)
if remove:
__winsigs__checklist__.clear()
win32api.SetConsoleCtrlHandler(handler, 0 if remove else 1)
else:
raise NotImplementedError('SetConsoleCtrlHandler is not supported on platforms other than windows')
def register(handler, handle_interrupt=None):
if (handler is None):
raise ValueError('Parameter "handler" is required')
handle_interrupt = False if handle_interrupt is None else handle_interrupt
global __og_sig_handlers__
atexit.register(handler)
if handle_interrupt:
signal(SIGINT, handle_signal)
signal(SIGTERM, handle_signal)
if sys.platform.startswith('win'):
signal_windows(handle_signal_windows, handle_interrupt, False)
else:
signal(SIGTSTP, handle_signal)
signal(SIGHUP, handle_signal)
def unregister(handler):
if (handler is None):
raise ValueError('Parameter "handler" is required')
global __og_sig_handlers__
atexit.unregister(handler)
signal(SIGINT, __og_sig_handlers__['SIGINT'])
signal(SIGTERM, __og_sig_handlers__['SIGTERM'])
if sys.platform.startswith('win'):
signal_windows(handle_signal_windows, True, True)
else:
signal(SIGTSTP, __og_sig_handlers__['SIGTSTP'])
signal(SIGHUP, __og_sig_handlers__['SIGHUP'])
if __name__ == '__main__':
print('Absolute TRUE process exit handling (atexit is psuedo-atexit, this is TRUE atexit!)')
@ECHO OFF
py -3 serve.py . -n localhost -p 8000 -d .__localservelogs__ -l
REM py -3 -m http.server 8000
PAUSE
#!/usr/bin/env python3
# encoding: utf-8
# Use instead of 'python3 -http.server 8000' when you need CORS
import sys
import os
from pathlib import Path
# Ensure current path is on the system path in case local relative import of libraries fail
CWD: str
CWD = None
try:
CWD = __file__
except NameError:
CWD = sys.argv[0]
CWD = os.fspath(Path(CWD).resolve().parent)
if (CWD not in sys.path):
sys.path.append(CWD)
import argparse
import exitlistener
from datetime import datetime, timezone
from functools import partial
from http.server import HTTPServer, SimpleHTTPRequestHandler
def log_is_valid():
global log
if log is None:
return False
else:
return not log.closed
def print_write(*args, **kwargs):
if log_is_valid():
global log
for arg in args:
log.write(arg)
log.write('\n')
log.flush()
print(*args, **kwargs)
def stdout_write(str):
if log_is_valid():
global log
log.write(str)
log.flush()
sys.stdout.write(str)
def close_log():
if log_is_valid():
global log
log.close()
def close_server():
global httpd
if not httpd is None:
httpd._BaseServer__is_shut_down.set()
httpd.__shutdown_request = True
httpd.shutdown()
httpd.server_close()
def onexit():
print_write('Shutting down server...')
try:
close_server()
except:
pass
try:
close_log()
except:
pass
try:
exitlistener.unregister(onexit)
except:
pass
# For HTTPServer
class CORSRequestHandler(SimpleHTTPRequestHandler):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
# Set according to: https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/MIME_types
self.extensions_map.update({
'.js': 'text/javascript', # Required or ES6 JavaScript modules will be interpreted incorrectly
'.html': 'text/html',
'.htm': 'text/html',
'.xml': 'application/xml',
'.css': 'text/css',
'.txt': 'text/plain',
'.apng': 'image/apng',
'.avif': 'image/avif',
'.gif': 'image/gif',
'.jpg': 'image/jpeg',
'.jpeg': 'image/jpeg',
'.jfif': 'image/jpeg',
'.pjpeg': 'image/jpeg',
'.pjp': 'image/jpeg',
'.png': 'image/png',
'.svg': 'image/svg+xml',
'.webp': 'image/webp',
'.wav': 'audio/wav',
'.webm': 'video/webm',
'.ogg': 'audio/ogg',
'.zip': 'application/zip',
'.7z': 'application/x-7z-compressed',
'.gz': 'application/gzip',
'.tar': 'application/x-tar',
'.tgz': 'application/x-gtar-compressed',
'.rar': 'application/rar',
'.vmo': 'application/x-virtools'
});
def end_headers(self):
self.send_response(200, 'ok')
# CORS headers (permissive)
self.send_header('Access-Control-Allow-Credentials', 'true')
self.send_header('Access-Control-Allow-Origin', '*')
self.send_header('Access-Control-Allow-Methods', 'GET, POST, OPTIONS')
self.send_header('Access-Control-Allow-Headers', 'X-Requested-With, Content-Type')
self.send_header('Cache-Control', 'no-store, no-cache, must-revalidate')
return super(CORSRequestHandler, self).end_headers()
def log_request(self, code='-', size='-'):
self.log_message('"%s" %s %s',
self.requestline, str(code), str(size))
def log_error(self, format, *args):
self.log_message(format, *args)
def log_message(self, format, *args):
msg = ('%s - - [%s] %s\n' %
(self.address_string(),
self.log_date_time_string(),
format%args))
stdout_write(msg)
def serve(directory, hostname, portnumber, enablelog, logdir):
# For logging to file
if not log_is_valid():
global log
log = open(
logdir + os.sep + 'serve' + str(
datetime.strptime(
datetime.now().strftime('%Y%m%d%H%M%S'), '%Y%m%d%H%M%S'
).replace(
tzinfo=timezone.utc
).timestamp()
) + '.log', 'w+', encoding='utf-8'
)
print_write('HTTPServer started on ' + str(hostname) + ' at port ' + str(portnumber))
print_write('To stop the server, close the window or send a cancel command via Ctrl+C.')
# Start server
global httpd
if httpd is None:
httpd = HTTPServer((hostname, portnumber), partial(CORSRequestHandler, directory=directory))
httpd.serve_forever()
if __name__ == '__main__':
STATUS_SUCCESS=0
STATUS_ERROR=1
try:
global log
log = None
global httpd
httpd = None
exitlistener.register(onexit, True)
parser = argparse.ArgumentParser(prog=sys.argv[0], formatter_class=lambda prog: argparse.ArgumentDefaultsHelpFormatter(
prog=sys.argv[0], max_help_position=os.get_terminal_size().columns, width=os.get_terminal_size().columns
))
parser.add_argument('-n', '--hostname', default='localhost', help='name of the host address (localhost, IP, etc.')
parser.add_argument('-p', '--portnumber', default='8000', type=int, help='port to host at')
parser.add_argument('-l', '--enablelog', action='store_true', help='enable log to file')
parser.add_argument('-d', '--logdir', default='logs', help='path to write log files to')
parser.add_argument('directory')
if len(sys.argv) == 1:
raise argparse.ArgumentError(None, 'Missing one or more arguments')
else:
args = parser.parse_args()
serve(args.directory, args.hostname, args.portnumber, args.enablelog, args.logdir)
sys.exit(STATUS_SUCCESS)
except (argparse.ArgumentError, argparse.ArgumentTypeError) as e:
print(e)
parser.print_help(sys.stderr)
sys.exit(STATUS_ERROR)
except (ValueError, ConnectionError) as e:
print_error(e)
sys.exit(STATUS_ERROR)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment