import os
import io
import re
from http.server import HTTPServer, BaseHTTPRequestHandler
posts = {}
gets = {}
def find_get(path):
try:
return gets[path]()
except KeyError:
return "404"
def find_post(path, request):
try:
return posts[path](request)
except KeyError:
return "404"
def get_relative_path():
return os.path.dirname(os.path.abspath(__file__))
def read_html(relative_file_path):
""" Reads the html file on the file location provided
and injects any external files (marked with {{file_path}} )
that may be present. Returns the html content as a string.
Example:
>>> html_string = read_html('templates/index.html')
"""
dir_path = get_relative_path()
file_path = os.path.join(dir_path, relative_file_path)
with open(file_path) as file:
html_content = file.read()
html_content = inject_external_files(html_content)
return html_content
def inject_external_files(html_content):
""" Replaces {{ 'file_path' }} with the file content of that file
path. Useful for seperation of javascript and css files.
Uses regex to capture the pattern. Here is a link to see how it
works: https://regex101.com/r/v917NK/2
"""
# https://regex101.com/
pattern = r'{{([^}]+)}}'
external_files = re.findall(pattern, html_content)
new_html_content = html_content
for match in external_files:
external_file_path = match.replace(' ', '')
external_file = open(os.path.join(get_relative_path(), external_file_path))
file_content = external_file.read()
external_file.close()
if match.find('.css') != -1:
file_content = ''
elif match.find('.js') != -1:
file_content = ''
to_be_replaced = '{{' + match + '}}'
new_html_content = new_html_content.replace(to_be_replaced, file_content)
return new_html_content
def post(route = '/'):
""" A decorator that takes in the route path as argument, and then
puts the post handler in the dictionary, with the route as a key.
The handler will receive one argument called 'body', where the body
of the post request will exist. """
def decorator(handler_function):
posts[route] = handler_function
return handler_function
return decorator
def get(route = '/'):
""" A decorator that takes in the route path as argument, and then
puts the get handler in the dictionary, with the route as a key.
The handler function should return either `bytes` or `str`. """
def decorator(handler_function):
gets[route] = handler_function
return handler_function
return decorator
# Explanation: https://blog.anvileight.com/posts/simple-python-http-server/
class Handler(BaseHTTPRequestHandler):
""" The class responsible for handling all the
different requests made to our server
"""
def _set_headers(self):
""" Standard header stuff that has to be done """
self.send_response(200)
self.send_header("Content-type", "text/html")
self.end_headers()
def do_GET(self):
""" Just serve up our one and only html page """
self._set_headers()
res = find_get(self.path)
if hasattr(res, 'encode'):
self.wfile.write(res.encode())
else:
self.wfile.write(res)
def do_POST(self):
""" An async ajax request. Find the function from 'posts.py' to handle this
particular request.
"""
self.send_response(200)
self.end_headers()
content_length = int(self.headers['Content-Length'])
body = self.rfile.read(content_length)
res = find_post(self.path, body)
self.wfile.write(res.encode())
def run_server (port = 8314):
server_adress = ('', port) # Should make the port a command line argument
server = HTTPServer(server_adress, Handler)
print(f'Starting server on port: {port}.')
server.serve_forever()