A few additions to dev server so it can run behind reverse proxy

This commit is contained in:
Neale Pickett 2017-11-09 21:47:25 +00:00
parent 3dec304423
commit 7662a67ca4
2 changed files with 60 additions and 31 deletions

10
Dockerfile.moth-compile Normal file
View File

@ -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"]

View File

@ -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)