Skip to content

Cql2RewriteLinksFilterMiddleware

Middleware to rewrite 'filter' in .links of the JSON response, removing the filter from the request state.

Cql2RewriteLinksFilterMiddleware dataclass

ASGI middleware to rewrite 'filter' in .links of the JSON response, removing the filter from the request state.

Parameters:

Name Type Description Default
app Callable[list, Awaitable[None]]
required
state_key str
'cql2_filter'
Source code in src/stac_auth_proxy/middleware/Cql2RewriteLinksFilterMiddleware.py
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
@dataclass(frozen=True)
class Cql2RewriteLinksFilterMiddleware:
    """ASGI middleware to rewrite 'filter' in .links of the JSON response, removing the filter from the request state."""

    app: ASGIApp
    state_key: str = "cql2_filter"

    async def __call__(self, scope: Scope, receive: Receive, send: Send) -> None:
        """Replace 'filter' in .links of the JSON response to state before we had applied the filter."""
        if scope["type"] != "http":
            return await self.app(scope, receive, send)

        request = Request(scope)
        original_filter = request.query_params.get("filter")
        cql2_filter: Optional[Expr] = getattr(request.state, self.state_key, None)
        if cql2_filter is None:
            # No filter set, just pass through
            return await self.app(scope, receive, send)

        # Intercept the response
        response_start = None
        body_chunks = []
        more_body = True

        async def send_wrapper(message: Message):
            nonlocal response_start, body_chunks, more_body
            if message["type"] == "http.response.start":
                response_start = message
            elif message["type"] == "http.response.body":
                body_chunks.append(message.get("body", b""))
                more_body = message.get("more_body", False)
                if not more_body:
                    await self._process_and_send_response(
                        response_start, body_chunks, send, original_filter
                    )
            else:
                await send(message)

        await self.app(scope, receive, send_wrapper)

    async def _process_and_send_response(
        self,
        response_start: Message,
        body_chunks: list[bytes],
        send: Send,
        original_filter: Optional[str],
    ):
        body = b"".join(body_chunks)
        try:
            data = json.loads(body)
        except Exception:
            await send(response_start)
            await send({"type": "http.response.body", "body": body, "more_body": False})
            return

        cql2_filter = Expr(original_filter) if original_filter else None
        links = data.get("links")
        if isinstance(links, list):
            for link in links:
                # Handle filter in query string
                if "href" in link:
                    url = urlparse(link["href"])
                    qs = parse_qs(url.query)
                    if "filter" in qs:
                        if cql2_filter:
                            qs["filter"] = [cql2_filter.to_text()]
                        else:
                            qs.pop("filter", None)
                            qs.pop("filter-lang", None)
                        new_query = urlencode(qs, doseq=True)
                        link["href"] = urlunparse(url._replace(query=new_query))

                # Handle filter in body (for POST links)
                if "body" in link and isinstance(link["body"], dict):
                    if "filter" in link["body"]:
                        if cql2_filter:
                            link["body"]["filter"] = cql2_filter.to_json()
                        else:
                            link["body"].pop("filter", None)
                            link["body"].pop("filter-lang", None)

        # Send the modified response
        new_body = json.dumps(data).encode("utf-8")

        # Patch content-length
        headers = [
            (k, v) for k, v in response_start["headers"] if k != b"content-length"
        ]
        headers.append((b"content-length", str(len(new_body)).encode("latin1")))
        response_start = dict(response_start)
        response_start["headers"] = headers
        await send(response_start)
        await send({"type": "http.response.body", "body": new_body, "more_body": False})

__call__(scope: Scope, receive: Receive, send: Send) -> None async

Replace 'filter' in .links of the JSON response to state before we had applied the filter.

Source code in src/stac_auth_proxy/middleware/Cql2RewriteLinksFilterMiddleware.py
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
async def __call__(self, scope: Scope, receive: Receive, send: Send) -> None:
    """Replace 'filter' in .links of the JSON response to state before we had applied the filter."""
    if scope["type"] != "http":
        return await self.app(scope, receive, send)

    request = Request(scope)
    original_filter = request.query_params.get("filter")
    cql2_filter: Optional[Expr] = getattr(request.state, self.state_key, None)
    if cql2_filter is None:
        # No filter set, just pass through
        return await self.app(scope, receive, send)

    # Intercept the response
    response_start = None
    body_chunks = []
    more_body = True

    async def send_wrapper(message: Message):
        nonlocal response_start, body_chunks, more_body
        if message["type"] == "http.response.start":
            response_start = message
        elif message["type"] == "http.response.body":
            body_chunks.append(message.get("body", b""))
            more_body = message.get("more_body", False)
            if not more_body:
                await self._process_and_send_response(
                    response_start, body_chunks, send, original_filter
                )
        else:
            await send(message)

    await self.app(scope, receive, send_wrapper)