Skip to content

Instantly share code, notes, and snippets.

@char101
Created August 22, 2023 07:42
Show Gist options
  • Save char101/e9b9ef4fb30d67759c337594e106c6ac to your computer and use it in GitHub Desktop.
Save char101/e9b9ef4fb30d67759c337594e106c6ac to your computer and use it in GitHub Desktop.
Python http server to browse zipped documentations
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