CGI implement custom response format

This commit is contained in:
Michael Lazar 2019-08-27 09:47:53 -04:00
parent 4157a7b167
commit 24907f821b
2 changed files with 35 additions and 22 deletions

View File

@ -6,4 +6,5 @@ server.
""" """
import cgi import cgi
print("200 text/html")
cgi.test() cgi.test()

View File

@ -204,7 +204,7 @@ class StaticDirectoryApplication(JetforceApplication):
self, self,
root_directory: str = "/var/gemini", root_directory: str = "/var/gemini",
index_file: str = "index.gmi", index_file: str = "index.gmi",
cgi_directory: str = "cgi-bin/", cgi_directory: str = "/cgi-bin",
): ):
super().__init__() super().__init__()
self.routes.append((RoutePattern(), self.serve_static_file)) self.routes.append((RoutePattern(), self.serve_static_file))
@ -218,7 +218,10 @@ class StaticDirectoryApplication(JetforceApplication):
self.mimetypes.add_type("text/gemini", ".gemini") self.mimetypes.add_type("text/gemini", ".gemini")
def serve_static_file(self, request: Request): def serve_static_file(self, request: Request):
"""
Convert a URL into a filesystem path, and attempt to serve the file
or directory that is represented at that path.
"""
url_path = pathlib.Path(request.path.strip("/")) url_path = pathlib.Path(request.path.strip("/"))
filename = pathlib.Path(os.path.normpath(str(url_path))) filename = pathlib.Path(os.path.normpath(str(url_path)))
@ -229,19 +232,26 @@ class StaticDirectoryApplication(JetforceApplication):
filesystem_path = self.root / filename filesystem_path = self.root / filename
if filesystem_path.is_file(): if filesystem_path.is_file():
if str(filename).startswith(self.cgi_directory): is_cgi = str(filename).startswith(self.cgi_directory)
if os.access(filesystem_path, os.X_OK): is_exe = os.access(filesystem_path, os.X_OK)
return self.run_cgi_script(filesystem_path, request.environ) if is_cgi and is_exe:
mimetype = self.guess_mimetype(filesystem_path.name) return self.run_cgi_script(filesystem_path, request.environ)
generator = self.load_file(filesystem_path)
return Response(Status.SUCCESS, mimetype, generator) else:
mimetype = self.guess_mimetype(filesystem_path.name)
generator = self.load_file(filesystem_path)
return Response(Status.SUCCESS, mimetype, generator)
elif filesystem_path.is_dir(): elif filesystem_path.is_dir():
index_file = filesystem_path / self.index_file index_file = filesystem_path / self.index_file
if index_file.exists(): if index_file.exists():
generator = self.load_file(index_file) generator = self.load_file(index_file)
return Response(Status.SUCCESS, "text/gemini", generator)
else: else:
generator = self.list_directory(url_path, filesystem_path) generator = self.list_directory(url_path, filesystem_path)
return Response(Status.SUCCESS, "text/gemini", generator) return Response(Status.SUCCESS, "text/gemini", generator)
else: else:
return Response(Status.NOT_FOUND, "Not Found") return Response(Status.NOT_FOUND, "Not Found")
@ -260,22 +270,21 @@ class StaticDirectoryApplication(JetforceApplication):
errors="surrogateescape", errors="surrogateescape",
) )
# Try to get the mimetype from the CGI response's content-type. status_line = out.stdout.readline().strip()
# Discard all other response headers for now. status_parts = status_line.split(maxsplit=1)
mimetype = "text/plain" if len(status_parts) != 2 or not status_parts[0].isdecimal():
for line in out.stdout: return Response(Status.CGI_ERROR, "Script generated invalid response")
header = line.lower().strip()
if header.startswith("content-type:"): status, meta = status_parts
mimetype = header.split(":", maxsplit=1)[1]
elif not header:
# A empty line signals the end of the headers
break
# Re-encode the rest of the body as bytes # Re-encode the rest of the body as bytes
body = codecs.iterencode(out.stdout, encoding="utf-8", errors="surrogateescape") body = codecs.iterencode(out.stdout, encoding="utf-8", errors="surrogateescape")
return Response(Status.SUCCESS, mimetype, body) return Response(int(status), meta, body)
def load_file(self, filesystem_path: pathlib.Path): def load_file(self, filesystem_path: pathlib.Path):
"""
Load a file using a generator to allow streaming data to the TCP socket.
"""
with filesystem_path.open("rb") as fp: with filesystem_path.open("rb") as fp:
data = fp.read(1024) data = fp.read(1024)
while data: while data:
@ -300,6 +309,9 @@ class StaticDirectoryApplication(JetforceApplication):
yield f"=>/{url_path / file.name}\t{file.name}\r\n".encode() yield f"=>/{url_path / file.name}\t{file.name}\r\n".encode()
def guess_mimetype(self, filename: str): def guess_mimetype(self, filename: str):
"""
Guess the mimetype of a file based on the file extension.
"""
mime, encoding = self.mimetypes.guess_type(filename) mime, encoding = self.mimetypes.guess_type(filename)
if encoding: if encoding:
return f"{mime}; charset={encoding}" return f"{mime}; charset={encoding}"
@ -578,8 +590,8 @@ def run_server() -> None:
) )
parser.add_argument( parser.add_argument(
"--cgi-dir", "--cgi-dir",
help="CGI script folder, relative to the root path", help="CGI script folder, relative to the server's root directory",
default="cgi-bin/", default="/cgi-bin",
) )
parser.add_argument( parser.add_argument(
"--index-file", help="The gemini directory index file", default="index.gmi" "--index-file", help="The gemini directory index file", default="index.gmi"