Skip to content

Commit

Permalink
Merge pull request #345 from lsst-sqre/tickets/DM-47769
Browse files Browse the repository at this point in the history
DM-47769: Add documentation of CaseInsensitiveFormMiddleware
  • Loading branch information
rra authored Nov 28, 2024
2 parents 3c9b2a3 + a898c43 commit fbde5f0
Show file tree
Hide file tree
Showing 2 changed files with 24 additions and 15 deletions.
18 changes: 12 additions & 6 deletions docs/user-guide/ivoa.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
21 changes: 12 additions & 9 deletions safir/src/safir/middleware/ivoa.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,10 @@

from starlette.types import ASGIApp, Receive, Scope, Send

__all__ = ["CaseInsensitiveQueryMiddleware"]
__all__ = [
"CaseInsensitiveFormMiddleware",
"CaseInsensitiveQueryMiddleware",
]


class CaseInsensitiveQueryMiddleware:
Expand Down Expand Up @@ -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
Expand All @@ -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
Expand All @@ -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
Expand All @@ -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
Expand All @@ -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",
Expand Down

0 comments on commit fbde5f0

Please sign in to comment.