mirror of https://github.com/dirtbags/moth.git
A few additions to dev server so it can run behind reverse proxy
This commit is contained in:
parent
e9bd074da4
commit
ae028eb4ec
|
@ -0,0 +1,10 @@
|
||||||
|
FROM alpine
|
||||||
|
|
||||||
|
ARG http_proxy
|
||||||
|
ENV http_proxy=${http_proxy}
|
||||||
|
|
||||||
|
RUN apk --no-cache add python3 py3-pillow
|
||||||
|
|
||||||
|
COPY tools/package-puzzles.py tools/moth.py tools/mistune.py tools/answer_words.txt /moth/
|
||||||
|
|
||||||
|
ENTRYPOINT ["python3", "/moth/package-puzzles.py"]
|
|
@ -31,12 +31,12 @@ sys.dont_write_bytecode = True
|
||||||
# XXX: This will eventually cause a problem. Do something more clever here.
|
# XXX: This will eventually cause a problem. Do something more clever here.
|
||||||
seed = 1
|
seed = 1
|
||||||
|
|
||||||
def page(title, body, scripts=[]):
|
def page(title, body, baseurl, scripts=[]):
|
||||||
return """<!DOCTYPE html>
|
return """<!DOCTYPE html>
|
||||||
<html>
|
<html>
|
||||||
<head>
|
<head>
|
||||||
<title>{title}</title>
|
<title>{title}</title>
|
||||||
<link rel="stylesheet" href="/files/src/www/res/style.css">
|
<link rel="stylesheet" href="{baseurl}/files/src/www/res/style.css">
|
||||||
{scripts}
|
{scripts}
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
|
@ -48,18 +48,11 @@ def page(title, body, scripts=[]):
|
||||||
</html>""".format(
|
</html>""".format(
|
||||||
title=title,
|
title=title,
|
||||||
body=body,
|
body=body,
|
||||||
|
baseurl=baseurl,
|
||||||
scripts="\n".join('<script src="{}"></script>'.format(s) for s in scripts),
|
scripts="\n".join('<script src="{}"></script>'.format(s) for s in scripts),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def mdpage(body, scripts=[]):
|
|
||||||
try:
|
|
||||||
title, _ = body.split('\n', 1)
|
|
||||||
except ValueError:
|
|
||||||
title = "Result"
|
|
||||||
title = title.lstrip("#")
|
|
||||||
title = title.strip()
|
|
||||||
return page(title, mistune.markdown(body, escape=False), scripts=scripts)
|
|
||||||
|
|
||||||
|
|
||||||
# XXX: What horrors did we unleash with our chdir shenanigans that
|
# XXX: What horrors did we unleash with our chdir shenanigans that
|
||||||
|
@ -70,6 +63,17 @@ class ThreadingServer(socketserver.ForkingMixIn, http.server.HTTPServer):
|
||||||
|
|
||||||
class MothHandler(http.server.SimpleHTTPRequestHandler):
|
class MothHandler(http.server.SimpleHTTPRequestHandler):
|
||||||
puzzles_dir = "puzzles"
|
puzzles_dir = "puzzles"
|
||||||
|
base_url = ""
|
||||||
|
|
||||||
|
def mdpage(self, body, scripts=[]):
|
||||||
|
try:
|
||||||
|
title, _ = body.split('\n', 1)
|
||||||
|
except ValueError:
|
||||||
|
title = "Result"
|
||||||
|
title = title.lstrip("#")
|
||||||
|
title = title.strip()
|
||||||
|
return page(title, mistune.markdown(body, escape=False), self.base_url, scripts=scripts)
|
||||||
|
|
||||||
|
|
||||||
def handle_one_request(self):
|
def handle_one_request(self):
|
||||||
try:
|
try:
|
||||||
|
@ -89,9 +93,9 @@ class MothHandler(http.server.SimpleHTTPRequestHandler):
|
||||||
def do_GET(self):
|
def do_GET(self):
|
||||||
if self.path == "/":
|
if self.path == "/":
|
||||||
self.serve_front()
|
self.serve_front()
|
||||||
elif self.path.startswith("/puzzles"):
|
elif self.path.startswith("/puzzles/"):
|
||||||
self.serve_puzzles()
|
self.serve_puzzles(self.path)
|
||||||
elif self.path.startswith("/files"):
|
elif self.path.startswith("/files/"):
|
||||||
self.serve_file(self.translate_path(self.path))
|
self.serve_file(self.translate_path(self.path))
|
||||||
else:
|
else:
|
||||||
self.send_error(HTTPStatus.NOT_FOUND, "File not found")
|
self.send_error(HTTPStatus.NOT_FOUND, "File not found")
|
||||||
|
@ -109,24 +113,24 @@ MOTH Development Server Front Page
|
||||||
Yo, it's the front page.
|
Yo, it's the front page.
|
||||||
There's stuff you can do here:
|
There's stuff you can do here:
|
||||||
|
|
||||||
* [Available puzzles](/puzzles)
|
* [Available puzzles](puzzles/)
|
||||||
* [Raw filesystem view](/files/)
|
* [Raw filesystem view](files/)
|
||||||
* [Documentation](/files/docs/)
|
* [Documentation](files/docs/)
|
||||||
* [Instructions](/files/docs/devel-server.md) for using this server
|
* [Instructions](files/docs/devel-server.md) for using this server
|
||||||
|
|
||||||
If you use this development server to run a contest,
|
If you use this development server to run a contest,
|
||||||
you are a fool.
|
you are a fool.
|
||||||
"""
|
"""
|
||||||
payload = mdpage(body).encode('utf-8')
|
payload = self.mdpage(body).encode('utf-8')
|
||||||
self.send_response(HTTPStatus.OK)
|
self.send_response(HTTPStatus.OK)
|
||||||
self.send_header("Content-Type", "text/html; charset=utf-8")
|
self.send_header("Content-Type", "text/html; charset=utf-8")
|
||||||
self.send_header("Content-Length", len(payload))
|
self.send_header("Content-Length", len(payload))
|
||||||
self.end_headers()
|
self.end_headers()
|
||||||
self.wfile.write(payload)
|
self.wfile.write(payload)
|
||||||
|
|
||||||
def serve_puzzles(self):
|
def serve_puzzles(self, path):
|
||||||
body = io.StringIO()
|
body = io.StringIO()
|
||||||
path = self.path.rstrip('/')
|
path = path.rstrip('/')
|
||||||
parts = path.split("/")
|
parts = path.split("/")
|
||||||
scripts = []
|
scripts = []
|
||||||
title = None
|
title = None
|
||||||
|
@ -151,14 +155,14 @@ you are a fool.
|
||||||
body.write("<ul>")
|
body.write("<ul>")
|
||||||
for i in sorted(glob.glob(os.path.join(self.puzzles_dir, "*", ""))):
|
for i in sorted(glob.glob(os.path.join(self.puzzles_dir, "*", ""))):
|
||||||
bn = os.path.basename(i.strip('/\\'))
|
bn = os.path.basename(i.strip('/\\'))
|
||||||
body.write('<li><a href="/puzzles/{}">puzzles/{}/</a></li>'.format(bn, bn))
|
body.write('<li><a href="{}/">puzzles/{}/</a></li>'.format(bn, bn))
|
||||||
body.write("</ul>")
|
body.write("</ul>")
|
||||||
elif not puzzle:
|
elif not puzzle:
|
||||||
# List all point values in a category
|
# List all point values in a category
|
||||||
title = "Puzzles in category `{}`".format(parts[2])
|
title = "Puzzles in category `{}`".format(parts[2])
|
||||||
body.write("<ul>")
|
body.write("<ul>")
|
||||||
for points in cat.pointvals():
|
for points in cat.pointvals():
|
||||||
body.write('<li><a href="/puzzles/{cat}/{points}/">puzzles/{cat}/{points}/</a></li>'.format(cat=parts[2], points=points))
|
body.write('<li><a href="{points}/">puzzles/{cat}/{points}/</a></li>'.format(cat=parts[2], points=points))
|
||||||
body.write("</ul>")
|
body.write("</ul>")
|
||||||
elif len(parts) == 4:
|
elif len(parts) == 4:
|
||||||
# Serve up a puzzle
|
# Serve up a puzzle
|
||||||
|
@ -175,7 +179,7 @@ you are a fool.
|
||||||
visibility = ''
|
visibility = ''
|
||||||
else:
|
else:
|
||||||
visibility = '(unlisted)'
|
visibility = '(unlisted)'
|
||||||
body.write('<li><a href="/puzzles/{cat}/{points}/{filename}">{filename}</a> {visibility}</li>'
|
body.write('<li><a href="{filename}">{filename}</a> {visibility}</li>'
|
||||||
.format(cat=parts[2],
|
.format(cat=parts[2],
|
||||||
points=puzzle.points,
|
points=puzzle.points,
|
||||||
filename=name,
|
filename=name,
|
||||||
|
@ -210,7 +214,7 @@ you are a fool.
|
||||||
shutil.copyfileobj(pfile.stream, self.wfile)
|
shutil.copyfileobj(pfile.stream, self.wfile)
|
||||||
return
|
return
|
||||||
|
|
||||||
payload = page(title, body.getvalue(), scripts=scripts).encode('utf-8')
|
payload = page(title, body.getvalue(), self.base_url, scripts=scripts).encode('utf-8')
|
||||||
self.send_response(HTTPStatus.OK)
|
self.send_response(HTTPStatus.OK)
|
||||||
self.send_header("Content-Type", "text/html; charset=utf-8")
|
self.send_header("Content-Type", "text/html; charset=utf-8")
|
||||||
self.send_header("Content-Length", len(payload))
|
self.send_header("Content-Length", len(payload))
|
||||||
|
@ -235,7 +239,7 @@ you are a fool.
|
||||||
return
|
return
|
||||||
if path.endswith(".md"):
|
if path.endswith(".md"):
|
||||||
ctype = "text/html; charset=utf-8"
|
ctype = "text/html; charset=utf-8"
|
||||||
content = mdpage(payload.decode('utf-8'))
|
content = self.mdpage(payload.decode('utf-8'))
|
||||||
payload = content.encode('utf-8')
|
payload = content.encode('utf-8')
|
||||||
try:
|
try:
|
||||||
fs = fspath.stat()
|
fs = fspath.stat()
|
||||||
|
@ -252,7 +256,7 @@ you are a fool.
|
||||||
self.wfile.write(payload)
|
self.wfile.write(payload)
|
||||||
|
|
||||||
|
|
||||||
def run(address=('0.0.0.0', 8080), once=False):
|
def run(address=('127.0.0.1', 8080), once=False):
|
||||||
httpd = ThreadingServer(address, MothHandler)
|
httpd = ThreadingServer(address, MothHandler)
|
||||||
print("=== Listening on http://{}:{}/".format(address[0], address[1]))
|
print("=== Listening on http://{}:{}/".format(address[0], address[1]))
|
||||||
if once:
|
if once:
|
||||||
|
@ -264,10 +268,25 @@ if __name__ == '__main__':
|
||||||
import argparse
|
import argparse
|
||||||
|
|
||||||
parser = argparse.ArgumentParser(description="MOTH puzzle development server")
|
parser = argparse.ArgumentParser(description="MOTH puzzle development server")
|
||||||
parser.add_argument('--puzzles', default='puzzles',
|
parser.add_argument(
|
||||||
help="Directory containing your puzzles")
|
'--puzzles', default='puzzles',
|
||||||
parser.add_argument('--once', default=False, action='store_true',
|
help="Directory containing your puzzles"
|
||||||
help="Serve one page, then exit. For debugging the server.")
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
'--once', default=False, action='store_true',
|
||||||
|
help="Serve one page, then exit. For debugging the server."
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
'--bind', default="127.0.0.1:8080",
|
||||||
|
help="Bind to ip:port"
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
'--base', default="",
|
||||||
|
help="Base URL to this server, for reverse proxy setup"
|
||||||
|
)
|
||||||
args = parser.parse_args()
|
args = parser.parse_args()
|
||||||
|
addr, port = args.bind.split(":")
|
||||||
|
port = int(port)
|
||||||
MothHandler.puzzles_dir = args.puzzles
|
MothHandler.puzzles_dir = args.puzzles
|
||||||
run(once=args.once)
|
MothHandler.base_url = args.base
|
||||||
|
run(address=(addr, port), once=args.once)
|
||||||
|
|
Loading…
Reference in New Issue