Skip to content

External Routes, Server-Sent Events, Websockets #63

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 21 commits into from
Jul 31, 2023
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
8ee162d
Changed Route class to public
michalpokusa Jul 12, 2023
e6a0b02
Minor refactor of passing URL parameters to handler
michalpokusa Jul 13, 2023
802d7fd
Added Server.add_routes for importing external routes
michalpokusa Jul 13, 2023
8fa70b6
Preparing for returning persistent connection responses
michalpokusa Jul 13, 2023
90085c3
Minor tweaks in _send_headers
michalpokusa Jul 13, 2023
28ae6e5
Added SSEResponse class
michalpokusa Jul 13, 2023
ebb7ca7
Added example for SSEResponse
michalpokusa Jul 13, 2023
1e1ad58
Added Websocket class and SWITCHING_PROTOCOLS_101
michalpokusa Jul 13, 2023
4047ef5
Added example for Websocket
michalpokusa Jul 13, 2023
86d11c9
Modified neopixel example to use Server.add_routes()
michalpokusa Jul 13, 2023
20a4eda
Updated docs
michalpokusa Jul 13, 2023
d372f8e
CI fixes, reformating etc.
michalpokusa Jul 13, 2023
e34d27d
Fix: Wrong returns in docstring
michalpokusa Jul 13, 2023
5c30a2a
Added as_route decorator as shorthand for creating Route objects
michalpokusa Jul 16, 2023
9dfaf80
Merge remote-tracking branch 'origin/main' into external-routes-webso…
michalpokusa Jul 18, 2023
d4dc768
Included ethernet example in docs, fixes to emphasized lines
michalpokusa Jul 18, 2023
978a0c9
Minor change in as_route docstring
michalpokusa Jul 21, 2023
d389013
Modified as_route docstring to be more verbose
michalpokusa Jul 21, 2023
4063b5a
Updated Copyright headers
michalpokusa Jul 30, 2023
7d8c0b1
Made SSE and Websocket examples more visual
michalpokusa Jul 31, 2023
5e57a64
Fix: Wrong method in example and .json() for non-POST requests
michalpokusa Jul 31, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Next Next commit
Changed Route class to public
  • Loading branch information
michalpokusa committed Jul 12, 2023
commit 8ee162d5aca19f58da703ee494b84d333ddc2368
1 change: 1 addition & 0 deletions adafruit_httpserver/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@
JSONResponse,
Redirect,
)
from .route import Route
from .server import Server
from .status import (
Status,
Expand Down
52 changes: 28 additions & 24 deletions adafruit_httpserver/route.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,34 +20,41 @@
from .methods import GET


class _Route:
class Route:
"""Route definition for different paths, see `adafruit_httpserver.server.Server.route`."""

def __init__(
self,
path: str = "",
methods: Union[str, Set[str]] = GET,
handler: Callable = None,
*,
append_slash: bool = False,
) -> None:
self._validate_path(path)
self._validate_path(path, append_slash)

self.parameters_names = [
name[1:-1] for name in re.compile(r"/[^<>]*/?").split(path) if name != ""
]
self.path = re.sub(r"<\w+>", r"([^/]+)", path).replace("....", r".+").replace(
"...", r"[^/]+"
) + ("/?" if append_slash else "")
self.methods = methods if isinstance(methods, set) else {methods}
self.methods = set(methods) if isinstance(methods, (set, list)) else set([methods])

self.handler = handler

@staticmethod
def _validate_path(path: str) -> None:
def _validate_path(path: str, append_slash: bool) -> None:
if not path.startswith("/"):
raise ValueError("Path must start with a slash.")

if "<>" in path:
raise ValueError("All URL parameters must be named.")

def match(self, other: "_Route") -> Tuple[bool, List[str]]:
if path.endswith("/") and append_slash:
raise ValueError("Cannot use append_slash=True when path ends with /")

def match(self, other: "Route") -> Tuple[bool, List[str]]:
"""
Checks if the route matches the other route.

Expand All @@ -59,34 +66,34 @@ def match(self, other: "_Route") -> Tuple[bool, List[str]]:

Examples::

route = _Route("/example", GET, True)
route = Route("/example", GET, True)

other1a = _Route("/example", GET)
other1b = _Route("/example/", GET)
other1a = Route("/example", GET)
other1b = Route("/example/", GET)
route.matches(other1a) # True, []
route.matches(other1b) # True, []

other2 = _Route("/other-example", GET)
other2 = Route("/other-example", GET)
route.matches(other2) # False, []

...

route = _Route("/example/<parameter>", GET)
route = Route("/example/<parameter>", GET)

other1 = _Route("/example/123", GET)
other1 = Route("/example/123", GET)
route.matches(other1) # True, ["123"]

other2 = _Route("/other-example", GET)
other2 = Route("/other-example", GET)
route.matches(other2) # False, []

...

route1 = _Route("/example/.../something", GET)
other1 = _Route("/example/123/something", GET)
route1 = Route("/example/.../something", GET)
other1 = Route("/example/123/something", GET)
route1.matches(other1) # True, []

route2 = _Route("/example/..../something", GET)
other2 = _Route("/example/123/456/something", GET)
route2 = Route("/example/..../something", GET)
other2 = Route("/example/123/456/something", GET)
route2.matches(other2) # True, []
"""

Expand All @@ -103,23 +110,20 @@ def __repr__(self) -> str:
path = repr(self.path)
methods = repr(self.methods)

return f"_Route(path={path}, methods={methods})"
return f"Route(path={path}, methods={methods})"


class _Routes:
"""A collection of routes and their corresponding handlers."""

def __init__(self) -> None:
self._routes: List[_Route] = []
self._handlers: List[Callable] = []
self._routes: List[Route] = []

def add(self, route: _Route, handler: Callable):
def add(self, route: Route):
"""Adds a route and its handler to the collection."""

self._routes.append(route)
self._handlers.append(handler)

def find_handler(self, route: _Route) -> Union[Callable["...", "Response"], None]:
def find_handler(self, route: Route) -> Union[Callable["...", "Response"], None]:
"""
Finds a handler for a given route.

Expand All @@ -146,7 +150,7 @@ def route_func(request, my_parameter):
if not found_route:
return None

handler = self._handlers[self._routes.index(_route)]
handler = _route.handler

keyword_parameters = dict(zip(_route.parameters_names, parameters_values))

Expand Down
9 changes: 2 additions & 7 deletions adafruit_httpserver/server.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@
from .methods import GET, HEAD
from .request import Request
from .response import Response, FileResponse
from .route import _Routes, _Route
from .route import _Routes, Route
from .status import BAD_REQUEST_400, UNAUTHORIZED_401, FORBIDDEN_403, NOT_FOUND_404


Expand Down Expand Up @@ -117,13 +117,8 @@ def route_func(request, my_parameter):
def route_func(request):
...
"""
if path.endswith("/") and append_slash:
raise ValueError("Cannot use append_slash=True when path ends with /")

methods = set(methods) if isinstance(methods, (set, list)) else set([methods])

def route_decorator(func: Callable) -> Callable:
self._routes.add(_Route(path, methods, append_slash), func)
self._routes.add(Route(path, methods, func, append_slash=append_slash))
return func

return route_decorator
Expand Down