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()