102 lines
3.1 KiB
Python
102 lines
3.1 KiB
Python
#
|
|
# Cassini
|
|
#
|
|
# Copyright (C) 2023 Vladimir Vukicevic
|
|
# License: MIT
|
|
#
|
|
|
|
import logging
|
|
import asyncio
|
|
import os
|
|
import hashlib
|
|
|
|
class SimpleHTTPServer:
|
|
BufferSize = 1024768
|
|
|
|
def __init__(self, host="0.0.0.0", port=0):
|
|
self.host = host
|
|
self.port = port
|
|
self.server = None
|
|
self.routes = {}
|
|
|
|
def register_file_route(self, path, filename):
|
|
size = os.path.getsize(filename)
|
|
md5 = hashlib.md5()
|
|
with open(filename, 'rb') as f:
|
|
while True:
|
|
data = f.read(1024)
|
|
if not data:
|
|
break
|
|
md5.update(data)
|
|
route = { 'file': filename, 'size': size, 'md5': md5.hexdigest() }
|
|
self.routes[path] = route
|
|
return route
|
|
|
|
def unregister_file_route(self, path):
|
|
del self.routes[path]
|
|
|
|
async def start(self):
|
|
self.server = await asyncio.start_server(self.handle_client, self.host, self.port)
|
|
self.port = self.server.sockets[0].getsockname()[1]
|
|
logging.debug(f'HTTP Listening on {self.server.sockets[0].getsockname()}')
|
|
|
|
async def serve_forever(self):
|
|
await self.server.serve_forever()
|
|
|
|
async def handle_client(self, reader, writer):
|
|
try:
|
|
await self.handle_client_inner(reader, writer)
|
|
except Exception as e:
|
|
logging.error(f"HTTP Exception handling client: {e}")
|
|
|
|
async def handle_client_inner(self, reader, writer):
|
|
logging.debug(f"HTTP connection from {writer.get_extra_info('peername')}")
|
|
data = b''
|
|
while True:
|
|
data += await reader.read(1024)
|
|
if b'\r\n\r\n' in data:
|
|
break
|
|
|
|
logging.debug(f"HTTP request: {data}")
|
|
request_line = data.decode().splitlines()[0]
|
|
method, path, _ = request_line.split()
|
|
|
|
if path not in self.routes:
|
|
logging.debug(f"HTTP path {path} not found in routes")
|
|
logging.debug(self.routes)
|
|
writer.write("HTTP/1.1 404 Not Found\r\n".encode())
|
|
writer.close()
|
|
return
|
|
|
|
route = self.routes[path]
|
|
logging.debug(f"HTTP method {method} path {path} route: {route}")
|
|
|
|
header = f"HTTP/1.1 200 OK\r\n"
|
|
#header += f"Content-Type: application/octet-stream\r\n"
|
|
header += f"Content-Type: text/plain; charset=utf-8\r\n"
|
|
header += f"Etag: {route['md5']}\r\n"
|
|
header += f"Content-Length: {route['size']}\r\n"
|
|
header += "\r\n"
|
|
|
|
logging.debug(f"Writing header:\n{header}")
|
|
writer.write(header.encode())
|
|
|
|
if method == "GET":
|
|
total = 0
|
|
with open(route['file'], 'rb') as f:
|
|
while True:
|
|
data = f.read(self.BufferSize)
|
|
if not data:
|
|
break
|
|
writer.write(data)
|
|
logging.debug(f"HTTP wrote {len(data)} bytes")
|
|
#await asyncio.sleep(1)
|
|
total += len(data)
|
|
logging.debug(f"HTTP wrote total {total} bytes")
|
|
|
|
await writer.drain()
|
|
writer.close()
|
|
await writer.wait_closed()
|
|
logging.debug(f"HTTP connection closed")
|
|
|