diff --git a/spiderweb/exceptions.py b/spiderweb/exceptions.py index fc0eaf4..09ccbe6 100644 --- a/spiderweb/exceptions.py +++ b/spiderweb/exceptions.py @@ -26,4 +26,8 @@ class ParseError(SpiderwebException): class GeneralException(SpiderwebException): - pass \ No newline at end of file + pass + + +class UnusedMiddleware(SpiderwebException): + pass diff --git a/spiderweb/main.py b/spiderweb/main.py index 478414f..fa2f913 100644 --- a/spiderweb/main.py +++ b/spiderweb/main.py @@ -12,6 +12,7 @@ from typing import Callable, Any from spiderweb.converters import * # noqa: F403 from spiderweb.exceptions import APIError, ConfigError, ParseError, GeneralException +from spiderweb.request import Request log = logging.getLogger(__name__) @@ -95,7 +96,7 @@ class APIServer(HTTPServer): self.add_route(route, method) try: - super().__init__(server_address, HandlerClass) + super().__init__(server_address, self.handler_class) except OSError: raise GeneralException("Port already in use.") @@ -137,21 +138,32 @@ class APIHandler(BaseHTTPRequestHandler): # BaseHTTPRequestHandler uses for some weird reason _routes = {} + def get_request(self): + return Request( + content="", + body="", + method=self.command, + headers=self.headers, + path=self.path + ) + def do_GET(self): - self.do_action() + request = self.get_request() + self.handle_request(request) def do_POST(self): content = "{}" if self.headers["Content-Length"]: length = int(self.headers["Content-Length"]) content = self.rfile.read(length) - info = None + request = self.get_request() + request.content = content if content: try: - info = json.loads(content) + request.json() except json.JSONDecodeError: raise APIError(400, "Invalid JSON", content) - self.do_action(info) + self.handle_request(request) def get_route(self, path) -> tuple[Callable, dict[str, Any]]: for option in self._routes.keys(): @@ -161,23 +173,22 @@ class APIHandler(BaseHTTPRequestHandler): ) raise APIError(404, "No route found") - def do_action(self, info=None): - info = info or {} + def handle_request(self, request): try: - url = urlparse.urlparse(self.path) + request.url = urlparse.urlparse(request.path) - handler, additional_args = self.get_route(url.path) + handler, additional_args = self.get_route(request.url.path) - if url.query: - params = urlparse.parse_qs(url.query) + if request.url.query: + params = urlparse.parse_qs(request.url.query) else: params = {} - info.update(params) + request.query_params = params if handler: try: - response = handler(info, **additional_args) + response = handler(request, **additional_args) self.send_response(200) if response is None: response = "" diff --git a/spiderweb/middleware.py b/spiderweb/middleware.py index e69de29..7639187 100644 --- a/spiderweb/middleware.py +++ b/spiderweb/middleware.py @@ -0,0 +1,31 @@ +from typing import Optional, NoReturn + +from spiderweb.request import Request +from spiderweb.response import HttpResponse + + +class SpiderwebMiddleware: + """ + All middleware should inherit from this class and have the following + (optional!) methods: + + process_request(self, request) -> None or Response + process_response(self, request, response) -> None + + Middleware can be used to modify requests and responses in a variety of ways. + If one of the two methods is not defined, the request or response will be passed + through unmodified. + + If `process_request` returns + + """ + def process_request(self, request: Request) -> HttpResponse | None: + # example of a middleware that sets a flag on the request + request.spiderweb = True + + + def process_response(self, request: Request, response: HttpResponse) -> NoReturn: + # example of a middleware that sets a header on the response + if hasattr(request, 'spiderweb'): + response['X-Spiderweb'] = 'true' + return response diff --git a/spiderweb/request.py b/spiderweb/request.py new file mode 100644 index 0000000..48390f9 --- /dev/null +++ b/spiderweb/request.py @@ -0,0 +1,17 @@ +import json + + +class Request: + def __init__(self, content=None, body=None, method=None, headers=None, path=None, url=None, query_params=None): + self.content: str = content + self.body: str = body + self.method: str = method + self.headers: dict[str] = headers + self.path: str = path + self.url = url + self.query_params = query_params + + def json(self): + return json.loads(self.content) + + diff --git a/spiderweb/response.py b/spiderweb/response.py new file mode 100644 index 0000000..5a94653 --- /dev/null +++ b/spiderweb/response.py @@ -0,0 +1,14 @@ +class HttpResponse: + ... + + +class JsonResponse(HttpResponse): + ... + + +class RedirectResponse(HttpResponse): + ... + + +class TemplateResponse(HttpResponse): + ...