diff --git a/docs/user-guide/ivoa.rst b/docs/user-guide/ivoa.rst index a7bbd6c4..301c6efa 100644 --- a/docs/user-guide/ivoa.rst +++ b/docs/user-guide/ivoa.rst @@ -5,23 +5,29 @@ IVOA protocol support The IVOA web protocols aren't entirely RESTful and have some unusual requirements that are not provided by modern web frameworks. Safir provides some FastAPI support facilities to make implementing IVOA services easier. -Query parameter case insensitivity -================================== +Parameter case insensitivity +============================ Many IVOA protocols require the key of a query parameter to be case-insensitive. For example, the requests ``GET /api/foo?param=bar`` and ``GET /api/foo?PARAM=bar`` are supposed to produce identical results. -Safir provides `safir.middleware.ivoa.CaseInsensitiveQueryMiddleware` to implement this protocol requirement. +The same is true for parameters provided in form bodies to ``POST``. -Add this middleware to the FastAPI application: +Safir provides two middlewares, `~safir.middleware.ivoa.CaseInsensitiveQueryMiddleware` and `~safir.middleware.ivoa.CaseInsensitiveFormMiddleware`, to implement this protocol requirement. + +Add these middlewares to the FastAPI application: .. code-block:: python - from safir.middleware.ivoa import CaseInsensitiveQueryMiddleware + from safir.middleware.ivoa import ( + CaseInsensitiveFormMiddleware, + CaseInsensitiveQueryMiddleware, + ) app = FastAPI() + app.add_middleware(CaseInsensitiveFormMiddleware) app.add_middleware(CaseInsensitiveQueryMiddleware) -In the route handlers, declare all query parameters in all lowercase. +In the route handlers, declare all form and query parameters in all lowercase. For instance, for the above example queries: .. code-block:: python diff --git a/safir/src/safir/middleware/ivoa.py b/safir/src/safir/middleware/ivoa.py index 09f64def..50431cdc 100644 --- a/safir/src/safir/middleware/ivoa.py +++ b/safir/src/safir/middleware/ivoa.py @@ -5,7 +5,10 @@ from starlette.types import ASGIApp, Receive, Scope, Send -__all__ = ["CaseInsensitiveQueryMiddleware"] +__all__ = [ + "CaseInsensitiveFormMiddleware", + "CaseInsensitiveQueryMiddleware", +] class CaseInsensitiveQueryMiddleware: @@ -74,13 +77,13 @@ async def __call__( scope = copy(scope) - if scope["method"] == "POST" and self.is_form_data(scope): - receive = self.wrapped_receive(receive) + if scope["method"] == "POST" and self._is_form_data(scope): + receive = self._wrapped_receive(receive) await self._app(scope, receive, send) @staticmethod - def is_form_data(scope: Scope) -> bool: + def _is_form_data(scope: Scope) -> bool: """Check if the request contains form data. Parameters @@ -101,7 +104,7 @@ def is_form_data(scope: Scope) -> bool: return content_type.startswith("application/x-www-form-urlencoded") @staticmethod - async def get_body(receive: Receive) -> bytes: + async def _get_body(receive: Receive) -> bytes: """Read the entire request body. Parameters @@ -123,7 +126,7 @@ async def get_body(receive: Receive) -> bytes: return body @staticmethod - async def process_form_data(body: bytes) -> bytes: + async def _process_form_data(body: bytes) -> bytes: """Process the body, lowercasing keys of form data. Parameters @@ -142,7 +145,7 @@ async def process_form_data(body: bytes) -> bytes: processed = urlencode(lowercased) return processed.encode("utf-8") - def wrapped_receive(self, receive: Receive) -> Receive: + def _wrapped_receive(self, receive: Receive) -> Receive: """Wrap the receive function to process form data. Parameters @@ -166,8 +169,8 @@ async def inner() -> dict: "more_body": False, } - body = await self.get_body(receive) - processed_body = await self.process_form_data(body) + body = await self._get_body(receive) + processed_body = await self._process_form_data(body) processed = True return { "type": "http.request",