Created
August 22, 2023 07:42
-
-
Save char101/e9b9ef4fb30d67759c337594e106c6ac to your computer and use it in GitHub Desktop.
Python http server to browse zipped documentations
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import email.utils | |
import mimetypes | |
import os | |
from contextlib import contextmanager | |
from http import HTTPStatus | |
from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer | |
from io import StringIO | |
from zipfile import ZipFile | |
DOC_DIR = '/directory/containing/zip/files' | |
archive_handles = {} | |
class Buffer(StringIO): | |
@contextmanager | |
def tag(self, name): | |
self.write(f'<{name}>\n') | |
yield | |
self.write(f'</{name}>\n') | |
class RequestHandler(BaseHTTPRequestHandler): | |
def do_GET(self): | |
path = self.path | |
if '?' in path: | |
path = path.split('?', 1)[0] | |
if '#' in path: | |
path = path.split('#', 1)[0] | |
if path == '/': | |
self._send_index() | |
return | |
parts = path.split('/', 2) | |
if len(parts) == 2 or not parts[1].endswith('.zip'): | |
self._send_content(HTTPStatus.NOT_FOUND, 'Not Found', 'text/plain') | |
return | |
_, archive, entry = parts | |
self._send_file(archive, entry) | |
def _send_content(self, status, content, type, last_modified=None): | |
if isinstance(content, str): | |
content = content.encode('utf-8') | |
self.send_response(status) | |
self.send_header('Content-Type', type) | |
self.send_header('Content-Length', len(content)) | |
if last_modified: | |
self.send_header('Last-Modified', email.utils.formatdate(last_modified, usegmt=True)) | |
self.end_headers() | |
self.wfile.write(content) | |
def _send_index(self): | |
buf = Buffer() | |
with buf.tag('html'): | |
with buf.tag('head'): | |
buf.write('<meta charset="utf-8">') | |
buf.write('<title>Index</title>') | |
with buf.tag('body'): | |
with buf.tag('ul'): | |
for file in os.listdir(DOC_DIR): | |
if file.endswith('.zip'): | |
buf.write(f'<li><a href="{file}/">{file}</a></li>') | |
self._send_content(HTTPStatus.OK, buf.getvalue().encode('utf-8'), 'text/html') | |
def _send_file(self, archive, entry): | |
global archive_handles | |
if archive not in archive_handles: | |
archive_path = os.path.join(DOC_DIR, archive) | |
archive_handles[archive] = (ZipFile(archive_path), int(os.path.getmtime(archive_path))) | |
zf, zf_time = archive_handles[archive] | |
if 'If-Modified-Since' in self.headers: | |
ims = int(email.utils.parsedate_to_datetime(self.headers['If-Modified-Since']).timestamp()) | |
if zf_time <= ims: | |
self.send_response(HTTPStatus.NOT_MODIFIED) | |
self.end_headers() | |
return | |
if entry == '' or entry.endswith('/'): | |
entry += 'index.html' | |
try: | |
with zf.open(entry) as f: | |
self._send_content(HTTPStatus.OK, f.read(), mimetypes.guess_type(entry)[0], zf_time) | |
except KeyError: | |
self._send_content(HTTPStatus.NOT_FOUND, 'Not Found', 'text/plain') | |
with ThreadingHTTPServer(('127.0.0.1', 8008), RequestHandler) as server: | |
server.serve_forever() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment