diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..282f14e --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +dist +build +*.egg-info/ +__pycache__/ diff --git a/README.md b/README.md new file mode 100644 index 0000000..d244764 --- /dev/null +++ b/README.md @@ -0,0 +1,54 @@ +# ksalf + +[![PyPI](https://img.shields.io/pypi/v/ksalf.svg)](https://pypi.org/project/ksalf/) + +A lightweight experimental HTTP webserver inspired by flask. +**! This is just a experimental (fun) project, please don't use it in production !** + +## Implemtation +Ksalf is a lightweight extension of the [python (base) http server](https://docs.python.org/3/library/http.server.html) . +It provides new feature like **url parsing and html responses**, to the python in-build http server. +Ksalf currently only supports **GET** requests. +The project was inspired by the [flask python project](https://github.com/pallets/flask). + +## Installtion +``` +pip install ksalf +``` + +## Example +``` +from http.server import HTTPServer +from ksalf import HTTPHandler + +class Handler(HTTPHandler): + + @HTTPHandler.route("/health") + def GET(self): + self.respond(b'healthy') + +if __name__ == "__main__": + PORT = 8080 + httpd = HTTPServer(('0.0.0.0', PORT), Handler) + print("Server running on http://localhost:" + str(PORT)) + httpd.serve_forever() +``` + +This example would serve a simple web app on your localhost:8080. +You can register a route with @HTTPHandler.route("/"). +*GET* requests always get processed by the `def GET(self):` implementation. +**curl** +``` +curl localhost:8080/health +``` +**Response** +``` +healthy +``` + + +## Future Development +* Implement tests +* Support other request methods than just *GET* +* Advanced URL parsing +* Enhance HTML Responses diff --git a/ksalf/__init__.py b/ksalf/__init__.py new file mode 100644 index 0000000..2353547 --- /dev/null +++ b/ksalf/__init__.py @@ -0,0 +1 @@ +from .handler import HTTPHandler diff --git a/ksalf/handler.py b/ksalf/handler.py new file mode 100644 index 0000000..6a70813 --- /dev/null +++ b/ksalf/handler.py @@ -0,0 +1,90 @@ +from http.server import BaseHTTPRequestHandler +from http import HTTPStatus +import re +from os import curdir, sep + +class HTTPHandler(BaseHTTPRequestHandler): + endpoints = {} + + def route(route_path = "/"): + """ + Registers a GET route for the handler. + + Keyword Arguments: + route_path: request route -> string (default: "/") + """ + def decorate(func): + # Gets called on init + # Registers a function for the given route. + arguments = func.__code__.co_varnames[:func.__code__.co_argcount] + HTTPHandler.endpoints[route_path] = func + def call(self, *args, **kwargs): + # Gets called when the function gets called + # Returns method, which is associated with the requests route + if self.request_path in self.endpoints: + result = self.endpoints[self.request_path](self, *args, **kwargs) + elif self.request_path.endswith(".css"): + f = open(curdir + sep + self.request_path) + self.respond(f.read().encode(), "text/css") + f.close() + return + else: + return + return result + return call + return decorate + + + def do_GET(self): + """ + Gets called on a GET request and parses the url paramters of the request. + It then calls the GET() method. + """ + self.parameters = self.__parse_request() + self.request_path = self.parameters.get("path") + self.GET() + + + def __parse_request(self): + """ + Parses the arguements(after the ?) in the url. + + Example: + localhost:8080?name=peter&year=2020 + paramter["name"] = "peter" + paramter["year"] = "2020" + + Return: + parameters: arguments -> dict + """ + parameters = {} + splitted_path = self.path.split("?") + parameters["path"] = splitted_path[0] + params = re.findall(r"[\w']+", ' '.join(map(str, splitted_path[1:]))) + if len(params) % 2: + params.pop() + if len(params) > 1: + for i in range(0, len(params), 2): + parameters[params[i]] = params[i+1] + return parameters + + + def respond(self, response_blob, mimetype = "text/html"): + """ Create a http response. + + Keyword arguments: + response_blob: content -> bytes + mimetype: Returntype, please specify -> string (default: text/plain) + """ + self.send_response(200) + self.send_header('Content-type', mimetype) + self.end_headers() + self.wfile.write(response_blob) + return + + def GET(self): + """ + Default method for a GET request. + Override with own implementation. + """ + pass diff --git a/setup.py b/setup.py new file mode 100644 index 0000000..51b0130 --- /dev/null +++ b/setup.py @@ -0,0 +1,22 @@ +import setuptools + +with open("README.md", "r") as fh: + long_description = fh.read() + +setuptools.setup( + name="ksalf", + version="0.0.2", + author="Benjamin Haegenlaeuer", + author_email="benni.haegenlaeuer@outlook.de", + description="A lightweight webserver implementation [inspired by flask]", + long_description=long_description, + long_description_content_type="text/markdown", + url="https://github.com/Haegi/ksalf", + packages=setuptools.find_packages(), + classifiers=[ + "Programming Language :: Python :: 3", + "License :: OSI Approved :: MIT License", + "Operating System :: OS Independent", + ], + python_requires='>=3.6', +)